{
  "openapi": "3.1.0",
  "info": {
    "title": "Whenna API",
    "version": "1.1.0",
    "description": "One link to find the time everyone's free. No auth except creatorKey for locking. All times are wall-clock in the plan's IANA timezone. AI agents: prefer the MCP endpoint at /mcp (JSON-RPC 2.0, tools whenna_create_plan / whenna_get_plan / whenna_mark_availability / whenna_best_times / whenna_lock_time)."
  },
  "servers": [{ "url": "https://whenna.com" }],
  "paths": {
    "/api/meetings": {
      "post": {
        "summary": "Create a plan",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": {
            "type": "object",
            "required": ["title", "days", "startHour", "endHour"],
            "properties": {
              "title": { "type": "string", "maxLength": 80 },
              "days": { "type": "array", "items": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "minItems": 1, "maxItems": 31 },
              "startHour": { "type": "integer", "minimum": 0, "maximum": 23 },
              "endHour": { "type": "integer", "minimum": 1, "maximum": 24 },
              "tz": { "type": "string", "description": "IANA timezone", "default": "UTC" },
              "slotMinutes": { "type": "integer", "enum": [30, 60], "default": 60 }
            }
          } } }
        },
        "responses": { "200": { "description": "Created", "content": { "application/json": { "schema": {
          "type": "object", "properties": { "id": { "type": "string" }, "creatorKey": { "type": "string", "description": "Keep private; authorizes /final" } }
        } } } } }
      }
    },
    "/api/meetings/{id}": {
      "get": {
        "summary": "Get plan state",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Plan with participants' slots, slotMinutes, and final lock if set" }, "404": { "description": "Not found" } }
      }
    },
    "/api/meetings/{id}/availability": {
      "put": {
        "summary": "Set a participant's availability (replaces previous answer)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["name", "slots"],
          "properties": {
            "name": { "type": "string", "maxLength": 40 },
            "slots": { "type": "array", "maxItems": 2000, "items": { "type": "string", "pattern": "^\\d{1,2}-\\d{1,3}$" }, "description": "\"<dayIndex>-<slotIndex>\"; slotIndex counts slotMinutes steps from startHour" }
          }
        } } } },
        "responses": { "200": { "description": "Saved; SSE update broadcast to everyone on the link" } }
      }
    },
    "/api/meetings/{id}/best": {
      "get": {
        "summary": "Ranked best time blocks",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "top", "in": "query", "schema": { "type": "integer", "default": 3, "maximum": 10 } }
        ],
        "responses": { "200": { "description": "Blocks: {day, start, end, available, of, free[]}" } }
      }
    },
    "/api/meetings/{id}/final": {
      "put": {
        "summary": "Lock (or clear) the final time — creator only",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object",
          "properties": {
            "creatorKey": { "type": "string" },
            "day": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
            "start": { "type": "string", "pattern": "^\\d{1,2}:\\d{2}$" },
            "mins": { "type": "integer", "minimum": 15, "maximum": 720, "default": 60 },
            "clear": { "type": "boolean", "description": "true to unlock instead" }
          }
        } } } },
        "responses": { "200": { "description": "Locked" }, "403": { "description": "Bad creatorKey" } }
      }
    },
    "/api/meetings/{id}/event.ics": {
      "get": {
        "summary": "Calendar event (.ics, TZID-correct)",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "day", "in": "query", "schema": { "type": "string" } },
          { "name": "start", "in": "query", "schema": { "type": "string", "example": "19:30" } },
          { "name": "mins", "in": "query", "schema": { "type": "integer", "default": 60 } }
        ],
        "responses": { "200": { "description": "text/calendar; defaults to the locked final time when set" } }
      }
    },
    "/api/meetings/{id}/stream": {
      "get": { "summary": "Live updates (SSE)", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "text/event-stream; `update` events carry full plan state" } } }
    },
    "/mcp": {
      "post": { "summary": "MCP endpoint for AI agents (JSON-RPC 2.0, stateless streamable-http)", "responses": { "200": { "description": "initialize / tools/list / tools/call" } } }
    }
  }
}
