{
  "openapi": "3.1.0",
  "info": {
    "title": "simonstawski.com public API",
    "version": "1.0.0",
    "description": "The public HTTP surface of Simon Stawski's personal site: the \"Ask about Simon\" recruiter agent and the contact/lead endpoint. For programmatic access to Simon's track record, prefer the MCP server (https://simon-agent-mcp.reading-rate.workers.dev, Streamable HTTP, no auth) — it serves the same verified corpus as structured tools and needs no human-verification step.",
    "contact": { "email": "hi@simonstawski.com", "url": "https://simonstawski.com" }
  },
  "servers": [{ "url": "https://simonstawski.com" }],
  "paths": {
    "/api/ask": {
      "post": {
        "operationId": "askAboutSimon",
        "summary": "Ask the recruiter agent a question about Simon",
        "description": "Answers questions about Simon Stawski's verified track record, streaming the reply as Server-Sent Events. Human verification is required: the first request must carry a Cloudflare Turnstile token minted in a browser; the response sets a signed session cookie that authorizes follow-up questions for ~2 hours. Non-browser agents cannot complete Turnstile — use the MCP server instead, which requires no verification. Rate limit: 15 questions per IP per day; a 429 carries a Retry-After header.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["question"],
                "properties": {
                  "question": { "type": "string", "maxLength": 500, "description": "The question to ask about Simon." },
                  "history": {
                    "type": "array",
                    "description": "Optional prior turns for context (last 3 exchanges kept).",
                    "items": {
                      "type": "object",
                      "required": ["role", "content"],
                      "properties": {
                        "role": { "type": "string", "enum": ["user", "assistant"] },
                        "content": { "type": "string", "maxLength": 1200 }
                      }
                    }
                  },
                  "turnstileToken": { "type": "string", "description": "Cloudflare Turnstile token; required unless a valid session cookie is presented." }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answer, as a Server-Sent Events stream of {\"response\": \"...\"} chunks terminated by [DONE]; or, for short-circuited answers, a JSON body {ok: true, reply}.",
            "content": {
              "text/event-stream": { "schema": { "type": "string" } },
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "ok": { "type": "boolean" }, "reply": { "type": "string" } }
                }
              }
            }
          },
          "400": { "description": "Malformed request or empty question.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Human verification failed (no valid Turnstile token or session cookie).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": {
            "description": "Rate limited (per-IP daily cap or global daily budget). Retry-After gives seconds until the UTC-midnight reset.",
            "headers": { "Retry-After": { "description": "Seconds until the limit resets at UTC midnight.", "schema": { "type": "integer" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "502": { "description": "Upstream model error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/contact/api/lead": {
      "post": {
        "operationId": "submitLead",
        "summary": "Identify yourself to Simon (recruiter/contact lead)",
        "description": "Stores who is asking, what they asked for, and the conversation that led there; Simon reads it and replies by email. Nothing is emailed automatically and no documents are served back. Same human-verification rule as /api/ask: Turnstile token or the session cookie the ask endpoint sets.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name", "email"],
                "properties": {
                  "name": { "type": "string", "maxLength": 120 },
                  "email": { "type": "string", "format": "email", "maxLength": 254 },
                  "company": { "type": "string", "maxLength": 160 },
                  "roleNote": { "type": "string", "maxLength": 600, "description": "What role or opportunity this is about." },
                  "intent": { "type": "string", "enum": ["resume", "followup"] },
                  "transcript": { "type": "string", "maxLength": 20000, "description": "The agent conversation that led here, if any." },
                  "turnstileToken": { "type": "string", "description": "Cloudflare Turnstile token; required unless a valid session cookie is presented." }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Stored.", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } } },
          "400": { "description": "Missing or invalid fields.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "Human verification failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "description": "Storage unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["ok", "error"],
        "properties": {
          "ok": { "type": "boolean", "const": false },
          "error": { "type": "string", "description": "Machine-readable error code, e.g. turnstile_failed, ip_limit, busy_day, model_error." },
          "message": { "type": "string", "description": "Human-readable explanation with a resolution hint." }
        }
      }
    }
  }
}
