{
  "openapi": "3.1.0",
  "info": {
    "title": "Anuraag Burman — Portfolio API",
    "version": "1.0.0",
    "description": "A public, read-only, unauthenticated JSON API over the same content that powers anuraagburman.com: Anuraag's profile, projects (including full case studies), and articles. Built so AI agents and LLM-based tools can discover, query, and cite this content directly instead of scraping rendered HTML. Every response is plain JSON; every non-2xx response uses the same small Error envelope (see the Error schema); CORS is open to all origins. A Model Context Protocol (MCP) server exposing the same data as callable tools is also available at POST /mcp for MCP-aware clients — prefer it over these REST endpoints when your client speaks MCP.",
    "contact": {
      "email": "anuraagburman02@gmail.com",
      "url": "https://anuraagburman.com/contact"
    },
    "license": {
      "name": "Content and API responses are provided as-is for informational and AI-agent use; no explicit license is granted for commercial redistribution."
    }
  },
  "servers": [
    {
      "url": "https://anuraagburman.com/api"
    }
  ],
  "externalDocs": {
    "url": "https://anuraagburman.com/developers/",
    "description": "Human-readable developer documentation for this API, including getting-started examples."
  },
  "paths": {
    "/v1/profile": {
      "get": {
        "operationId": "getProfile",
        "summary": "Get Anuraag Burman's profile",
        "description": "Returns who Anuraag Burman is: name, job title, headline, bio, location, current role, career highlights, education, contact/social links, and a lightweight summary list of his projects (id/title/tagline/status only — use GET /v1/projects or GET /v1/projects/{slug} for more). Call this first for any general \"who is this person\" / \"tell me about them\" / \"how do I contact them\" question. Takes no parameters.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Profile returned successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects": {
      "get": {
        "operationId": "listProjects",
        "summary": "List projects",
        "description": "Returns short summaries of every project (title, tagline, status, tech stack, links) — not the full case study. Use this to browse or filter, then call GET /v1/projects/{slug} for the full write-up of one you're interested in. `status` and `limit` are both optional; omitting both returns every project.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "Shipped",
            "description": "Case-insensitive exact match against a project's status, e.g. \"Live\", \"Shipped\", \"In Progress\". A value that matches nothing returns an empty array, not an error."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 5,
            "description": "Maximum number of projects to return, applied after `status` filtering. Must be a positive integer."
          }
        ],
        "responses": {
          "200": {
            "description": "Project summaries matching the filters (possibly empty).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectSummary"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` was present but not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/projects/{slug}": {
      "get": {
        "operationId": "getProject",
        "summary": "Get one project by slug",
        "description": "Returns the full case study for a single project: problem statement, metrics, tech stack, links, and any deep-dive sections (Q&A, cost breakdowns, architecture layers — shape varies by section type). Get a valid `slug` from GET /v1/projects or GET /v1/search first; this returns 404 for an unknown one.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "localflow",
            "description": "The project's slug/id, as returned by GET /v1/projects."
          }
        ],
        "responses": {
          "200": {
            "description": "The project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "404": {
            "description": "No project exists with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/articles": {
      "get": {
        "operationId": "listArticles",
        "summary": "List articles",
        "description": "Returns short summaries of every article (title, excerpt, published date, tags, estimated reading time in minutes) — not the full text. Use this to browse or filter, then call GET /v1/articles/{slug} for the full markdown text of one you're interested in. `tag` and `limit` are both optional; omitting both returns every article.",
        "parameters": [
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "ai",
            "description": "Case-insensitive exact match against one of an article's tags. A value that matches nothing returns an empty array, not an error."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 5,
            "description": "Maximum number of articles to return, applied after `tag` filtering. Must be a positive integer."
          }
        ],
        "responses": {
          "200": {
            "description": "Article summaries matching the filters (possibly empty).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ArticleSummary"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` was present but not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/articles/{slug}": {
      "get": {
        "operationId": "getArticle",
        "summary": "Get one article by slug",
        "description": "Returns the full text of a single article as markdown (converted from the CMS's Portable Text), plus its title, excerpt, tags, published date, and reading time. Get a valid `slug` from GET /v1/articles or GET /v1/search first; this returns 404 for an unknown one.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "shipping-taste-at-scale",
            "description": "The article's slug, as returned by GET /v1/articles."
          }
        ],
        "responses": {
          "200": {
            "description": "The article, including bodyMarkdown.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "404": {
            "description": "No article exists with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/search": {
      "get": {
        "operationId": "search",
        "summary": "Search projects and articles",
        "description": "Full-text (case-insensitive substring) search across both projects (title/tagline/problem) and articles (title/excerpt/tags) in a single call, returning a flat, ranked-by-type (projects then articles) list. Use this when you don't already know whether what you're looking for is a project or an article, or don't know its slug — otherwise prefer GET /v1/projects or GET /v1/articles directly.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "example": "subscriptions",
            "description": "Search term. Required and must be non-empty."
          }
        ],
        "responses": {
          "200": {
            "description": "Matching projects and articles (possibly empty).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SearchResult"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`q` was missing or empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "406": {
            "description": "The `Accept` header explicitly excludes both application/json and */*, so this JSON-only endpoint cannot satisfy it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Get this OpenAPI document",
        "description": "Returns this OpenAPI 3.1 document describing every endpoint on this API, generated from the same single source of truth as the static /openapi.json file. Useful for tooling that discovers and validates the API programmatically (e.g. an LLM function-calling framework that ingests OpenAPI specs directly) rather than a human reading it by hand.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "This OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method used is not supported on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "callMcp",
        "summary": "MCP Streamable HTTP endpoint",
        "description": "Model Context Protocol (spec version 2025-06-18) endpoint over the Streamable HTTP transport. Send a single JSON-RPC 2.0 message as the body (`initialize`, `ping`, `tools/list`, `tools/call`, or a notification/response). This is a stateless, non-streaming server, so a request-shaped message always gets back a single JSON-RPC response object with Content-Type: application/json — never an SSE stream. Prefer this over the plain GET /v1/* endpoints when driving this API from an MCP-aware agent/client, since it exposes the same data as typed, self-describing tools (see tools/list). GET and DELETE on this same path both return 405: this server has no server-initiated push and no sessions to terminate.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "A single JSON-RPC 2.0 message (request or notification). Loosely typed here since JSON-RPC/MCP does not map cleanly onto OpenAPI's per-operation request/response model — see https://modelcontextprotocol.io/specification/2025-06-18/basic/transports for the authoritative shape.",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "description": "Present on requests/responses, absent on notifications."
                  },
                  "method": {
                    "type": "string",
                    "example": "tools/call"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "jsonrpc"
                ]
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                  "name": "get_profile",
                  "arguments": {}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A single JSON-RPC 2.0 response object, containing either `result` or `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "The body was a JSON-RPC notification or response (no `id`-correlated reply is sent for these)."
          },
          "400": {
            "description": "Malformed JSON, an invalid JSON-RPC envelope (missing `jsonrpc`/`method`), or an unsupported MCP-Protocol-Version header value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Standard error envelope returned by every endpoint on non-2xx responses.",
        "required": [
          "error",
          "status"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint",
              "documentation"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "project_not_found"
              },
              "message": {
                "type": "string",
                "example": "No project exists with slug 'foo'."
              },
              "hint": {
                "type": "string",
                "example": "List valid slugs at GET /api/v1/projects."
              },
              "documentation": {
                "type": "string",
                "format": "uri",
                "example": "https://anuraagburman.com/developers/"
              }
            }
          },
          "status": {
            "type": "integer",
            "example": 404
          }
        }
      },
      "ProjectSummary": {
        "type": "object",
        "description": "Short project summary as returned by listProjects, and nested inside Profile.projects.",
        "properties": {
          "id": {
            "type": "string",
            "example": "localflow"
          },
          "title": {
            "type": "string"
          },
          "tagline": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "Shipped"
          },
          "stack": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "Project": {
        "type": "object",
        "description": "Full project case study as returned by getProject.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "tagline": {
            "type": "string"
          },
          "problem": {
            "type": "string"
          },
          "problemFull": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "badges": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "color": {
            "type": "string"
          },
          "metrics": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "learned": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stack": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "updatedAt": {
            "type": "string"
          },
          "expandableSections": {
            "type": "array",
            "description": "Deep-dive sections: Q&A, cost breakdowns, architecture layers, etc. Shape varies by `type`.",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "ArticleSummary": {
        "type": "object",
        "description": "Short article summary as returned by listArticles.",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "readingTime": {
            "type": "integer",
            "description": "Estimated minutes to read (whole minutes, minimum 1 for any non-empty article)."
          }
        }
      },
      "Article": {
        "type": "object",
        "description": "Full article as returned by getArticle: every ArticleSummary field plus bodyMarkdown.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ArticleSummary"
          },
          {
            "type": "object",
            "properties": {
              "bodyMarkdown": {
                "type": "string",
                "description": "The article body, converted from the CMS's Portable Text to markdown."
              }
            }
          }
        ]
      },
      "Profile": {
        "type": "object",
        "description": "Profile summary as returned by getProfile.",
        "properties": {
          "name": {
            "type": "string"
          },
          "jobTitle": {
            "type": "string"
          },
          "headline": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "location": {
            "type": "string"
          },
          "currentRole": {
            "type": "object",
            "properties": {
              "company": {
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "badge": {
                "type": "string"
              },
              "period": {
                "type": "string"
              },
              "location": {
                "type": "string"
              }
            }
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "metric": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          },
          "education": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "institution": {
                  "type": "string"
                },
                "degree": {
                  "type": "string"
                },
                "years": {
                  "type": "string"
                }
              }
            }
          },
          "links": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "linkedin": {
                "type": "string",
                "format": "uri"
              },
              "github": {
                "type": "string",
                "format": "uri"
              },
              "site": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "projects": {
            "type": "array",
            "description": "Lightweight project list — id/title/tagline/status only. See GET /v1/projects for the fuller ProjectSummary.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "tagline": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "project",
              "article"
            ]
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}
