Errors
Every response uses the same envelope. Failures set success to false, put a human-readable sentence in error, and a stable machine-readable identifier in code. Branch on code, never on the message: the prose is free to change, the code is not.
{
"success": false,
"error": "Live autopilot is not open on this venue. Live autopilot is available on Kalshi and Polymarket US, and paper autopilot on every venue.",
"code": "LIVE_TRADING_UNAVAILABLE"
}VALIDATION400do not retryThe body or query did not match the schema. The message names the offending field, e.g. criteria.priceMax. Unknown fields are rejected rather than ignored, so a typo fails loudly instead of silently matching everything.
IDEMPOTENCY_KEY_REQUIRED400do not retryA live order or sell was sent without an Idempotency-Key header. Generate one, keep it, and reuse it if you retry.
SCOPE_REQUIRED403do not retryThe key is valid but lacks the scope this endpoint needs, or the account behind it has no active subscription. Scopes cannot be widened on an existing key: issue a new one.
LIVE_TRADING_UNAVAILABLE403do not retryReal-money autopilot is not open on that venue. It is open on Kalshi, through your own connected Kalshi key, and on Polymarket US, through your own connected Polymarket US account. Global Polymarket is paper only. Paper autopilot works on every venue. This does NOT affect placing an order yourself, which is a separate path.
GEO_BLOCKED403do not retryTrading is not available from the region the account onboarded in.
SPEND_CAP_EXCEEDED403do not retryThe order would take the key past its daily spend cap. The reservation is released, so the day's remaining headroom is unchanged. Resets at midnight UTC.
NOT_FOUND404do not retryThe resource does not exist or belongs to a different account. Ownership is checked on every path, and a resource you do not own is reported the same as one that does not exist.
IDEMPOTENCY_IN_PROGRESS409retry after waitingAn identical request with the same Idempotency-Key is still running. Wait and poll rather than sending it again with a fresh key, which would place a second order.
WALLET_NOT_READY409do not retryThe trading wallet is not deployed, its delegation expired, or it holds no USDC. GET /v1/portfolio?include=readiness names which.
AUTOPILOT_NOT_READY409do not retryAutopilot cannot start in its current configuration. readiness.blockers names why.
QUOTA_EXCEEDED429retry after waitingToo many requests this minute. The response carries retry-after with the seconds until the window resets.
UPSTREAM_FAILED502safe to retryThe venue rejected or could not be reached. The message carries their reason where there is one. Nothing was charged against your spend cap.
INTERNAL500safe to retryOur fault. Retry, and write in if it persists.
Handling failures
Three groups matter. Some failures are permanent for the request as written and retrying changes nothing. Some clear on their own and want a wait. Some are ours or the venue's and are safe to send again.
const res = await fetch(url, { headers });
const body = await res.json();
if (!body.success) {
switch (body.code) {
case "QUOTA_EXCEEDED":
// Wait retry-after seconds, then retry the same request.
break;
case "SCOPE_REQUIRED":
// Permanent for this key. Issue a new one with the scope.
break;
case "WALLET_NOT_READY":
case "GEO_BLOCKED":
case "LIVE_TRADING_UNAVAILABLE":
// Permanent until the user changes something. Do not retry.
break;
case "UPSTREAM_FAILED":
// The venue, not you. Safe to retry with the SAME Idempotency-Key.
break;
}
}Retrying an order safely
Reuse the same Idempotency-Key when retrying a live order or sell. A retry carrying the original key returns the original result rather than buying twice, and sets Idempotency-Replayed: true so you can tell the two apart.
Reusing a key with different details is refused with VALIDATION rather than treated as a new order, so a key you have changed the amount on will never place a second position by accident.
Over MCP
Auth and rate-limit failures surface as HTTP status codes before the protocol handshake, so a bad key fails at connection time rather than mid-session. Tool-level failures come back as a normal tool result whose JSON carries the same error and, where one applies, the same code. A tool your key lacks the scope for is not listed at all, so a scope failure is usually invisible: the model never sees the tool.
Limits are covered on Rate limits, and scopes on Authentication.