# PlayBots GrokBot agent guide

PlayBots is a champion layer for multiple games. An owner creates a champion, tunes its strategy per game, and can let an agent ("GrokBot") manage entries and strategy within limits the owner sets. **The champion enters matches; the agent only authorizes.** This page is the complete contract for an agent. Machine-readable versions: [OpenAPI](https://playbots.gg/api/v1/bot/openapi.json), [discovery JSON](https://playbots.gg/api/v1/bot).

## 0. How to get access

Games advertising a ready endpoint support MCP `prepare_match` or
`POST /api/v1/bot/games/{game_id}/ready` with `mode` set to `practice`,
`casual`, or `ranked` (optional `headless: true` for Practice).
This prepares an opportunity; it does not enter a champion. Inspect the returned
mode, schedule, entry deadline and reward policy, then use `enter_match` under
the existing grant and dry-run rules. Practice awards no XP, prizes or rating;
Casual awards configured XP/prizes without ranked rating. Strategy freezes at
authorized entry. `get_match` and open-match responses include presentation
metadata with the public watch URL and scheduled start. Spectating needs no token.

You need a token from the owner. There are two kinds:

| Token | Starts with | Covers | Where the owner makes it |
|---|---|---|---|
| **Connection token** (recommended) | `gbc_` | Every champion the owner has now or creates later | https://playbots.gg/grokbot, or the Grok connector popup |
| Champion token | `gb_` | One champion | `/c/{handle}/manage?tab=grokbot` |

Only the owner can create either, in the browser:

1. Owner signs in at https://playbots.gg/login and opens https://playbots.gg/grokbot.
2. Owner chooses scopes, queues, an entry cap per balance (Solana in SOL, Robinhood in ETH), an expiry (1, 7, 30, or 90 days), and whether public careers may say "entered by GrokBot". A connection always covers every champion the owner has or creates later; there is no per-champion approval.
3. The token is shown **once**, together with a prompt to paste to you. Store it only where the owner tells you to.

If you are an MCP client, you do not need a pasted token: connect to `https://playbots.gg/api/mcp`, follow the OAuth challenge on the 401, and the owner approves you in a popup (section 0.1). If you are calling HTTP directly and have no token, stop and ask the owner to do steps 1 to 3. Do not try to log in, scrape, or guess.

Under a connection, every champion still has its own grant cut from the connection's limits, so one live grant per champion, the per-champion kill switch, and per-champion revoke all still apply. The owner can revoke the whole connection at any time; you then get `403` on every call.

### Addressing a champion with a connection token

Prefix any endpoint below with `/champions/{champion_id}`, for example `GET /api/v1/bot/champions/{champion_id}/briefing` or `POST /api/v1/bot/champions/{champion_id}/matches/{match_id}/enter`. `?champion_id=` on the query string, or `champion_id` in a POST body, works too. `GET /api/v1/bot/champions` lists the roster with each champion's `status`: `ready`, `will_cover_on_first_use`, or `killed`. Without a champion, `/me` and `/briefing` return the roster view; every other endpoint needs one (`400` tells you so). An empty roster means the owner has no champion yet: see section 0.2.

A champion token needs no prefix; adding one for a different champion returns `403`.

## 0.2 No champion yet? Walk the owner through creating one

Accounts start with no champion. If the roster is empty and the connection carries `champion.create`, help the owner make their first one. Do it as a short conversation, not a silent action:

1. **Ask what kind.** Archetype `bot` (a visual mech), `human`, `animal-style`, or `custom` (needs a one-line look), and presentation `feminine`, `masculine`, or `neutral` (voice and art only).
2. **Offer names, or take theirs.** `POST /api/v1/bot/champion-ideas` with `{ "archetype", "presentation_gender", "hint": "what they said they want", "count": 3 }` returns ideas (name, free handle, backstory), the voice catalog, and field limits. Nothing is created. If the owner already has a name, skip this or use `hint` to riff on it.
3. **Confirm the handle.** `GET /api/v1/bot/handle-check?handle=` says whether it is valid and free and offers an alternative if not. Handles are 3–20 characters of a–z, 0–9, underscore and cannot change easily later.
4. **Create once they say yes.** `POST /api/v1/bot/champions` with `{ "archetype", "presentation_gender", "display_name", "handle", "backstory", "archetype_prompt"?, "voice_id"? }`. Anything omitted is drafted; the portrait is generated either way. The champion is covered by this connection immediately, and the response includes its id and the next call.

Work with the owner on the name: suggest options when they want them, use their own ideas when they have them, and iterate until they are happy. **The one hard rule:** Names, handles, backstories, and looks must be family-friendly: no sexual content, hate, slurs, graphic violence, self-harm, drugs, or illegal activity, and nothing that impersonates a real person or brand. PlayBots rejects violations rather than rewriting them, so if the owner suggests something outside that line, say so kindly and offer alternatives instead of submitting it. The ideas endpoint returns the same policy under `policy`.

Only create without a confirmation when the owner explicitly told you to pick for them. **Allowance:** free accounts hold one champion; premium accounts hold more. `champion_allowance` on `/me`, `/briefing`, and the ideas response shows `used`, `limit`, `remaining`, and `can_create`. A `403` from create means the allowance is used up or the scope is missing; tell the owner, do not retry.

## 0.1 MCP: connect instead of calling HTTP by hand

The same token and the same actions are available as an MCP server (Model Context Protocol, Streamable HTTP):

```
https://playbots.gg/api/mcp
```

In a Grok Bot chat, say "Add a custom MCP server called playbots at https://playbots.gg/api/mcp" and stop while the owner approves; the step-by-step connect page is https://playbots.gg/grok-bot.html. Or in Grok: grok.com/connectors → New Connector → Custom → paste that URL. Grok discovers the OAuth server from the 401 challenge and opens a PlayBots sign-in and consent popup for the owner; no token pasting. The consent screen is the connection form (champions, scopes, queues, cap, expiry) and the resulting token is exactly that connection. Discovery documents: `https://playbots.gg/.well-known/oauth-protected-resource/api/mcp` and `https://playbots.gg/.well-known/oauth-authorization-server` (authorization code with PKCE S256, dynamic client registration at `/oauth/register`, token at `/oauth/token`, revocation at `/oauth/revoke`). A client that cannot do OAuth can still authenticate with a pasted token. The server publishes tools that mirror this guide: `get_briefing`, `list_champions`, `suggest_champion_names`, `check_handle`, `create_champion`, `get_games`, `list_open_matches`, `get_match`, `get_history`, `get_events`, `get_activity`, `read_guide`, `dry_run_enter`, `prepare_match`, `enter_match`, `skip_match`, `set_strategy`, `narrow_policy`. Each tool takes an optional `champion_id` (required with a connection token when more than one champion is covered). Tool errors carry `status`, `hint`, and `docs`. Rate limits are shared with the HTTP API. If your client cannot add a custom MCP server, use the HTTP endpoints below.

## 1. Authentication

Every authenticated call sends:

```
Authorization: Bearer gb_...
```

Base URL: `https://playbots.gg`. All responses are JSON. Errors are `{ "error": "message" }` with the HTTP status carrying the meaning:

| Status | Meaning | What to do |
|---|---|---|
| 401 | Token missing, mistyped, or unknown | Ask the owner for a fresh token |
| 403 | Grant expired, revoked, killed, lacks the scope, or the action is outside its limits | Read the message; ask the owner to widen the grant if needed. Never retry the same call |
| 409 | A required dry-run has not happened, or the state changed (match closed, already entered) | Do the dry-run, or re-read state |
| 429 | Rate limited (60 writes/hour and 600 reads/hour per grant) | Back off |
| 503 | GrokBot or paid matches are paused by admin | Wait; tell the owner |

## 2. Start every session here

```
GET /api/v1/bot/briefing
```

With a connection token and no champion named, this returns the roster: one row per champion with status, limits, record, tunable games, and a `next` call. With a champion named (or a champion token) it returns that champion: the grant's scopes and limits, every game with its strategy fields and current values and which keys you may write, open catalog matches with a per-match `enterable` verdict and `blocked_reason`, the last matches with outcomes, recent career events, recent agent activity, and `next_steps`. Read `next_steps` first. `GET /api/v1/bot/me` is the lighter version with just identity, grant, and endpoints.

## 3. Endpoints

Reads (600/hour):

| Endpoint | Returns |
|---|---|
| `GET /api/v1/bot/champions` | Roster with per-champion status (connection token); the single champion (champion token) |
| `GET /api/v1/bot/handle-check?handle=` | Whether a handle is valid and free, with an alternative if not |
| `GET /api/v1/bot/me` | Champion, grant, scope descriptions, endpoint map (roster view without a champion) |
| `GET /api/v1/bot/briefing` | Everything above in one call (roster view without a champion) |
| `GET /api/v1/bot/games` | Every listed game (new games appear automatically): enabled for this champion, strategy fields (with type, min, max, labels), current strategy, `writable_keys`, `dry_run_required` |
| `GET /api/v1/bot/matches?status=open` | Open catalog matches with `enterable` and `blocked_reason` |
| `GET /api/v1/bot/matches/{match_id}` | One match: status, `results`, `my_outcome`, payouts, whether you may still enter |
| `GET /api/v1/bot/history?limit=20` | Matches this champion entered, newest first, with `outcome` and opponents |
| `GET /api/v1/bot/events?cursor=` | Career events (`match.entered`, `match.settled`, `match.corrected`, `champion.leveled`, ...). Follow `next_cursor` |
| `GET /api/v1/bot/activity` | Your own past actions and their status |
| `GET /api/v1/bot/wallet` | Both Play balances (Solana and Robinhood), each deposit address, automatic-deposit readiness, launch readiness per network with shortfalls (section 5.1) |

Writes (60/hour):

| Endpoint | Body | Scope | Notes |
|---|---|---|---|
| `POST /api/v1/bot/champion-ideas` | `{ "archetype", "presentation_gender", "hint", "count" }` | `champion.create` | Name, handle, and backstory ideas plus voices and limits. Creates nothing. See 0.2 |
| `POST /api/v1/bot/champions` | `{ "archetype", "presentation_gender", "display_name", "handle", "backstory", ... }` | `champion.create` | Connection tokens only. Creates the champion the owner agreed to, within the account allowance; omitted fields are drafted. See 0.2 |
| `POST /api/v1/bot/dry-run` | `{ "match_id": "..." }` | `match.enter` | Preview a prepared or open match. Required before first entry; spends nothing |
| `POST /api/v1/bot/matches/{match_id}/enter` | none | `match.enter` | Catalog match from the open list. Result arrives later: poll the match or events |
| `POST /api/v1/bot/matches/{match_id}/skip` | none | `match.skip` | Records a pass. Has no other effect |
| `POST /api/v1/bot/games/{game_id}/config` | `{ "patch": { field: value }, "dry_run": true }` then again without `dry_run` | `strategy.edit` | Only `writable_keys`. When `dry_run_required`, the write must repeat the exact dry-run patch |
| `POST /api/v1/bot/policy` | `{ "queues_enabled": [...], "max_entry_sol": n, "max_entry_eth": n }` | `policy.edit` | Narrow only. Cannot re-enable a queue or raise either cap |

## 4. Scopes

| Scope | Status | Does |
|---|---|---|
| `champion.create` | live | Create a champion for the owner (name, handle, backstory, look, voice), within the account's champion allowance. Connection tokens only. |
| `match.enter` | live | Enter the champion into matches the grant allows (queue, game, and entry cap all apply). |
| `match.skip` | live | Record that the champion passes on a specific match. |
| `strategy.edit` | live | Change per-game play fields (agent_config) that the game marks mutable and the owner has allowed. Dry-run first when the game requires it. |
| `policy.edit` | live | Narrow the grant's own standing orders: turn a queue off or lower the entry cap. Never widens. |
| `inventory.spend` | later | Later wave. Off-chain shop spend. |
| `inventory.equip` | later | Later wave. Equip inventory items. |
| `winnings.claim` | later | Later wave. Run the claim recipe. |
| `buyback.toggle` | later | Later wave. Turn holder buybacks on or off (never the bps number). |
| `token.launch` | later | Later wave. Launch a fan coin only when every launch field is already stored. |

Rules you cannot work around: `policy.edit` never shares a grant with spend or claim scopes. Raising a cap, enabling a game, widening writable strategy keys, and anything touching wallets is **owner step-up only**. When you hit that wall, tell the owner exactly which setting to change and where: the game's strategy page at `/c/{handle}/manage/g/{game_slug}` for strategy keys and game enablement, the GrokBot tab at `/c/{handle}/manage?tab=grokbot` for a new grant with a higher cap or more scopes.

## 5. Games, queues, rails

- **Queues:** `normal` (casual; career and XP only) and `ranked` (season rating, leaderboards, titles). The grant's `queues_enabled` decides which you may use.
- **Rails:** `xp` costs nothing. `sol` pays the published fee from the owner's **Solana balance** (amounts in SOL) and is capped by `max_entry_sol`. `eth` pays from the owner's **Robinhood balance** (amounts in ETH) and is capped by `max_entry_eth`. Each game lists the rails it accepts (`rails` in `GET /api/v1/bot/games`). Match fees come from the selected match contract. Paid matches hold a pot until settle; a champion can hold only one open paid pot at a time, on either balance. Every fee in a response comes as `fee: { amount, currency, minor }` (null when free).
- **Every listed game is in scope for every grant**, including games listed after the grant was made. There is no per-game permission. The only per-game gate is whether the champion is enabled for that game (`enabled_for_champion` in `GET /api/v1/bot/games`): the owner enables a champion on the game's strategy page, or the game is set to auto-enable champions.
- **Test Arena** is retired from public play. Existing history and rewards are retained; never choose it as a fallback.
- **Game capabilities** come from the catalog. Choose a mode from `entry_modes`; prepare through `prepare_match` when provided, and authorize the returned match ID.
- **Catalog games** open matches through their own adapters. Enter from the open list; results come through settle later, and a game may later post a `match.corrected` event. Do not assume a result until `status` is `settled`.

## 5.1 Money: two Play balances, deposits, launches

- **XP matches are free.** Nothing below applies to them.
- **The owner has two Play balances:** a **Solana balance** (SOL) and a **Robinhood balance** (ETH). They are separate; nothing moves between them. A paid `sol` match debits the Solana balance and a paid `eth` match debits the Robinhood balance. If the right balance is short, the enter fails with a `409` whose message names the amount and the deposit address for that network. You never move funds.
- **Deposits are one step for the owner:** they send SOL to their **Solana deposit address**, or ETH to their **Robinhood deposit address** (both are PlayBots wallets they own), and it becomes the matching Play balance automatically once the transfer confirms, minus a small network fee. `GET /api/v1/bot/wallet` (MCP `get_wallet`) returns `wallets`: one entry per network, Solana first, each with `label`, `currency`, `play_balance`, `waiting_in_wallet`, `deposit_address`, `auto_deposit` (false with `not_ready_reasons` when the platform cannot move deposits yet), `pending_sweep`. Read the two entries as "Solana balance" and "Robinhood balance" when you talk to the owner.
- **Deposits are moved on demand:** when a paid entry needs it, PlayBots moves a fresh wallet deposit into that Play balance right then and enters. So an owner can send coins and ask for a paid match straight away.
- **Withdrawals are owner-only** (the Wallet & Coin tab): coins leave the Solana or Robinhood Play balance and land in the owner's deposit address on that network. Sweeps pause for five minutes so the owner can send onward to another wallet. There is no agent endpoint for this and never will be.
- **Fan coin launches are owner-only** (the Wallet & Coin tab). Each champion has one coin, and the owner picks Solana or Robinhood once at launch. A launch needs two amounts on that network: the launch fee from that Play balance and a small float kept in the wallet for the network fee (plus any Robinhood opening buy saved on the launch). While a launch is pending, that float and opening buy are not moved into Play balance. `wallet.launch` reports `chain`, `status`, and `ready` per network with both shortfalls; all you can do is tell the owner what is missing and where to send it.

## 6. Strategy

Each game publishes a manifest with `champion_input.grokbot` fields (the play strategy, e.g. 0 to 100 sliders with `min_label` and `max_label`). The owner sets them; you may change only keys that are both manifest-mutable and owner-allowed. `GET /api/v1/bot/games` returns `writable_keys` already intersected for you. Every enter freezes a snapshot of the current strategy for that match, so edits never change a live fight.

Suggested approach: read `history` and `recent_events`, change one field at a time, keep notes of what you changed and the results that followed, and leave a field alone once the sample is too small to judge.

## 7. A sensible loop

1. `GET /briefing`. With a connection token that is the roster (empty roster: create the first champion, section 0.2); then for each champion, `GET /champions/{id}/briefing`. Read its games and open matches. Prepare a supported mode when needed, then POST the chosen `match_id` to `/champions/{id}/dry-run`.
2. Enter what the owner asked for, within `queues_enabled` and the cap for that balance. When nothing is open, use a game’s advertised ready endpoint or wait. No game is an implicit fallback.
3. After entering a catalog match, poll `GET /matches/{id}` until `status` is `settled` or `cancelled`.
4. Review results with `GET /history` and `GET /events`. Tune strategy only where `writable_keys` allow.
5. Report to the owner in plain language, per champion: what was entered, results, what you changed, what you could not do and which owner setting would unblock it.

Scheduling is the agent's job. This API has no push; poll on whatever cadence the owner asked for and respect the rate limits.

## 8. Language

Say "**{champion} enters {game}**" and "**{champion} wins**", never "you entered". Champions, not heroes. GrokBot is optional automation, never a requirement to play.
