{
  "openapi": "3.1.0",
  "info": {
    "title": "Kroonen AI API",
    "description": "Public API for the Kroonen AI lab site. The public surface is a contact submission endpoint (Amazon SES + Cloudflare Turnstile), a read-only Genesis 1B training status feed (Weights & Biases proxy), and a deterministic docs search over every page of the site. All are served from the Cloudflare Pages Functions at the /api/ prefix. No authentication is required for the public surface; authenticated programmatic access is described by the OAuth metadata at /.well-known/oauth-authorization-server.",
    "version": "1.0.0",
    "contact": {
      "name": "Kroonen AI",
      "email": "hello@kroonen.ai",
      "url": "https://www.kroonen.ai/"
    }
  },
  "servers": [
    {
      "url": "https://www.kroonen.ai",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "contact", "description": "Contact form submission (human, Turnstile-gated)" },
    { "name": "genesis", "description": "Genesis 1B training telemetry (read-only)" },
    { "name": "docs", "description": "Deterministic search over the site's docs and pages" }
  ],
  "paths": {
    "/api/contact": {
      "post": {
        "tags": ["contact"],
        "operationId": "submitContact",
        "summary": "Submit a contact form message",
        "description": "Validates the payload, verifies the Cloudflare Turnstile token, and delivers the message via Amazon SES. Protected against automated submission by a mandatory Turnstile challenge (action `contact`).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ContactRequest" },
              "examples": {
                "example": {
                  "value": {
                    "name": "Robin Kroonen",
                    "email": "robin@kroonen.ai",
                    "service": "train-adapt-model",
                    "message": "Interested in model adaptation work.",
                    "turnstileToken": "<turnstile-token>"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message accepted by the email provider.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ContactSuccess" }
              }
            }
          },
          "400": { "description": "Validation error (missing/invalid field).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactError" } } } },
          "403": { "description": "Origin not allowed or Turnstile verification failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactError" } } } },
          "415": { "description": "Content-Type must be application/json.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactError" } } } },
          "503": { "description": "Required configuration or Turnstile verification service temporarily unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactError" } } } },
          "500": { "description": "Email provider request failed or an unexpected server error occurred.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactError" } } } }
        }
      },
      "options": {
        "tags": ["contact"],
        "operationId": "contactPreflight",
        "summary": "CORS preflight",
        "responses": { "204": { "description": "Preflight accepted." } }
      }
    },
    "/api/genesis-status": {
      "get": {
        "tags": ["genesis"],
        "operationId": "getGenesisStatus",
        "summary": "Genesis 1B training status",
        "description": "Returns the current training step, loss, run state, and an ETA for the Genesis 1B 1B-parameter run. Cached for 60 seconds.",
        "responses": {
          "200": {
            "description": "Current training status.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GenesisStatus" },
                "examples": {
                  "running": {
                    "value": { "step": 41233, "loss": 2.7311, "state": "running", "etaMs": 1787000000000 }
                  }
                }
              }
            }
          },
          "503": { "description": "Status unavailable (missing dependency).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenesisStatusError" } } } },
          "502": { "description": "Upstream telemetry error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenesisStatusError" } } } },
          "500": { "description": "Unexpected server error.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GenesisStatusError" } } } }
        }
      },
      "head": {
        "tags": ["genesis"],
        "operationId": "getGenesisStatusHead",
        "summary": "Genesis 1B training status (HEAD)",
        "responses": { "200": { "description": "Status headers only." } }
      }
    },
    "/api/search": {
      "get": {
        "tags": ["docs"],
        "operationId": "searchDocs",
        "summary": "Deterministic docs search",
        "description": "Keyword search over the title, headings, description, and body text of every public page. The index (/search-index.json) regenerates on each site build; the same query against the same build always returns the same ranked results. Cached for 300 seconds.",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string", "maxLength": 200 }, "description": "Search query." },
          { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 25, "default": 10 }, "description": "Maximum number of results." }
        ],
        "responses": {
          "200": {
            "description": "Ranked results.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" },
                "examples": {
                  "example": {
                    "value": { "query": "checkpoint deadlock", "count": 1, "results": [{ "url": "https://www.kroonen.ai/blog/genesis-checkpoint-failures/", "title": "Fixing FSDP Checkpoint Deadlocks on 2× RTX 4090 | Kroonen AI", "description": "Diagnosing and fixing FSDP checkpoint deadlocks.", "score": 142, "snippet": "…the checkpoint deadlock appeared after…" }] }
                  }
                }
              }
            }
          },
          "400": { "description": "Missing q parameter.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchError" } } } },
          "503": { "description": "Search index unavailable.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchError" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ContactRequest": {
        "type": "object",
        "required": ["name", "email", "service", "message", "turnstileToken"],
        "properties": {
          "name": { "type": "string", "maxLength": 100, "description": "Your name." },
          "email": { "type": "string", "format": "email", "maxLength": 254, "description": "Reply-to email address." },
          "service": {
            "type": "string",
            "description": "Category of interest.",
            "enum": [
              "build-product-system", "train-adapt-model", "evaluate-stress-test", "not-sure",
              "custom-ai-deployment", "model-pre-training", "model-fine-tuning", "dataset-preparation",
              "genesis", "ai-research", "application-development", "communications-development",
              "libre-webui", "libre-bot", "libre-phone", "libre-claw", "ai-agents-orchestration",
              "safety-evaluation", "other"
            ]
          },
          "message": { "type": "string", "maxLength": 5000, "description": "The message." },
          "turnstileToken": { "type": "string", "description": "Cloudflare Turnstile siteverify token (action `contact`)." }
        }
      },
      "ContactSuccess": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "message": { "type": "string", "example": "Message sent successfully!" }
        }
      },
      "ContactError": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string", "example": "Invalid email format" }
        }
      },
      "GenesisStatus": {
        "type": "object",
        "properties": {
          "step": { "type": ["integer", "null"], "description": "Current training step." },
          "loss": { "type": ["number", "null"], "description": "Most recent train loss." },
          "state": { "type": ["string", "null"], "description": "W&B run state." },
          "etaMs": { "type": ["integer", "null"], "description": "Estimated completion time (epoch ms), or null when not running." }
        }
      },
      "GenesisStatusError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "status unavailable" }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": ["query", "count", "results"],
        "properties": {
          "query": { "type": "string", "description": "The query as interpreted (trimmed, max 200 chars)." },
          "count": { "type": "integer", "description": "Number of results returned." },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["url", "title", "score"],
              "properties": {
                "url": { "type": "string", "format": "uri", "description": "Canonical page URL." },
                "title": { "type": "string" },
                "description": { "type": "string" },
                "score": { "type": "number", "description": "Relevance score; higher is better. Stable across identical builds." },
                "snippet": { "type": "string", "description": "Body excerpt around the first matched term." }
              }
            }
          }
        }
      },
      "SearchError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "missing query: use /api/search?q=YOUR_QUERY" }
        }
      }
    },
    "securitySchemes": {
      "kroonenOauth": {
        "type": "oauth2",
        "description": "OAuth 2.0 authorization code with PKCE. See /.well-known/oauth-authorization-server for endpoints.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.kroonen.ai/oauth/authorize",
            "tokenUrl": "https://www.kroonen.ai/oauth/token",
            "scopes": {
              "public:read": "Read public site and product data.",
              "contact:write": "Submit contact messages on the owner's behalf."
            }
          }
        }
      }
    },
    "securitySchemesNote": {
      "description": "The public surface above is currently unauthenticated; the scheme documents the intended authenticated programmatic flow."
    }
  },
  "security": []
}
