# ChipClock — Full Agent Reference > Full example-driven reference for agent integrations. Everything in `/llms.txt` plus complete request/response pairs, all error codes, idempotency details, and tested end-to-end workflows. Machine-readable schema lives at `/openapi.json`. Base URL: `https://chipclock.com` OpenAPI: `https://chipclock.com/openapi.json` Short reference: `https://chipclock.com/llms.txt` --- ## 1. Quickstart — zero to live tournament One request bootstraps an org, a ghost user, a demo tournament, a clock, and an admin API key. ``` POST /api/quickstart content-type: application/json { "org_name": "Demo League", "session_name": "Friday Night", "email": "agent-caller@example.com" } ``` All three body fields are optional. Omit everything to accept defaults. ### Successful response (201) ``` { "org_slug": "demo-league-9a2f6b1e", "api_key": "ck_live_…43chars…", "api_key_prefix": "AbCdEfGh", "session_id": "00000000-0000-0000-0000-000000000000", "session_url": "https://chipclock.com/live/…", "clock_url": "https://chipclock.com/clock/…", "openapi_url": "https://chipclock.com/openapi.json", "expires_at": "2026-04-24T12:00:00.000Z", "hints": { "start_clock": "POST /api/orgs/{org_slug}/clock with body {\"sessionId\":\"{session_id}\",\"action\":\"start\"}", "add_player": "POST /api/orgs/{org_slug}/players with body {\"nickname\":\"Alice\"}", "check_in": "POST /api/orgs/{org_slug}/sessions/{session_id}/check-in with body {\"rosterPlayerId\":\"\"}", "retry_safety": "This endpoint is NOT idempotent — a retry after network timeout creates a second trial org. Only retry if you saved no response." }, "next_actions": [ { "rel": "start_clock", "method": "POST", "href": "/api/orgs/{org_slug}/clock" }, { "rel": "add_player", "method": "POST", "href": "/api/orgs/{org_slug}/players" }, { "rel": "docs", "href": "https://chipclock.com/llms.txt" } ], "_warning": "Store `api_key` now — it will not be shown again. Trial org auto-deletes at `expires_at` unless a Stripe card is added." } ``` ### Rules - **Store `api_key` on first response.** Only the hash is persisted server-side. There is no recovery. - **Trial orgs expire after 7 days.** A daily cron at 03:00 UTC deletes expired trial orgs and their ghost owners, with a 1-day grace window. - **API keys expire 8 days after creation.** The extra day means that during the grace window you see clean 404s (org gone) rather than 401s (key expired). - **Not idempotent.** Each call creates a new trial. If you retry on a network timeout and the first call actually succeeded, you will have a second orphan trial. The cron will clean both up, but prefer saving the first response before retrying. - **Rate limit 5/hr/IP** (advisory). On serverless the window is per-instance, so a determined attacker can multiply it. This is documented as acceptable for v1. Legitimate agents with >5 trials/hour are rare. ### Error responses - `400 invalid_input` — body failed Zod validation (e.g. bad email, org_name > 100 chars). - `429 rate_limited` — hourly cap hit. `Retry-After` header present. - `503 service_unavailable` — slug collision retry loop failed 5 times. Retry immediately or supply a different `org_name`. --- ## 2. Authentication ``` Authorization: Bearer ck_live_<43-char-base64url> ``` - API keys are **org-scoped**. A key issued for org A cannot touch org B's URL space; requests to `/api/orgs/other-slug/...` return 404. - API keys carry a **role** (`owner`, `admin`, `manager`, `player`). A key cannot grant a role higher than its creator. - Keys from `POST /api/quickstart` are always `admin` role. - **Key management cannot go through a key.** `POST /api/orgs/{slug}/api-keys` rejects callers authenticated via Bearer (to prevent key-rotation loops). Use the web UI to create or rotate post-quickstart. - Revoking a key is a soft-delete — `revoked_at` is set and the key stops working immediately on the next request. The public endpoints listed below accept no auth; they use a session UUID as a read-only capability. Private sessions return 404 to non-authenticated callers. --- ## 3. Error envelope Every non-2xx body is: ``` { "error": "human-readable-message", "code": "machine_code", "message": "human-readable-message", "hint": "what to do next", "doc_url": "https://chipclock.com/llms.txt#anchor", "next_actions": [{ "rel": "login", "href": "/login", "method": "GET" }], "retry_after_seconds": 60 } ``` Agents should branch on `code`. The legacy `error` duplicates `message` and is preserved for the web UI. ### Codes you will see | HTTP | code | Meaning | |---:|---|---| | 400 | `invalid_input` | Zod validation failed. `hint` names the field and rule. | | 400 | `missing_field` | Required field absent from body/query/path. | | 401 | `unauthorized` | No auth. Supply Bearer or log in. | | 401 | `invalid_pin` | Player PIN did not match. | | 403 | `forbidden` | Role insufficient, or trying to manage keys via Bearer. | | 404 | `not_found` | Resource absent, or org/session/key outside your scope. | | 409 | `conflict` | Unique constraint, idempotency-key mismatch, or version conflict. | | 429 | `rate_limited` | See `retry_after_seconds` / `Retry-After` header. | | 500 | `internal_error` | Retry with `Idempotency-Key`. | | 503 | `service_unavailable` | Dependency degraded; retry with backoff. | --- ## 4. Idempotency ``` POST /api/orgs/{slug}/clock Idempotency-Key: my-unique-retry-id-123 content-type: application/json { "sessionId": "…", "action": "start" } ``` - Mutating endpoints (POST, PATCH, DELETE) accept `Idempotency-Key`. - Replaying the **same** key with the **same** body within 24h returns the cached response. - Replaying the **same** key with a **different** body returns `409 conflict` (`code: idempotency_key_conflict`). - Responses above 64KB are not cached — a retry re-executes. - `POST /api/quickstart` is **not** wrapped in idempotency. See §1. --- ## 5. Common workflows ### 5a. "Run a tournament from scratch" ``` # 1. Bootstrap an org POST /api/quickstart {} → { api_key, org_slug, session_id } # 2. Add three players POST /api/orgs/{slug}/players { "nickname": "Alice" } → { id: "p1" } POST /api/orgs/{slug}/players { "nickname": "Bob" } → { id: "p2" } POST /api/orgs/{slug}/players { "nickname": "Carol" } → { id: "p3" } # 3. Check everyone in POST /api/orgs/{slug}/sessions/{sid}/check-in { "rosterPlayerId": "p1" } POST /api/orgs/{slug}/sessions/{sid}/check-in { "rosterPlayerId": "p2" } POST /api/orgs/{slug}/sessions/{sid}/check-in { "rosterPlayerId": "p3" } # 4. Start the clock POST /api/orgs/{slug}/clock { "sessionId": "{sid}", "action": "start" } # 5. Knock someone out later POST /api/orgs/{slug}/eliminations { "sessionId": "{sid}", "rosterPlayerId": "p3" } # 6. Snapshot GET /api/live-tournament?sessionId={sid} → { clock, blinds, remaining, recentEliminations, prizePool, tables } ``` ### 5b. "Who's still in?" ``` GET /api/live-tournament?sessionId={sid} → look at `remaining` (int) and `recentEliminations` (array). ``` This endpoint is public (no auth). The session UUID is the capability. ### 5c. "Recommend payouts" ``` POST /api/orgs/{slug}/recommend { "prompt": "payouts for 27 players, $2,700 prize pool" } ``` Rate limited 5/min. Returns a Claude-generated suggestion. ### 5d. "Pause because the pizza arrived" ``` POST /api/orgs/{slug}/clock { "sessionId": "{sid}", "action": "pause", "pauseMessage": "Pizza break" } ``` `resume` later flips it back. Level advance / regress work the same way. ### 5e. "Player wants to rebuy" Self-service — no Bearer needed, PIN-gated: ``` POST /api/public-player-requests { "sessionId": "{sid}", "rosterPlayerId": "p1", "pin": "1234", "requestType": "rebuy" } ``` Then the host resolves it: ``` POST /api/orgs/{slug}/sessions/{sid}/player-requests { "requestId": "...", "status": "accepted" } ``` --- ## 6. Data model (essential shapes) ```ts interface GameSession { id: string; // uuid name: string; status: "active" | "paused" | "completed" | "cancelled"; buy_in_amount: number | null; starting_stack: number; level_duration_minutes: number; blinds_config: BlindLevel[]; payout_structure: PayoutStructure | null; max_players_per_table: number; is_private: boolean; minigames: string[] | null; seat_on_pay: boolean; // … } interface BlindLevel { level: number; small_blind: number; big_blind: number; ante?: number; duration_minutes?: number; is_break?: boolean; } interface ClockState { current_level: number; time_remaining_seconds: number; is_running: boolean; is_paused: boolean; pause_message: string | null; last_updated_at: string; // ISO-8601 effective_remaining?: number; // server-corrected for drift on reads } interface RosterPlayer { id: string; nickname: string; pin: string | null; // 4 digits, plaintext status: "active" | "archived"; // … } ``` Full schemas: `/openapi.json`. --- ## 7. Rate limits summary | Key | Limit | |---|---| | `/api/quickstart` | 5 / hour / IP | | `/api/orgs/*/clock` POST | 30 / min / IP | | public reads (live-tournament, feed, standings) | 60 / min / IP | | `/api/public-player-requests` POST | 10 / min / IP | | `/api/public-player-pin` POST (set) | 2 / min / IP | | `/api/public-player-pin` POST (verify) | 5 per 15 min per player | | `/api/auth/*` | 10 / min / IP | | `/api/orgs/*/recommend` | 5 / min | 429 responses carry `Retry-After`. Back off exponentially from there. --- ## 8. Operational notes - **Multi-tenancy is enforced in-app.** Every query includes `org_id` in its WHERE clause. There is no RLS policy to fall back on — the key alone is the capability. - **Eventual consistency on the clock.** Reads via `/api/clock?sessionId=…` include `effective_remaining`, server-corrected for drift against `last_updated_at`. Clients should use that field, not the raw `time_remaining_seconds`, when rendering countdowns. - **Real-time via Supabase Broadcast** for web clients; agents should poll `/api/live-tournament` at 2–5s intervals. - **Private sessions** (`is_private: true`) are invisible to the public endpoints (they 404). Use the authenticated `/api/orgs/{slug}/sessions/{id}` instead. --- ## 9. Changelog & roadmap - `/changelog` — public release notes. - Deferred to v1.1: claim flow (trial → real org), tournament simulator endpoint, programmatic blog content. --- ## 10. Support - File issues: `support@chipclock.com` - Human companion UI: https://chipclock.com