# Create Binked replays with an AI agent

For a step-by-step walkthrough, example prompts and troubleshooting, start with the
[AI assistant user guide](https://binked.app/user-guide/ai-assistants). This page is the
technical reference for MCP clients and the HTTP API.

An AI agent can turn a hand you describe into a Binked replay, and start a video of it.
There are two ways to connect one:

- **Recommended: OAuth sign-in.** In Claude or a compatible MCP client, add `https://api.binked.app/mcp` as a
  connector and sign in to Binked when asked. No token is needed. See
  [MCP connection](#mcp-connection).
- **Everything else** (scripts, headless agents, clients without sign-in). Create a token
  in **Settings → Manage → AI agents → Personal access tokens · Advanced** and copy it into your client's secret
  configuration. Each token can validate and create hands and start video renders in your
  account, expires after 90 days, and can be revoked in Settings. Treat it as a password.

Neither way lets an agent manage your account, read your private lists, or modify saved
hands.

## HTTP API

Base URL: `https://api.binked.app/api/v1`

| Method | Path              | Purpose                                   |
| ------ | ----------------- | ----------------------------------------- |
| GET    | `/hands/schema`   | Public JSON Schema for hand input         |
| GET    | `/hands/examples` | Public complete examples, run-it included |
| POST   | `/hands/validate` | Validate without saving                   |
| POST   | `/hands`          | Validate, save and return the replay URL  |

Both POST endpoints require `Authorization: Bearer <your-agent-token>` and
`Content-Type: application/json`. Their body is the hand object itself.
Creation also requires `Idempotency-Key`: 8–128 letters, digits, underscores or
hyphens. Generate a new key for each intended hand. Reuse the same key and input
when retrying a failed or timed-out request.

The token is a Binked personal token, not a Firebase API key or Firebase ID token.

Example hand (`hand.json`):

```json
{
	"schemaVersion": 1,
	"variant": "NLH",
	"title": "Button takes it down",
	"players": [{ "stack": 100 }, { "stack": 100, "cards": ["As", "Kd"], "label": "Hero" }],
	"heroSeat": 2,
	"blinds": { "small": 1, "big": 2 },
	"streets": {
		"preflop": [
			{ "seat": 2, "type": "raise", "to": 6 },
			{ "seat": 1, "type": "fold" }
		]
	}
}
```

PowerShell (set `BINKED_TOKEN` through your secret configuration):

```powershell
$headers = @{ Authorization = "Bearer $env:BINKED_TOKEN" }
$hand = Get-Content -Raw hand.json
Invoke-RestMethod -Method Post -Uri https://api.binked.app/api/v1/hands/validate -Headers $headers -ContentType application/json -Body $hand
$headers['Idempotency-Key'] = 'my-hand-001'
Invoke-RestMethod -Method Post -Uri https://api.binked.app/api/v1/hands -Headers $headers -ContentType application/json -Body $hand
```

Successful creation returns HTTP 201:

```json
{ "id": "hand-id", "url": "https://binked.app/hands/hand-id", "replayed": false }
```

A retry returns HTTP 200, the same ID and URL, and `replayed: true`.
The hand appears in **My Hands**. Saved hands are immutable and publicly accessible
through the replay link. Validation does not publish anything.

Validation returns `{ "valid": true, "hand": { "config": {}, "title": "..." }, "summary": { "players": 2, "pot": 4, "winners": [2] } }`
with the complete generated config. On failure, it returns HTTP 422:

```json
{
	"valid": false,
	"errors": [
		{
			"path": "streets.preflop.1",
			"code": "CHECK_FACING_BET",
			"message": "Seat 1 cannot check: 4 remains to call."
		}
	]
}
```

Fix the indicated input and validate again. Never invent player decisions or cards
to satisfy validation; ask the person describing the hand for missing facts.

## Input conventions

- `schemaVersion` is `1`. Supported variants: `NLH`, `PLO`, `PLO-5`.
- Seats are 1-based and clockwise. Player array index 0 is seat 1. The last seat is
  the button. With 3+ players, seat 1 is SB and seat 2 is BB. Heads-up, seat 1 is BB
  and seat 2 is the button/SB. Optional `blinds.third` is posted by seat 3.
- Amounts are **whole chips in a single consistent unit**, up to 1,000,000,000.
  For fractional stakes, scale every amount consistently (e.g. $0.50/$1 becomes
  50/100 and a $100 stack becomes 10000). The replay displays those units. Currency
  conversion is not automatic.
- Starting stacks are before forced bets. Antes can be `{ "type": "everyone",
"amount": 1 }`, `big-blind`, or `button`. The last blind has priority over a
  big-blind ante when short stacked.
- Optional `straddles` is an ordered array of `{ "seat": 3, "amount": 4 }`.
  Each live straddle must be at least twice the previous forced bet and fully
  covered by the stack. Preflop action starts clockwise after the last straddler.
  Alternative house rules for button-straddle action order are not supported.
- Betting actions: `fold`, `check`, `call`, `bet`, `raise`. Calls have no amount;
  the server calculates it, including short all-in calls. Bets and raises use `to`,
  the player's total contribution on that street. Optional `comment` adds a note.
- `streets.preflop` is required. Later streets are objects: flop has `cards`
  (three cards) and `actions`; turn and river have `card` and `actions`. Include
  empty action arrays for all-in runouts. Stop after a fold win.
- Cards use rank/suit notation: `As`, `Td`, `7h`, `2c`. Known hole cards belong on
  the player. Omit `cards` or use `null` when unknown. Known hands must contain
  exactly 2, 4 or 5 cards for the variant. Cards cannot repeat anywhere in the hand.
- If multiple players remain, provide a full board (or `runouts`) and one `showdown`
  decision per player: `{ "seat": 2, "action": "show" }` or `muck`. Showing requires known
  hole cards. `keep-hidden` requires every other remaining player to have mucked
  and an eligible winner for every pot. Known hole cards do not implicitly mean
  the player showed them.
- **Run it twice or three times** with `runouts`, an array of two or three boards.
  It is allowed only when betting has ended all-in (at most one remaining player
  still has chips) with cards still to come. End `streets` at the street where the
  betting ended, and give every runout exactly the streets after it: all-in preflop
  needs `flop`, `turn` and `river` in each; all-in on the flop needs `turn` and
  `river`; all-in on the turn needs `river`. Every remaining player must `show`.
  Each pot is split evenly between the runs in small-blind units, with any extra
  going to the first run. A street dealt once after the all-in and then shared by
  the runs is not supported. See the `runItTwice` example.
- Binked derives forced bets, chip collections, uncalled returns, pot eligibility,
  winner evaluation and payouts. Do not supply internal replay events or owner IDs.
- Version 1 supports side pots and split pots. Rake, partial hands, and house-rule
  overrides are not supported. Unknown fields are rejected instead of silently
  discarded.

## MCP connection

Use the remote **Streamable HTTP** endpoint:

`https://api.binked.app/mcp`

There are two ways to authenticate:

- **Sign in (OAuth).** The client opens a Binked page where you sign in and approve the
  connection; no token is copied. Review and disconnect connected apps in
  **Settings → Manage → AI agents**. A connection ends after 90 days without use.
  Removing the connector in the client does not disconnect it on Binked.
  - _Claude (web, desktop, mobile):_ open **Customize → Connectors**, click **+**, then
    **Add custom connector**, enter the URL and click **Add**. Then connect it and sign
    in. On Team and Enterprise plans an owner adds it first under **Organization
    settings → Connectors**. Turn it on in a chat with **+ → Connectors**.
  - _Claude Code:_ `claude mcp add --transport http binked https://api.binked.app/mcp`,
    then run `/mcp` in a session, select `binked` and sign in.
- **Personal token.** Configure the header `Authorization: Bearer <your-agent-token>`
  in a client that supports custom headers. Syntax varies by client. Headless agents
  and the HTTP API use this method.
  - _Claude Code:_

    ```bash
    claude mcp add --transport http binked https://api.binked.app/mcp \
      --header "Authorization: Bearer <your-agent-token>"
    ```

A personal token can validate hands, create replays and start video renders. When you
sign in, the approval page lists what the app asked for, and the app gets only that:
`hands:create` (validate hands and create replays) and `videos:create` (start video
renders). Access obtained by signing in works on the MCP endpoint only, not on the
HTTP API. An app connected before video tools existed can create hands only; disconnect
and connect it again to allow videos.

For client developers: authorization follows the MCP specification. A `401` carries
`WWW-Authenticate` with a `resource_metadata` URL (RFC 9728). The authorization server
is `https://api.binked.app` (RFC 8414 metadata), offering the authorization code grant
with mandatory S256 PKCE, the `hands:create` and `videos:create` scopes (omitting
`scope` requests both; `tools/list` returns only the tools the granted scopes allow),
and rotating refresh tokens for public clients. Identify your client with a Client ID
Metadata Document: `client_id` is the HTTPS URL of that document. Dynamic Client
Registration and token revocation endpoints are not offered. Redirect URIs must be HTTPS,
or loopback HTTP (`localhost`, `127.0.0.1`, any port) for native apps.

The server exposes these tools with discoverable schemas:

- `validate_hand({ "hand": ... })` — no hand is saved.
- `create_hand({ "hand": ..., "idempotencyKey": "my-hand-001" })` — publishes a
  replay and returns its URL. Retry with the same arguments.
- `request_video({ "handId": "...", "orientation": "landscape" | "portrait" })` —
  starts rendering an MP4 of a saved hand. Use it only when the person asks for a
  video. Rendering takes a few minutes and continues after the call returns, so do
  not wait for it: give the person the returned `pageUrl`, where they sign in, watch
  progress and download the file. Asking again for the same hand and orientation
  returns the existing video and uses no quota.
- `get_video({ "handId": "...", "orientation": ... })` — reports `not_requested`,
  `queued`, `rendering`, `ready` or `failed`. It never starts a render.

Video results carry the page link, never the file or a download URL. Videos share the
account's limit of 20 new videos per UTC day with videos the person starts themselves,
and a hand can have at most 100 actions. A refused request is a tool result with
`isError: true`, `error` and `status` (for example 429 for the daily limit, 404 for an
unknown hand, 422 for a hand that is too long).

In clients that support MCP Apps, such as Claude, `validate_hand` and `create_hand` also
show a playable replay of the hand. A validated hand is labelled as an unsaved preview:
nothing exists on Binked until `create_hand` succeeds. Other clients see text only, and
the tool results are the same either way.

All tools declare an `outputSchema`. Tool results include JSON in
`structuredContent` and text. Problems the agent can act on are tool results with
`isError: true`: invalid hands carry `valid: false` and `errors`; refused creations
carry `error`, `code` and `status` (for example `IDEMPOTENCY_CONFLICT` with 409, or
`DAILY_LIMIT` with 429). Missing or bad tokens, untrusted origins, oversized bodies and
the per-minute request throttle are HTTP errors, and so are protocol faults, which carry
a JSON-RPC error body (for example 400 when the `Mcp-Method` or `Mcp-Name` header
disagrees with the request, or for an unsupported protocol version). This server is
stateless: it does not require session affinity, and it does not offer an SSE
subscription or resumable event stream. It speaks MCP `2026-07-28`, where every
request carries its own protocol version and no `initialize` handshake is needed,
and it still answers clients that open with `initialize` (`2025-11-25` and earlier).
Responses are always JSON.

## Errors and limits

| Status | Meaning                                                          |
| ------ | ---------------------------------------------------------------- |
| 400    | Invalid JSON or missing/invalid idempotency key                  |
| 401    | Missing, invalid, revoked, or expired token; unavailable account |
| 403    | Untrusted browser origin on MCP                                  |
| 409    | Idempotency key reused with different normalized hand content    |
| 413    | Request body exceeds the server's 100 KB limit                   |
| 422    | Hand input or poker sequence is invalid/incomplete               |
| 429    | Rate or daily limit reached; respect `Retry-After`               |
| 500    | Server error; retry creation with the same key                   |

Limits: 10 tokens and 20 connected apps per account, 60 authenticated requests per
minute per token or connection, 100 new hands and 20 new videos per account per UTC day,
200 decisions per street, 2–10 players (2–8 for PLO-5). Every MCP request counts toward
the request limit, including listing tools and loading the replay view, and so do
validation and retries; successful
retries do not consume another daily creation allowance. JSON-RPC batch (array) bodies
are rejected with 400: send one message per request. Idempotency records are
retained until account deletion. Revoking a token does not delete its replays.
