{
  "openapi": "3.1.0",
  "info": {
    "title": "nikunjk.com API",
    "version": "1.0.0",
    "summary": "Public API for nikunjk.com, the personal site of Nikunj Kothari (Partner at FPV Ventures)",
    "description": "Read-only API exposing the content of nikunjk.com for developers and AI agents: profile pages as markdown, recent writing, Open Graph images, and a logo proxy. An MCP (Model Context Protocol) server is available at /api/mcp over Streamable HTTP. All endpoints are public and require no authentication. Errors are returned as structured JSON with `success: false`, an `error` message, and a machine-readable `code`.",
    "termsOfService": "https://www.nikunjk.com",
    "contact": {
      "name": "Nikunj Kothari",
      "url": "https://www.nikunjk.com/developers",
      "email": "nikunj@fpvventures.com"
    }
  },
  "servers": [
    {
      "url": "https://www.nikunjk.com",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "content", "description": "Site content: profile, pages, and writing" },
    { "name": "agents", "description": "Endpoints designed for AI agents (MCP, markdown mirrors, llms.txt)" },
    { "name": "utilities", "description": "Images and other utilities" }
  ],
  "paths": {
    "/api/writing": {
      "get": {
        "operationId": "listWriting",
        "tags": ["content"],
        "summary": "List recent blog posts",
        "description": "Returns up to 50 recent posts from writing.nikunjk.com (title, URL, image, description, publish date).",
        "responses": {
          "200": {
            "description": "Recent posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["success", "data"],
                  "properties": {
                    "success": { "const": true },
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/WritingPost" }
                    }
                  }
                }
              }
            }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/md": {
      "get": {
        "operationId": "listMarkdownPages",
        "tags": ["agents"],
        "summary": "List pages available as markdown",
        "description": "Returns a markdown index of every page that has a markdown mirror. Individual pages are served at /api/md/{page} or by appending .md to a page URL (e.g. /founders.md).",
        "responses": {
          "200": {
            "description": "Markdown index of available pages",
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/md/{page}": {
      "get": {
        "operationId": "getPageMarkdown",
        "tags": ["agents"],
        "summary": "Get a page as markdown",
        "description": "Returns the markdown mirror of a page with YAML front matter (title, description, url, lastModified). Also reachable as /{page}.md (e.g. /founders.md).",
        "parameters": [
          {
            "name": "page",
            "in": "path",
            "required": true,
            "description": "Page identifier",
            "schema": {
              "type": "string",
              "examples": ["index", "founders", "founder-testimonials", "developers"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page content as markdown with YAML front matter",
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          },
          "404": {
            "description": "Unknown page; the body is a markdown document listing available pages",
            "content": { "text/markdown": { "schema": { "type": "string" } } }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "mcp",
        "tags": ["agents"],
        "summary": "MCP server (Streamable HTTP)",
        "description": "Model Context Protocol server over Streamable HTTP. Send JSON-RPC 2.0 messages (initialize, tools/list, tools/call, ping). Stateless: no session ID required. Tools: get_profile, get_page, list_writing. A discovery manifest is published at /.well-known/mcp.json.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" },
              "examples": {
                "initialize": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "initialize",
                    "params": {
                      "protocolVersion": "2025-06-18",
                      "capabilities": {},
                      "clientInfo": { "name": "example-client", "version": "1.0.0" }
                    }
                  }
                },
                "listTools": {
                  "value": { "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
                },
                "callTool": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 3,
                    "method": "tools/call",
                    "params": { "name": "get_profile", "arguments": {} }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC 2.0 response",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JsonRpcResponse" }
              }
            }
          },
          "202": { "description": "Notification accepted (no response body)" },
          "400": {
            "description": "Malformed JSON or invalid JSON-RPC message",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JsonRpcResponse" }
              }
            }
          },
          "405": {
            "description": "Non-POST request; this server does not offer a standalone SSE stream",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JsonRpcResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/logo/{domain}": {
      "get": {
        "operationId": "getLogo",
        "tags": ["utilities"],
        "summary": "Get a company logo by domain",
        "description": "Fetches a logo for the given domain from public sources, falling back to a generated SVG placeholder. Rate limited to 60 requests/minute per IP.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "Public domain name, e.g. railway.com",
            "schema": { "type": "string", "examples": ["railway.com"] }
          }
        ],
        "responses": {
          "200": {
            "description": "Logo image (PNG, ICO, or SVG placeholder)",
            "content": {
              "image/png": { "schema": { "type": "string", "format": "binary" } },
              "image/svg+xml": { "schema": { "type": "string" } }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/og": {
      "get": {
        "operationId": "getOgImage",
        "tags": ["utilities"],
        "summary": "Generate an Open Graph image",
        "description": "Renders a 1200x630 Open Graph image with the given title and subtitle.",
        "parameters": [
          {
            "name": "title",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "maxLength": 120 }
          },
          {
            "name": "subtitle",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "maxLength": 200 }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["default", "portfolio", "speaking", "article"] }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG image",
            "content": { "image/png": { "schema": { "type": "string", "format": "binary" } } }
          }
        }
      }
    },
    "/api/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": ["agents"],
        "summary": "XML sitemap",
        "responses": {
          "200": {
            "description": "Sitemap XML",
            "content": { "text/xml": { "schema": { "type": "string" } } }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": ["agents"],
        "summary": "llms.txt for AI agents",
        "description": "Structured plain-text profile of Nikunj Kothari optimized for LLMs, with links to all machine-readable resources. A longer version is at /llms-full.txt.",
        "responses": {
          "200": {
            "description": "llms.txt content",
            "content": { "text/plain": { "schema": { "type": "string" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "WritingPost": {
        "type": "object",
        "required": ["title", "url"],
        "properties": {
          "title": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "imageUrl": { "type": "string" },
          "description": { "type": "string" },
          "publishedAt": { "type": "string" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["success", "error"],
        "properties": {
          "success": { "const": false },
          "error": { "type": "string", "description": "Human-readable error message" },
          "code": {
            "type": "string",
            "description": "Machine-readable error code",
            "examples": ["NOT_FOUND", "METHOD_NOT_ALLOWED", "FEED_FETCH_ERROR"]
          },
          "hint": { "type": "string", "description": "How to resolve the error" },
          "docs": { "type": "string", "format": "uri", "description": "Link to API documentation" }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "method": { "type": "string", "examples": ["initialize", "tools/list", "tools/call", "ping"] },
          "params": { "type": "object" }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc", "id"],
        "properties": {
          "jsonrpc": { "const": "2.0" },
          "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "result": { "type": "object" },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameters",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "MethodNotAllowed": {
        "description": "HTTP method not allowed",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded; retry after the interval in the Retry-After header",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServerError": {
        "description": "Internal server error",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
