{
  "name": "proto-retro-cf",
  "description": "Free, no-signup team retro board. Create a board, add cards to three fixed columns, reveal together. No accounts, no subscriptions, no third-party realtime vendor.",
  "base_url": "https://retro.eswat2.app",
  "repository": "https://github.com/eswat2/proto-retro-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 UUID (participantId) returned by create or join, 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/boards/:boardId/stream is Server-Sent Events (data: lines of JSON, ': ping' comments every 25s, a final 'event: closed' frame with {code, reason}); GET /api/boards/:boardId/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 board state on connect and after every mutation below — the 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+board and source IP+board — and responds 429 with a Retry-After header (seconds) if either is exceeded. Limits are generous for normal use (e.g. 30 cards / 10s per participant) but will reject a tight retry/poll loop.",
    "card_visibility": "Card visibility (hidden until revealed) is a client-side render rule only — the full board state, including every card's real text, is pushed to every connected client at all times."
  },
  "docs": {
    "readme": "https://github.com/eswat2/proto-retro-cf/blob/main/README.md",
    "architecture": "https://github.com/eswat2/proto-retro-cf/blob/main/docs/ARCHITECTURE.md",
    "architecture_page": "https://retro.eswat2.app/architecture",
    "features": "https://github.com/eswat2/proto-retro-cf/blob/main/docs/FEATURES.md",
    "llms_txt": "https://retro.eswat2.app/llms.txt"
  },
  "defaults": {
    "columns": [
      { "id": "went-well", "title": "Went well" },
      { "id": "to-improve", "title": "To improve" },
      { "id": "action-items", "title": "Action items" }
    ],
    "board_lifetime": "closes at the first of: 1h without changes and nobody connected, 2h without changes, 4h after creation. Board state carries closesAt/closeReason.",
    "board_id_format": "adjective-noun-<8 random base32 chars> (e.g. brave-otter-k7m2qx9p)",
    "card_text_max_length": 280
  },
  "api": {
    "endpoints": [
      {
        "method": "POST",
        "path": "/api/boards",
        "headers": { "x-create-key": "string (creator key; 401 without it)" },
        "body": { "name": "string" },
        "returns": { "boardId": "string", "participantId": "string", "ownerToken": "string" },
        "description": "Create a board (201). Requires the creator key; every other endpoint is open to anyone with the board id. Keep ownerToken — it's the only way to close the board early."
      },
      {
        "method": "GET",
        "path": "/api/boards/:boardId",
        "returns": { "board": "object" },
        "description": "Fetch current board state."
      },
      {
        "method": "POST",
        "path": "/api/boards/:boardId/join",
        "body": { "name": "string" },
        "returns": { "participantId": "string" },
        "description": "Join an existing board."
      },
      {
        "method": "POST",
        "path": "/api/boards/:boardId/cards",
        "body": { "participantId": "string", "columnId": "went-well | to-improve | action-items", "text": "string" },
        "returns": { "cardId": "string" },
        "description": "Add a card to a column (201)."
      },
      {
        "method": "DELETE",
        "path": "/api/boards/:boardId/cards/:cardId",
        "body": { "participantId": "string" },
        "returns": { "ok": true },
        "description": "Delete your own card. 404 if the card doesn't exist or you're not its author."
      },
      {
        "method": "POST",
        "path": "/api/boards/:boardId/reveal",
        "body": { "participantId": "string" },
        "returns": { "ok": true },
        "description": "Reveal all cards on the board."
      },
      {
        "method": "POST",
        "path": "/api/boards/:boardId/close",
        "body": { "ownerToken": "string" },
        "returns": { "ok": true },
        "description": "Close the board for everyone (creator only; 403 without the right ownerToken)."
      },
      {
        "method": "GET",
        "path": "/api/boards/:boardId/ws",
        "headers": { "Upgrade": "websocket" },
        "returns": "WebSocket (101)",
        "description": "WebSocket carrying this board's state as JSON text messages — pushes automatically on every mutation above."
      },
      {
        "method": "GET",
        "path": "/api/boards/:boardId/stream",
        "returns": "text/event-stream",
        "description": "Server-Sent Events stream of this board's state (same messages as /ws) — works with curl -N."
      }
    ]
  }
}
