Errors and rate limits
There are two kinds of failure, and they look different on purpose.
Protocol errors
A call that can’t be served returns a JSON-RPC error object. Branch on data.reason; the numeric code groups reasons into families.
{ "jsonrpc": "2.0", "id": 4, "error": { "code": -32002, "message": "IWM requires the Pro plan", "data": { "reason": "plan" } } }
data.reason |
Code | Meaning | What to do |
|---|---|---|---|
| (none) | -32001 |
Unauthenticated: token missing, expired or revoked. Over HTTP this is a 401 with a resource_metadata link |
Refresh the token; if that fails, authorize again (a revoked agent reconnects as a new agent) |
scope |
-32002 |
The agent is read-only, so place_order and cancel_order are refused |
Stay read-only; only the user can change it, in the terminal |
plan |
-32002 |
Not available on the user’s plan, e.g. a symbol beyond SPY and QQQ on Free | Tell the user; don’t retry |
tradier_not_connected |
-32002 |
The user’s Tradier connection has lapsed, so there is no live data or order path | Ask the user to reconnect Tradier in the terminal |
halted |
-32002 |
The user halted this agent | Stop; the user resumes it from the terminal |
agent_limit |
-32002 |
This agent connected beyond the plan’s agent limit (Free 1, Pro 3), so it was created halted | Tell the user: revoke another agent and resume this one in the terminal, or upgrade. Don’t retry |
not_found |
-32003 |
Unknown symbol, expiry or order | Check list_symbols or get_orders |
rate_limited |
-32004 |
Too many calls this minute | Back off, then retry; see the limits below |
not_ready |
-32005 |
The ladder for that expiry has no valid fit yet (just after open, or a symbol that just started streaming) | Retry in a second or two |
timeout |
-32005 |
The ladder didn’t become ready in time | Retry shortly; if it persists, try another expiry |
invalid_params |
-32602 |
The input failed the tool’s schema | Fix the arguments; the schema is on each tool’s page |
Order rejections
A place_order that reaches the order service but breaks a rule is not an error. It returns an order with state: "rejected" and a rejection, and it is logged like any other order.
{ "state": "rejected",
"rejection": { "code": "max_contracts", "limit": "max_contracts_per_order=10",
"message": "12 contracts exceeds this agent's limit of 10" } }
code |
Fired by |
|---|---|
max_contracts |
Contracts per order |
max_debit |
Net debit per order |
max_open_spreads |
Open spreads on this symbol |
daily_loss |
Daily loss reached; read-only until the next session |
rate_limit |
Orders per minute |
trading_window |
Outside market hours, or the last minutes of a 0DTE session |
halted |
The user halted this agent |
invalid_intent |
The intent can’t be built, e.g. a limit above the width |
quote_invalid |
The rung’s quotes failed validation |
plan |
Not allowed on this plan, e.g. a live order from a Free agent |
broker |
Tradier rejected the order; message carries Tradier’s reason |
Orders are never shrunk to fit. Paper and live rejections are identical.
Rate limits
Per agent, per minute:
| Plan | Read calls | Write calls |
|---|---|---|
| Free | 20 | Paper only |
| Pro | 60 | 10 |
The agent’s own orders_per_minute limit also applies and can be lower. get_limits reports what remains.