{
  "name": "proto-poker-cf",
  "description": "Free, no-signup planning poker. Create a room, vote on a Fibonacci deck, reveal together, start a new round. No accounts, no subscriptions, no third-party realtime vendor.",
  "base_url": "https://poker.eswat2.app",
  "repository": "https://github.com/eswat2/proto-poker-cf",
  "llm_instructions": {
    "note": "This is a client-rendered single-page app — fetch this manifest or the docs below rather than scraping rendered page HTML.",
    "auth": "None. Each participant is a client-generated UUID (participantId), sent in the request body, cached in localStorage. No accounts, no server-side sessions.",
    "realtime": "Two equivalent push channels, neither a normal request/response route: GET /api/rooms/:roomId/stream is Server-Sent Events (data: lines of JSON, ': ping' comments every 25s, a final 'event: closed' frame with {code, reason}); GET /api/rooms/:roomId/ws is a WebSocket (send Upgrade: websocket; send 'ping' to get 'pong'; a final {\"event\":\"closed\",code,reason} message precedes the close). Both send the full room state on connect and after every mutation below — the POST responses themselves only confirm the write, not the resulting state. Close codes: 4404 idle, 4408 lifetime, 4410 closed by host.",
    "rate_limits": "Every mutation route is rate-limited on two keys — participantId+room and source IP+room — and responds 429 with a Retry-After header (seconds) if either is exceeded. Limits are generous for normal use (e.g. 30 votes / 10s per participant) but will reject a tight retry/poll loop."
  },
  "docs": {
    "readme": "https://github.com/eswat2/proto-poker-cf/blob/main/README.md",
    "architecture": "https://github.com/eswat2/proto-poker-cf/blob/main/docs/ARCHITECTURE.md",
    "architecture_page": "https://poker.eswat2.app/architecture",
    "features": "https://github.com/eswat2/proto-poker-cf/blob/main/docs/FEATURES.md",
    "llms_txt": "https://poker.eswat2.app/llms.txt"
  },
  "defaults": {
    "deck": [1, 2, 3, 5, 8, 13, 21, "♠", "?"],
    "room_lifetime": "closes at the first of: 1h without changes and nobody connected, 2h without changes, 4h after creation. Room state carries closesAt/closeReason.",
    "room_id_format": "adjective-noun-<8 random base32 chars> (e.g. brave-otter-k7m2qx9p)"
  },
  "api": {
    "endpoints": [
      {
        "method": "POST",
        "path": "/api/rooms",
        "headers": { "x-create-key": "string (creator key; 401 without it)" },
        "body": { "name": "string" },
        "returns": { "roomId": "string", "participantId": "string", "ownerToken": "string (keep it: the only way to close the room early)" },
        "description": "Create a room. Requires the creator key; every other endpoint is open to anyone with the room id, except close."
      },
      {
        "method": "GET",
        "path": "/api/rooms/:roomId",
        "returns": { "room": "object" },
        "description": "Fetch current room state (initial load / fallback)."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/join",
        "body": { "name": "string" },
        "returns": { "participantId": "string" },
        "description": "Join an existing room."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/vote",
        "body": { "participantId": "string", "value": "number | '♠' | '?'" },
        "returns": { "ok": true },
        "description": "Cast or change a vote."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/reveal",
        "body": { "participantId": "string" },
        "returns": { "ok": true },
        "description": "Reveal all votes for the current round."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/new-round",
        "body": { "participantId": "string", "topic": "string (optional)" },
        "returns": { "ok": true },
        "description": "Clear votes, hide again, optionally set a new topic."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/topic",
        "body": { "participantId": "string", "topic": "string" },
        "returns": { "ok": true },
        "description": "Update the topic string."
      },
      {
        "method": "POST",
        "path": "/api/rooms/:roomId/close",
        "body": { "ownerToken": "string" },
        "returns": { "ok": true },
        "description": "Close the room for everyone (creator only; 403 without the right ownerToken)."
      },
      {
        "method": "GET",
        "path": "/api/rooms/:roomId/ws",
        "headers": { "Upgrade": "websocket" },
        "returns": "WebSocket (101)",
        "description": "WebSocket carrying this room's state as JSON text messages — pushes automatically on every mutation above."
      },
      {
        "method": "GET",
        "path": "/api/rooms/:roomId/stream",
        "returns": "text/event-stream",
        "description": "Server-Sent Events stream of this room's state (same messages as /ws) — works with curl -N."
      }
    ]
  }
}
