API Docs
Trade prediction markets with virtual dollars from your terminal, and hand the same commands to an agent. The CLI runs dual-mode: a person gets tables and a confirmation before every trade, a pipe gets token-efficient output, no prompts, and next-step hints. Everything here is paper trading; no real money moves, ever.
Install
Node 20 or newer. Nothing in the CLI is a secret: all of its authority is your own signed-in session.
# one-off, nothing installed
npx arena-cli login
# or install the binary
npm install -g arena-cli
arenaThere is nothing else to install for agent use. AXI is not a separate package, it is how arena behaves when its output is piped, so the same binary serves a person and an agent.
Sign in
Arena has no password login. arena login hands the browser the interactive step and catches the redirect on a loopback port.
$ arena login
→ opening your browser to sign in with Google…
(listening on http://127.0.0.1:53682/callback)
✔ signed in as sharp_joe · 100.00 spendable · season 2026-09arena login --provider applesigns in with Apple instead of Google.arena login --guestopens no browser and creates a guest account. Be warned: signing in with Google or Apple afterwards is a different user, and the guest account’s trades stay behind on it.arena login --device, the flow for a machine with no browser, is not in this release. On a headless box, sign in on a machine that has one and copy~/.config/arena/credentials.jsonacross, or usearena login --guest.- Credentials go in your OS keychain, falling back to
~/.config/arena/credentials.jsonwith mode 0600. The access token lives about an hour and refreshes itself;arena logoutdeletes the stored pair.
Command reference
Reads work signed out where the data is public. Writes need a session.
| Command | What it does |
|---|---|
| arena | Home view. Live balance, open-position count, season and next steps, not help text. |
| arena login [--provider google|apple] [--guest] | Sign in. Opens a browser and catches the redirect on 127.0.0.1. --device, the headless flow, is not in this release. |
| arena logout | Forget the stored credentials. |
| arena whoami | Who you are signed in as, Pro status, spendable balance, season. |
| arena markets [--league …] [--group …] [--event …] [--limit 20] | Browse open markets. Default limit 20. |
| arena market <ticker> | One market in detail. |
| arena search <query> | Substring search over titles and tickers. |
| arena catalog | The groups and leagues you can filter by. not yet shipped |
| arena screener [--min-volume …] | Rank open markets by volume, spread, move, time to close. not yet shipped |
| arena orderbook <ticker> [--depth 10] | Resting bids and asks. |
| arena chart <ticker> [--range 1h|1d|1w] | Price history. not yet shipped |
| arena buy <ticker> --side yes|no --contracts N [--max-price CENTS] [--yes] [--dry-run] | Open a position. Quotes, confirms, places, then reconciles the actual fill against the quote. |
| arena sell <trade-id> [--yes] [--dry-run] | Close a position at the live bid. Trade ids come from arena positions. |
| arena settle | Settle anything already resolved. A backstop: the server settles automatically within about five minutes. |
| arena positions [--live] | Open positions with unrealised P&L. --live adds current prices with one extra market read. |
| arena balance | Spendable balance and net worth. |
| arena history [--month 2026-08] | Your settled trades. --month reads a past season. |
| arena picks | Today’s AI picks. |
| arena leaderboard [--top 25] | This season’s standings, with the AI models and the paper funds ranked in alongside the players, exactly as the app ranks them. Rows carry an AI, ETF or Team fund badge. |
| arena trader <ref> [picks] | A published trader record. Public, so it works signed out. |
| arena watch <ticker…> [--orderbook] | Live prices, repainted in place. not yet shipped |
| arena hook install | Install the ambient-context hook into your agent runner. not yet shipped |
Flags that work everywhere
| Flag | What it does |
|---|---|
| --json | Raw JSON on stdout. Works in both modes, on every command. The stable escape hatch. |
| --fields a,b,c | Widen a list beyond its default three or four columns. |
| --full | Stop truncating long text. |
| --yes | Skip the confirmation prompt. Required for any mutation in agent mode. |
| --dry-run | On buy and sell: do the whole quote and stake calculation, send nothing. |
A first session
Terminal samples on this page use the placeholder ticker ARENA-EXAMPLE-1. Real tickers come from arena markets and arena search.
$ arena balance
Balance 84.30
Net worth 96.55
Season 2026-09
$ arena buy ARENA-EXAMPLE-1 --side yes --contracts 10
NFL · KC vs BUF · Sep 10 — yes @ 62¢ ask (bid 58¢)
stake 6.20 · balance 84.30 → 78.10
Fills server-side at the live ask, so it may differ from this quote.
Continue? [y/N] y
✔ filled at 63¢ · stake 6.30 · trade a1b2c3d4 (slippage +1¢ vs quote)
$ arena positions
TRADE MARKET SIDE ENTRY NOW P&L
a1b2c3d4 NFL · KC vs BUF · Sep 10 yes 63¢ 67¢ +0.40
$ arena sell a1b2c3d4 --yes
✔ sold at 67¢ · +0.40Three things worth knowing about that buy. The engine is market-order-only: you never set a price, and the fill happens server-side at the live ask, which is why the CLI reconciles the actual fill against the quote and tells you the slippage. --max-price refuses to submit when the quoted ask is above your ceiling. And every logical attempt carries a fresh idempotency key, so a network timeout can be retried with the same key without any risk of filling twice.
For agents: the AXI contract
Arena implements AXI (axi.md). If you are wiring this into an agent, this section is the contract.
Agent mode triggers itself
The CLI is in agent mode when !process.stdout.isTTY (that is, whenever output is piped or captured) or when ARENA_AGENT=1 is set. There is nothing to configure. In agent mode:
- output is TOON, not tables, and never has spinners or colour;
- there are no prompts, ever. A mutation without
--yesis a hard error, and the CLI will not wait on stdin; - stdout carries data, stderr carries logs and diagnostics, so
2>/dev/nullis always safe; - every list reports
totalCount, and an empty result prints an explicitcount: 0oremptyReasonrather than nothing at all; - every output ends with a
help[]array of next-step command templates.
The home view
Bare arena is live state, not help text. An agent can orient itself in one call.
bin: arena
about: Arena — paper trading for prediction markets
mode: paper
season: 2026-09
balance: 84.30
netWorth: 96.55
openPositions: 3
help[3]: arena positions,arena leaderboard,arena buy <ticker> --side yes --contracts 10 --yesTOON shape
The encoder follows TOON v4.1:
- a tabular array is
key[N]{f1,f2}:with its rows indented one level; - an array of primitives is inline:
key[N]: a,b; - an empty array is
key: [], neverkey[0]:; - a string is quoted when it is empty, padded,
true,false,nullor numeric-like, when it holds a colon, comma, quote, backslash, bracket, brace or control character, or when it starts with-or#. Internal spaces, dots and the middle dot need no quoting.
season: 2026-09
status: live
fieldSize: 282
published: 3
rankBasis: net_worth_at_cost
asOf: "2026-09-11T15:00:00Z"
mode: paper
leaderboard[3]{rank,name,returnPct,record}:
4,sharp_joe,18.42,41-22-3
11,kcnerd,9.1,19-15-0
37,Lina B.,2.35,8-7-1
help[2]: arena trader sharp_joe,arena leaderboard --season 2026-08An empty result is an answer, not a failure. It exits 0 and says why:
trader: sharp_joe
season: 2026-10
totalCount: 0
picks: []
emptyReason: No settled picks in season 2026-10 yet.
help[1]: arena trader sharp_joe picks --season 2026-09Errors are structured too, and in agent mode they go to stdout so a captured run always parses:
error:
code: trader_not_found
status: 404
detail: No published record exists for this trader id.
help[1]: arena leaderboard--json prints the underlying JSON verbatim instead, in either mode. It is the stable, non-proprietary escape hatch and it is not going away.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success, including a definitive empty result. |
| 2 | Usage error: unknown flag, bad argument, invalid season. Reserved for this, so the CLI never silently ignores a flag it does not understand. |
| 4 | Not signed in, or the session expired after one refresh attempt. Run arena login. |
| 5 | Arena Pro required for this action. |
| 6 | The backend refused, definitively: market closed or resolved, no liquidity, insufficient balance, trade not yours, record not found. Do not retry. |
| 7 | Network failure, 5xx, or rate limited. Retrying later may work. A trade that fails this way can be retried with the same idempotency key. |
Any other non-zero exit is an unexpected internal error, so treat it as a bug and report it rather than retrying in a loop.
# agent mode, no --yes: hard error, exits non-zero, never waits on stdin
$ arena buy ARENA-EXAMPLE-1 --side yes --contracts 10 | cat
error:
code: confirmation_required
detail: Mutations in agent mode require --yes. Nothing was sent.
help[1]: arena buy ARENA-EXAMPLE-1 --side yes --contracts 10 --yes
$ echo $?
2Ambient context hook
A session-start hook lets the agent see current balance, open positions and the season before it acts, instead of spending a discovery round trip on it. Bare arena in agent mode prints exactly that, so the hook is one line of config: in Claude Code, ~/.claude/settings.json. (arena hook install, which writes this for you, is not in this release.)
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "arena" }] }
]
}
}arena: signed in
mode: paper
season: 2026-09
balance: 84.30
openPositions: 3
help[2]: arena positions,arena markets --league nbaPrompt your coding agent
Paste this into Claude Code, Cursor or any other coding agent to teach it the CLI in one shot.
You have the Arena CLI installed as `arena`. It is paper trading for sports
prediction markets, virtual money only, so nothing you do can spend real funds.
Start by running `arena` on its own. It prints my balance, my open positions and
the current season, so you do not need a discovery round trip.
The commands you will want:
arena markets --league nba open markets
arena search "lakers" find a market by text
arena market <ticker> one market in detail
arena orderbook <ticker> resting bids and asks
arena positions open positions with P&L
arena balance spendable points and net worth
arena buy <ticker> --side yes --contracts 10 --yes
arena sell <trade-id> --yes
arena history settled trades
Your output is piped, so the CLI is already in agent mode: you get TOON instead
of tables, you are never prompted, and every mutation needs --yes or it fails.
Add --json to any command if you would rather parse JSON.
Exit codes: 0 ok, 2 bad usage, 4 not signed in, 5 needs Pro, 6 client error,
7 network or server error. Treat anything else as a bug.
Before you buy anything, read the orderbook and tell me the ask and the stake.
Never place or close a trade I did not ask for.MCP server
A local stdio MCP server ships alongside the CLI and reuses the same stored login. Run arena login once, then point your client at it.
claude mcp add arena -- npx -y arena-mcp-serverFor Claude Desktop and anything else that reads an mcpServers block, add:
{
"mcpServers": {
"arena": {
"command": "npx",
"args": ["-y", "arena-mcp-server"]
}
}
}The stdio server acts as you, on your own machine, with your own session, the same trust shape as running the CLI yourself. A hosted, third-party MCP server is a different question and needs the scoped-grant work that API keys are the first half of.
Get an API key
A key acts for your account and carries only the scopes you give it. It is shown once, at creation, and stored only as a hash. We cannot recover it for you.
| Scope | Grants |
|---|---|
| records:read | Public Arena records: leaderboards and published trader records. |
| portfolio:read | The key owner’s own balance, positions and settled history. |
| trade:write | Reserved. Not issued in v1, because placing trades from a third-party key waits on the abuse and rate-limit work. Use the CLI with your own login to trade. |
Keys are created, listed and revoked here in the browser, signed in. There is deliberately no API for minting a key from a key: a leaked read-only key must not be able to mint itself a replacement and survive its own revocation. Revocation takes effect immediately on the next request.
Using a key
Send the key as a bearer token. Every keyed response is no-store.
curl -sS https://arena-predictions.com/api/v1/me \
-H "Authorization: Bearer arena_sk_your_key_here"{
"user": { "id": "0f4c…", "displayName": "sharp_joe" },
"key": {
"prefix": "arena_sk_4Fq1XyZ",
"scopes": ["records:read", "portfolio:read"]
},
"mode": "paper"
}/api/v1/me needs a valid key but no particular scope. It is how an agent discovers whose account it is acting for and what it is allowed to do, before it tries anything that could fail.
Errors
Every failure uses one envelope, so you can branch on the code:
{
"error": {
"code": "invalid_token",
"message": "API key is invalid, revoked or expired."
}
}| Status · code | Meaning |
|---|---|
| 400 invalid_request | Malformed body or parameter. The message names the field. |
| 401 unauthorized · invalid_token | Missing, malformed, revoked or expired key. Always carries a WWW-Authenticate: Bearer challenge. |
| 403 insufficient_scope | Valid key, wrong scope. Create a new key with the scope enabled. |
| 403 guest_account | Key management needs a Google or Apple account, not a guest session. |
| 403 scope_not_available | The scope exists but is not being issued yet (trade:write). |
| 404 key_not_found | No active key with that id on this account. |
| 409 key_limit_reached | Ten active keys already. Revoke one first. |
| 500 · 503 internal_error | Our fault, not yours. The key may be perfectly good. Retry (503 carries Retry-After); if it persists, get in touch. |
What a key can reach
The keyed API serves Arena-owned data only: leaderboards, published trader records, and the key owner’s own paper-trading results. Live market data from the exchanges Arena quotes is not redistributed through it. If you want live prices, use the CLI signed in as yourself. That is your own session, exactly like using the website.
Arena is paper trading. Every number these endpoints return is virtual and has no cash value. See how Arena works, the Terms of Use and the Privacy Policy, or get in touch.