{
  "openapi": "3.1.0",
  "info": {
    "title": "Pixeltable public agent APIs",
    "version": "1.0.0",
    "description": "Public, unauthenticated agent endpoints on pixeltable.com (NLWeb Q&A, WebMCP, liveness). Versioning: send `API-Version: 1` or use `/v1/ask`, `/v1/mcp`, `/v1/health` aliases. Breaking changes get a new version and are announced with `Deprecation` and `Sunset` response headers before removal. Pixeltable Cloud REST remains authenticated; see /auth.md and /.well-known/oauth-protected-resource.",
    "contact": {
      "email": "contact@pixeltable.com",
      "url": "https://pixeltable.com/developers"
    }
  },
  "servers": [
    { "url": "https://pixeltable.com", "description": "Current host (API-Version 1)" },
    { "url": "https://pixeltable.com/v1", "description": "URL path version 1" }
  ],
  "tags": [
    { "name": "ask", "description": "NLWeb natural-language Q&A" },
    { "name": "mcp", "description": "Read-only WebMCP" },
    { "name": "health", "description": "Liveness" }
  ],
  "paths": {
    "/ask": {
      "get": {
        "operationId": "askGet",
        "tags": ["ask"],
        "summary": "NLWeb natural-language Q&A",
        "description": "Ask a question about Pixeltable. Returns JSON with a top-level `_meta` block, or Server-Sent Events when the client requests streaming. Rate limit: 10 requests per minute per IP. RateLimit and Retry-After headers are always present on success and 429.",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "maxLength": 2000 },
            "description": "Natural-language question. Alias: `q`."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "maxLength": 2000 }
          },
          {
            "name": "Accept",
            "in": "header",
            "schema": { "type": "string", "example": "text/event-stream" }
          },
          {
            "name": "Prefer",
            "in": "header",
            "schema": { "type": "string", "example": "streaming=true" }
          }
        ],
        "responses": {
          "200": {
            "description": "Answer JSON or SSE stream",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AskSuccess" }
              },
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limited (10 requests per minute per IP)",
            "headers": {
              "Retry-After": { "schema": { "type": "integer" } },
              "RateLimit": { "schema": { "type": "string" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "askPost",
        "tags": ["ask"],
        "summary": "NLWeb natural-language Q&A",
        "description": "Same as GET /ask. Retries with the same Idempotency-Key replay the first JSON response for 10 minutes. Streaming responses are not cached.",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AskRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Answer JSON or SSE stream",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AskSuccess" }
              },
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limited (10 requests per minute per IP)",
            "headers": {
              "Retry-After": { "schema": { "type": "integer" } },
              "RateLimit": { "schema": { "type": "string" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "get": {
        "operationId": "mcpDescriptor",
        "tags": ["mcp"],
        "summary": "WebMCP transport descriptor",
        "description": "JSON descriptor of the read-only WebMCP server (also served at /.well-known/mcp).",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "responses": {
          "200": {
            "description": "Transport descriptor",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/McpDescriptor" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "mcpJsonRpc",
        "tags": ["mcp"],
        "summary": "WebMCP JSON-RPC",
        "description": "JSON-RPC 2.0 methods: initialize, tools/list, tools/call, ping. Tools: search_docs, list_integrations. Read-only. Idempotency-Key replays the first JSON-RPC response for 10 minutes.",
        "parameters": [
          { "$ref": "#/components/parameters/ApiVersion" },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/JsonRpcRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or error object",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/JsonRpcResponse" }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "tags": ["health"],
        "summary": "Liveness probe",
        "description": "Returns 200 when this process can serve public agent endpoints.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "responses": {
          "200": {
            "description": "Process is up",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ApiVersion": {
        "name": "API-Version",
        "in": "header",
        "required": false,
        "description": "API version. Only `1` is currently served. Omitted requests use version 1. Retiring versions are signaled with Deprecation and Sunset headers.",
        "schema": { "type": "string", "enum": ["1"], "default": "1" }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Client-supplied key so a retried POST returns the first JSON response instead of running again. Keys live 10 minutes. Max 256 characters.",
        "schema": { "type": "string", "minLength": 1, "maxLength": 256 }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "status", "retryable"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable code",
                "enum": ["bad_request", "rate_limited", "not_found", "invalid_json"]
              },
              "message": { "type": "string" },
              "status": { "type": "integer" },
              "retryable": { "type": "boolean" }
            }
          }
        }
      },
      "AskRequest": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "maxLength": 2000 },
          "q": { "type": "string", "maxLength": 2000 },
          "prefer": {
            "type": "object",
            "properties": { "streaming": { "type": "boolean" } }
          }
        }
      },
      "AskResult": {
        "type": "object",
        "required": ["@type", "name", "url"],
        "properties": {
          "@type": { "type": "string", "example": "WebPage" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "description": { "type": "string" }
        }
      },
      "AskSuccess": {
        "type": "object",
        "required": ["query_id", "query", "results", "answer", "_meta"],
        "properties": {
          "query_id": { "type": "string", "format": "uuid" },
          "query": { "type": "string" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/AskResult" }
          },
          "answer": { "type": "string" },
          "_meta": {
            "type": "object",
            "required": ["response_type", "version", "site"],
            "properties": {
              "response_type": { "type": "string", "example": "summary" },
              "version": { "type": "string", "example": "0.1" },
              "site": { "type": "string", "example": "pixeltable.com" },
              "streaming": { "type": "boolean" }
            }
          }
        }
      },
      "McpDescriptor": {
        "type": "object",
        "required": ["transport", "protocol", "protocolVersion", "serverInfo", "tools"],
        "properties": {
          "transport": { "type": "string", "example": "webmcp" },
          "protocol": { "type": "string", "example": "mcp" },
          "protocolVersion": { "type": "string" },
          "serverInfo": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "version": { "type": "string" }
            }
          },
          "instructions": { "type": "string" },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "description": { "type": "string" }
              }
            }
          },
          "usage": { "type": "string" }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": {},
          "method": { "type": "string" },
          "params": { "type": "object" }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": {},
          "result": { "type": "object" },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" },
              "data": {}
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "string", "example": "ok" }
        }
      }
    },
    "responses": {
      "AskSuccess": {
        "description": "Answer JSON or SSE stream",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/AskSuccess" }
          },
          "text/event-stream": {
            "schema": { "type": "string" }
          }
        }
      },
      "BadRequest": {
        "description": "Missing or invalid query",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited (10 requests per minute per IP)",
        "headers": {
          "Retry-After": {
            "schema": { "type": "integer" },
            "description": "Seconds until the window resets"
          },
          "RateLimit": {
            "schema": { "type": "string" },
            "description": "RFC RateLimit header: limit, remaining, reset"
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      }
    }
  }
}
