{
  "openapi": "3.1.0",
  "info": {
    "title": "Ayush Rai public API",
    "version": "1.0.0",
    "description": "Read-only API over the same public content as Ayush Rai's website. Unverified award metrics are omitted. There is no separate dataset for this API.\n\nAccess: Free and public. No account or API key is needed because every endpoint is read-only over published content. There is no separate test environment or paid tier. Clients may cache responses for five minutes. REST responses grant CORS only to the site's own origins, so call the REST API from a server, an agent, or the CLI rather than from a browser page on another site. The MCP endpoint and its Server Card allow any origin.\n\nRate limits: 60 requests per 60 seconds per client address, counted per server instance. Every response carries RateLimit-Policy and RateLimit headers (draft-ietf-httpapi-ratelimit-headers-11). Over the limit, the API returns 429 with a Retry-After header in seconds and error code rate_limited.\n\nErrors: every error body is { \"error\": { \"code\", \"message\", \"hint\", \"docs\" } }. code is stable; hint says how to fix the request.\n\nVersioning: Versioned in the URL path. /api/v1 is stable: fields may be added, but none are removed or renamed. Breaking changes ship under a new path such as /api/v2. A replaced version keeps working for at least 90 days; during that window its responses carry Deprecation and Sunset headers and a Link with rel=\"successor-version\", and the dates are listed on /developers. The unversioned /api/search and /api/projects are aliases of v1 and follow the same policy. No version is deprecated today.\n\nMCP: the same project and search tools are available over Streamable HTTP at POST /api/mcp. Its Server Card is at GET /api/mcp/server-card.",
    "contact": {
      "name": "Ayush Rai",
      "email": "ayushrai0211@gmail.com",
      "url": "https://www.ayushrai.site/developers/"
    }
  },
  "externalDocs": {
    "description": "Developer notes",
    "url": "https://www.ayushrai.site/developers/"
  },
  "servers": [
    {
      "url": "https://www.ayushrai.site"
    }
  ],
  "security": [],
  "paths": {
    "/api/v1/search": {
      "get": {
        "operationId": "searchPortfolioV1",
        "summary": "Search the public site index",
        "description": "Lexical match on title, then tags or type, then summary. Ties keep content order. This is not semantic search. An empty q returns the same default slice as the on-site search dialog. Page with cursor and nextCursor.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "person",
                "work",
                "product",
                "project",
                "research",
                "experience",
                "education",
                "leadership",
                "recognition",
                "idea",
                "principle",
                "note",
                "writing",
                "elsewhere",
                "capability",
                "domain"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to 12 with a query and 8 without.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque nextCursor from the previous page. Omit for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching public records",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "searchPortfolio",
        "summary": "Search the public site index (v1 alias)",
        "description": "Lexical match on title, then tags or type, then summary. Ties keep content order. This is not semantic search. An empty q returns the same default slice as the on-site search dialog. Page with cursor and nextCursor.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "person",
                "work",
                "product",
                "project",
                "research",
                "experience",
                "education",
                "leadership",
                "recognition",
                "idea",
                "principle",
                "note",
                "writing",
                "elsewhere",
                "capability",
                "domain"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Defaults to 12 with a query and 8 without.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque nextCursor from the previous page. Omit for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching public records",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/projects": {
      "get": {
        "operationId": "projects",
        "summary": "List, fetch, or compare public projects",
        "description": "v1 alias. No query lists projects in pages, like /api/v1/projects. slug returns one project. compare accepts 2 to 4 comma-separated slugs.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "tooling",
                "education",
                "client",
                "studio",
                "web",
                "personal",
                "data"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "product",
                "project",
                "client",
                "experiment",
                "open-source"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20
            }
          },
          {
            "name": "archive",
            "in": "query",
            "required": false,
            "description": "Pass 1 to include archived projects.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque nextCursor from the previous page. Omit for the first page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "compare",
            "in": "query",
            "required": false,
            "description": "Comma-separated slugs, two to four",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slugs",
            "in": "query",
            "required": false,
            "description": "Same as compare; read only when compare is absent.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of projects, one project, or a comparison",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectsAliasResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or archived slug. error.code is not_found.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "listProjectsV1",
        "summary": "List public projects",
        "description": "Active projects by default. Pass archive=1 to include archived records. Maximum limit is 20 when set; the default page is 50. Page with cursor and nextCursor.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "tooling",
                "education",
                "client",
                "studio",
                "web",
                "personal",
                "data"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "product",
                "project",
                "client",
                "experiment",
                "open-source"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20
            }
          },
          {
            "name": "archive",
            "in": "query",
            "required": false,
            "description": "Pass 1 to include archived projects.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque nextCursor from the previous page. Omit for the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of projects",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{slug}": {
      "get": {
        "operationId": "getProjectV1",
        "summary": "Fetch one public project",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
            }
          },
          {
            "name": "archive",
            "in": "query",
            "required": false,
            "description": "Pass 1 to include archived projects.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One project",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or archived slug. error.code is not_found.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/compare": {
      "get": {
        "operationId": "compareProjectsV1",
        "summary": "Compare two to four public projects",
        "parameters": [
          {
            "name": "slugs",
            "in": "query",
            "required": true,
            "description": "Comma-separated slugs, two to four, no duplicates",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "archive",
            "in": "query",
            "required": false,
            "description": "Pass 1 to include archived projects.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Projects found, plus slugs that were not",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompareResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, filter, slug, cursor, or compare list. error.code is one of invalid_query, invalid_slug, invalid_filter, invalid_compare, invalid_cursor.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server fault, not a problem with the request. error.code is internal_error.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/mcp/server-card": {
      "get": {
        "operationId": "getMcpServerCard",
        "summary": "MCP Server Card for the Streamable HTTP endpoint",
        "description": "SEP-2127 Server Card. Supports ETag and If-None-Match. The card itself conforms to https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json.",
        "responses": {
          "200": {
            "description": "Server Card",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "ETag": {
                "description": "Entity tag of the card",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/mcp-server-card+json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerCard"
                }
              }
            }
          },
          "304": {
            "description": "Not modified since the ETag sent in If-None-Match"
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. REST accepts GET, HEAD, and OPTIONS.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/ask": {
      "get": {
        "operationId": "askGet",
        "summary": "NLWeb-shaped lexical ask (GET)",
        "description": "Natural-language query over the same lexical index as /api/v1/search. Not generative. Pass query or q. Responses include _meta.version (0.55) and _meta.response_type.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Alias for query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "streaming",
            "in": "query",
            "required": false,
            "description": "Pass true for a text/event-stream response (start, result, complete events).",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "NLWeb conversational_search answer or failure. With streaming, text/event-stream frames: start (_meta.streaming true), result ({ index, item }) per hit or error, then complete.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. /ask accepts GET, HEAD, and POST.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "askPost",
        "summary": "NLWeb-shaped lexical ask (POST)",
        "description": "Same as GET /ask. Body may be { \"query\": \"…\" }, { \"query\": { \"text\": \"…\" } }, or { \"q\": \"…\" }.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "NLWeb conversational_search answer or failure. With streaming, text/event-stream frames: start (_meta.streaming true), result ({ index, item }) per hit or error, then complete.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "POST body is not JSON. _meta.response_type is failure; error.code is invalid_query.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          },
          "405": {
            "description": "Unsupported HTTP method. error.code is method_not_allowed. /ask accepts GET, HEAD, and POST.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. error.code is rate_limited.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "RateLimit": {
        "description": "Remaining quota and seconds until reset, for example \"default\";r=59;t=60.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitPolicy": {
        "description": "Quota policy: \"default\";q=60;w=60.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "schemas": {
      "SearchItem": {
        "type": "object",
        "required": [
          "id",
          "title",
          "type",
          "summary",
          "href",
          "tags"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "person",
              "work",
              "product",
              "project",
              "research",
              "experience",
              "education",
              "leadership",
              "recognition",
              "idea",
              "principle",
              "note",
              "writing",
              "elsewhere",
              "capability",
              "domain"
            ]
          },
          "summary": {
            "type": "string"
          },
          "href": {
            "type": "string",
            "description": "Site-relative path of the page"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PublicProject": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "summary",
          "type",
          "category",
          "status",
          "yearStart",
          "technologies",
          "archive",
          "pagePath",
          "htmlUrl"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "product",
              "project",
              "client",
              "experiment",
              "open-source"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "tooling",
              "education",
              "client",
              "studio",
              "web",
              "personal",
              "data"
            ]
          },
          "status": {
            "type": "string"
          },
          "yearStart": {
            "type": "integer"
          },
          "yearEnd": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "const": "present"
              }
            ]
          },
          "technologies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "links": {
            "type": "object",
            "properties": {
              "demo": {
                "type": "string",
                "format": "uri"
              },
              "github": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "archive": {
            "type": "boolean"
          },
          "workPath": {
            "type": "string",
            "description": "On-site case study, when one exists"
          },
          "pagePath": {
            "type": "string"
          },
          "htmlUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": [
          "query",
          "match",
          "ranking",
          "results",
          "nextCursor"
        ],
        "properties": {
          "query": {
            "type": "string"
          },
          "match": {
            "type": "string",
            "const": "lexical"
          },
          "ranking": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchItem"
            }
          },
          "nextCursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "AskRequest": {
        "type": "object",
        "properties": {
          "query": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 200
                  }
                }
              }
            ]
          },
          "q": {
            "type": "string",
            "maxLength": 200
          },
          "prefer": {
            "type": "object",
            "properties": {
              "streaming": {
                "type": "boolean",
                "description": "true selects the SSE response"
              }
            }
          }
        }
      },
      "AskResponse": {
        "type": "object",
        "required": [
          "_meta"
        ],
        "properties": {
          "query": {
            "type": "string"
          },
          "site": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "total_results": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "_meta": {
            "type": "object",
            "required": [
              "version",
              "response_type"
            ],
            "properties": {
              "version": {
                "type": "string",
                "const": "0.55"
              },
              "response_type": {
                "type": "string",
                "enum": [
                  "answer",
                  "failure"
                ]
              },
              "response_format": {
                "type": "string"
              },
              "mode": {
                "type": "string"
              },
              "site": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "ProjectListResponse": {
        "type": "object",
        "required": [
          "projects",
          "count",
          "nextCursor"
        ],
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicProject"
            }
          },
          "count": {
            "type": "integer",
            "description": "Number of projects in this page"
          },
          "nextCursor": {
            "$ref": "#/components/schemas/NextCursor"
          }
        }
      },
      "ProjectResponse": {
        "type": "object",
        "required": [
          "project"
        ],
        "properties": {
          "project": {
            "$ref": "#/components/schemas/PublicProject"
          }
        }
      },
      "CompareResponse": {
        "type": "object",
        "required": [
          "projects",
          "missing"
        ],
        "properties": {
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicProject"
            }
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Requested slugs with no public project"
          }
        }
      },
      "ProjectsAliasResponse": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/ProjectListResponse"
          },
          {
            "$ref": "#/components/schemas/ProjectResponse"
          },
          {
            "$ref": "#/components/schemas/CompareResponse"
          }
        ]
      },
      "NextCursor": {
        "type": [
          "string",
          "null"
        ],
        "description": "Pass as cursor to fetch the next page. null means this is the last page. Treat it as opaque."
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint",
              "docs"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_query",
                  "invalid_slug",
                  "invalid_filter",
                  "invalid_compare",
                  "invalid_cursor",
                  "not_found",
                  "route_not_found",
                  "method_not_allowed",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "What was wrong with this request"
              },
              "hint": {
                "type": "string",
                "description": "How to fix the request"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "ServerCard": {
        "type": "object",
        "required": [
          "$schema",
          "name",
          "version",
          "description"
        ],
        "properties": {
          "$schema": {
            "type": "string",
            "const": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json"
          },
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$"
          },
          "version": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "maxLength": 100
          },
          "title": {
            "type": "string"
          },
          "websiteUrl": {
            "type": "string",
            "format": "uri"
          },
          "repository": {
            "type": "object",
            "required": [
              "url",
              "source"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "source": {
                "type": "string"
              }
            }
          },
          "remotes": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "url"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "streamable-http",
                    "sse"
                  ]
                },
                "url": {
                  "type": "string"
                },
                "supportedProtocolVersions": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
