{
  "openapi": "3.1.0",
  "info": {
    "title": "JewishMythology.com public API",
    "version": "1.0.0",
    "summary": "Unauthenticated search, RAG, and Maggid chat for the Jewish Mythology corpus.",
    "description": "Public, unauthenticated read APIs for semantic search, Maggid RAG answers, and on-page Maggid chat. Free open reference library — no API keys, no OAuth, no paid tiers.\n\nVersioning: `API-Version: 1` is sent on every response. The canonical URL prefix is `/api/v1/`. Unversioned `/api/*` paths are aliases of v1 while 1.x remains backward compatible. Breaking changes will increment the major version and be served under `/api/v{major}/`. Deprecations use RFC 9745 `Deprecation` and `Sunset` headers.\n\nErrors use RFC 9457 `application/problem+json` with a stable `code`, `type` URL, `title`, `status`, and `detail`. The `error` field is a legacy alias of `detail` for existing browser clients.\n\nRate limit: 60 requests per 60 seconds per IP. Responses include IETF `RateLimit` and `RateLimit-Policy` headers; 429 responses also include `Retry-After`.\n\nHuman/agent docs: https://jewishmythology.com/docs",
    "contact": {
      "name": "Arthur Ariel Sabintsev",
      "email": "arthur.sabintsev@gmail.com",
      "url": "https://jewishmythology.com/contact"
    },
    "license": {
      "name": "Site terms",
      "url": "https://jewishmythology.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://jewishmythology.com",
      "description": "Production (unversioned /api/* aliases v1)"
    }
  ],
  "tags": [
    { "name": "Search", "description": "Vector retrieval over myths, texts, and source collections" },
    { "name": "Maggid", "description": "RAG answers and on-page chat" },
    { "name": "Discovery", "description": "Status and catalog" }
  ],
  "paths": {
    "/api/v1/semantic-search": {
      "post": {
        "operationId": "semanticSearchV1",
        "tags": ["Search"],
        "summary": "Vector search over myths, texts, and source collections",
        "description": "Embeds the query and returns ranked semantic documents from the Vectorize index. No generative answer.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/SemanticSearch" },
        "responses": {
          "200": { "$ref": "#/components/responses/SemanticSearchOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" },
          "502": { "$ref": "#/components/responses/Problem502" }
        }
      }
    },
    "/api/semantic-search": {
      "post": {
        "operationId": "semanticSearch",
        "tags": ["Search"],
        "summary": "Vector search (alias of /api/v1/semantic-search)",
        "description": "Unversioned alias of POST /api/v1/semantic-search.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/SemanticSearch" },
        "responses": {
          "200": { "$ref": "#/components/responses/SemanticSearchOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" },
          "502": { "$ref": "#/components/responses/Problem502" }
        }
      }
    },
    "/api/v1/ai-search": {
      "post": {
        "operationId": "aiSearchV1",
        "tags": ["Maggid"],
        "summary": "Maggid RAG answer grounded in the myth anthology",
        "description": "Semantic retrieval over myths plus a streamed Maggid-voice answer.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/AiSearch" },
        "responses": {
          "200": { "$ref": "#/components/responses/MaggidStreamOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" },
          "502": { "$ref": "#/components/responses/Problem502" }
        }
      }
    },
    "/api/ai-search": {
      "post": {
        "operationId": "aiSearch",
        "tags": ["Maggid"],
        "summary": "Maggid RAG answer (alias of /api/v1/ai-search)",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/AiSearch" },
        "responses": {
          "200": { "$ref": "#/components/responses/MaggidStreamOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" },
          "502": { "$ref": "#/components/responses/Problem502" }
        }
      }
    },
    "/api/v1/chat": {
      "post": {
        "operationId": "maggidChatV1",
        "tags": ["Maggid"],
        "summary": "On-page Maggid chat for a myth context",
        "description": "Same Maggid model as ai-search, scoped with current myth metadata when provided by the chat widget.",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/Chat" },
        "responses": {
          "200": { "$ref": "#/components/responses/MaggidStreamOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" }
        }
      }
    },
    "/api/chat": {
      "post": {
        "operationId": "maggidChat",
        "tags": ["Maggid"],
        "summary": "On-page Maggid chat (alias of /api/v1/chat)",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "requestBody": { "$ref": "#/components/requestBodies/Chat" },
        "responses": {
          "200": { "$ref": "#/components/responses/MaggidStreamOk" },
          "400": { "$ref": "#/components/responses/Problem400" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" }
        }
      }
    },
    "/api/v1/status": {
      "get": {
        "operationId": "apiStatusV1",
        "tags": ["Discovery"],
        "summary": "API liveness",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "responses": {
          "200": { "$ref": "#/components/responses/StatusOk" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" }
        }
      }
    },
    "/api/status": {
      "get": {
        "operationId": "apiStatus",
        "tags": ["Discovery"],
        "summary": "API liveness (alias of /api/v1/status)",
        "parameters": [{ "$ref": "#/components/parameters/ApiVersion" }],
        "responses": {
          "200": { "$ref": "#/components/responses/StatusOk" },
          "405": { "$ref": "#/components/responses/Problem405" },
          "429": { "$ref": "#/components/responses/Problem429" }
        }
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "operationId": "apiCatalog",
        "tags": ["Discovery"],
        "summary": "RFC 9727 API catalog",
        "responses": {
          "200": {
            "description": "Linkset catalog",
            "content": {
              "application/linkset+json": {
                "schema": {
                  "type": "object",
                  "required": ["linkset"],
                  "properties": {
                    "linkset": {
                      "type": "array",
                      "items": { "type": "object" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ApiVersion": {
        "name": "API-Version",
        "in": "header",
        "required": false,
        "description": "Major API version. Responses always send `API-Version: 1`. Omit on requests; v1 is the default.",
        "schema": {
          "type": "string",
          "enum": ["1"],
          "default": "1"
        }
      }
    },
    "requestBodies": {
      "SemanticSearch": {
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/SemanticSearchRequest" },
            "example": {
              "query": "Lilith speaks the Name and flees Eden",
              "topK": 8
            }
          }
        }
      },
      "AiSearch": {
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/AiSearchRequest" },
            "example": { "query": "Who is Samael in Jewish tradition?" }
          }
        }
      },
      "Chat": {
        "required": true,
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ChatRequest" },
            "example": {
              "query": "Why did Moses hesitate?",
              "slug": "why-noah-hesitated-to-leave-the-ark",
              "title": "Why Noah Hesitated to Leave the Ark"
            }
          }
        }
      }
    },
    "responses": {
      "SemanticSearchOk": {
        "description": "Ranked results",
        "headers": {
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "X-Request-Id": { "$ref": "#/components/headers/RequestId" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/SemanticSearchResponse" }
          }
        }
      },
      "MaggidStreamOk": {
        "description": "SSE stream of Maggid tokens and source chips",
        "headers": {
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "X-Request-Id": { "$ref": "#/components/headers/RequestId" }
        },
        "content": {
          "text/event-stream": {
            "schema": { "$ref": "#/components/schemas/MaggidSseEvent" }
          },
          "application/json": {
            "schema": { "$ref": "#/components/schemas/MaggidSseEvent" }
          }
        }
      },
      "StatusOk": {
        "description": "Liveness payload",
        "headers": {
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "X-Request-Id": { "$ref": "#/components/headers/RequestId" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/StatusResponse" }
          }
        }
      },
      "Problem400": {
        "description": "Invalid JSON, missing query, or oversized content",
        "headers": {
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "X-Request-Id": { "$ref": "#/components/headers/RequestId" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "Problem405": {
        "description": "Wrong HTTP method. POST-only operations return 405 on GET with Allow: POST, OPTIONS.",
        "headers": {
          "Allow": {
            "description": "Methods the operation accepts",
            "schema": { "type": "string", "example": "POST, OPTIONS" }
          },
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "Problem429": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the window resets",
            "schema": { "type": "integer" }
          },
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      },
      "Problem502": {
        "description": "Embedding or upstream model failure. Retry with backoff; no side effects.",
        "headers": {
          "API-Version": { "$ref": "#/components/headers/ApiVersion" },
          "RateLimit": { "$ref": "#/components/headers/RateLimit" },
          "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "X-Request-Id": { "$ref": "#/components/headers/RequestId" }
        },
        "content": {
          "application/problem+json": {
            "schema": { "$ref": "#/components/schemas/Problem" }
          }
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "Major API version",
        "schema": { "type": "string", "example": "1" }
      },
      "RateLimit": {
        "description": "IETF RateLimit structured field",
        "schema": { "type": "string", "example": "\"default\";r=59;t=60" }
      },
      "RateLimitPolicy": {
        "description": "IETF RateLimit-Policy structured field",
        "schema": { "type": "string", "example": "\"default\";q=60;w=60" }
      },
      "RequestId": {
        "description": "Correlation ID for this response",
        "schema": { "type": "string", "format": "uuid" }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "required": ["type", "title", "status", "detail", "code"],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Stable URL for this error class, anchored on /docs"
          },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" },
          "code": {
            "type": "string",
            "enum": [
              "invalid_json",
              "invalid_query",
              "content_too_long",
              "method_not_allowed",
              "rate_limit",
              "embedding_failed",
              "not_found",
              "admin_retired"
            ]
          },
          "instance": { "type": "string" },
          "error": {
            "type": "string",
            "description": "Legacy alias of detail for existing browser clients"
          }
        }
      },
      "SemanticSearchRequest": {
        "type": "object",
        "required": ["query"],
        "properties": {
          "query": {
            "type": "string",
            "maxLength": 500,
            "description": "Search query (max 500 characters)"
          },
          "category": {
            "type": "string",
            "description": "Optional category filter"
          },
          "topK": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20,
            "default": 10
          }
        }
      },
      "SemanticSearchResponse": {
        "type": "object",
        "required": ["results"],
        "properties": {
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SemanticHit" }
          }
        }
      },
      "SemanticHit": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "enum": ["myth", "text", "source", "theme"] },
          "title": { "type": "string" },
          "slug": { "type": "string" },
          "url": { "type": "string" },
          "canonicalUrl": { "type": "string" },
          "snippet": { "type": "string" },
          "score": { "type": "number" },
          "source": { "type": "string" },
          "citation": { "type": "string" }
        }
      },
      "AiSearchRequest": {
        "type": "object",
        "required": ["query"],
        "properties": {
          "query": { "type": "string", "maxLength": 500 },
          "category": { "type": "string" },
          "history": {
            "type": "array",
            "description": "Optional prior turns for follow-ups",
            "items": { "$ref": "#/components/schemas/ChatTurn" }
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "required": ["query"],
        "properties": {
          "query": { "type": "string", "maxLength": 500 },
          "history": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ChatTurn" }
          },
          "slug": { "type": "string", "description": "Current myth slug" },
          "title": { "type": "string" },
          "content": {
            "type": "string",
            "maxLength": 12000,
            "description": "Optional current-myth body for pinning"
          }
        }
      },
      "ChatTurn": {
        "type": "object",
        "properties": {
          "role": { "type": "string", "enum": ["user", "assistant"] },
          "content": { "type": "string" }
        }
      },
      "MaggidSseEvent": {
        "type": "object",
        "description": "One SSE `data:` payload. Clients should parse each event independently.",
        "properties": {
          "sources": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SemanticHit" }
          },
          "token": { "type": "string" },
          "error": { "type": "string" }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": ["ok", "service", "auth"],
        "properties": {
          "ok": { "type": "boolean" },
          "service": { "type": "string" },
          "version": { "type": "string" },
          "apis": { "type": "array", "items": { "type": "string" } },
          "aliases": { "type": "array", "items": { "type": "string" } },
          "auth": { "type": "string" },
          "docs": { "type": "string", "format": "uri" },
          "openapi": { "type": "string", "format": "uri" },
          "catalog": { "type": "string", "format": "uri" },
          "errors": { "type": "string" },
          "rateLimit": { "type": "string" }
        }
      }
    }
  }
}
