Guide
Errors, rate limits and retries
Every error code and what to do about it, the rate limits, and how Idempotency-Key makes retries safe.
Errors keep the usual envelope with error.code (stable, branch on it), error.message (plain English, show it) and error.field (the input at fault, when there is one).
{
"status": "error",
"data": null,
"error": {
"code": "invalid_request",
"message": "price must be > 0 for LIMIT orders",
"field": "orders[0].price"
}
}4xx: the request was refused and nothing was done. Fix it before retrying.5xx: a problem on our side or at the broker.503means nothing was done: retry with backoff.- Quote the
x-request-idresponse header when you ask for help.
Error codes #
| Code | HTTP | What it means | What to do |
|---|---|---|---|
malformed_json | 400 | The body is not JSON, or does not fit the request shape (unknown or missing fields, wrong types). | Fix the request as the message says. Retrying it unchanged fails again. |
invalid_query | 400 | The query string does not fit the route's parameters. | Fix the request as the message says. Retrying it unchanged fails again. |
bad_request | 400 | The request could not be read before reaching the route (e.g. an unreadable path segment); message says what. | Fix the request as the message says. Retrying it unchanged fails again. |
idempotency_key_required | 400 | The Idempotency-Key header is missing (use a new UUID per order request). | Add an Idempotency-Key header: a new UUID per request. |
invalid_idempotency_key | 400 | The Idempotency-Key header is not 1-128 printable characters. | Fix the request as the message says. Retrying it unchanged fails again. |
unauthorized | 401 | No credential, or the credential is invalid, expired or revoked. | Check the Authorization header. If the key was revoked, create a new one. |
forbidden | 403 | The credential may not use this route (missing scope, app-session-only route, IP not allowed). | Use a key with the scope named in the message, from an allowed IP. App-only routes need the app. |
own_session_required | 403 | Credentials can only be created from the user's own signed-in session. | Your credential may not do this. Check its scopes, IP allowlist or plan. |
live_not_enabled | 403 | Live deployments need the strategies_live scope and a broker open for live automated strategies. | Your credential may not do this. Check its scopes, IP allowlist or plan. |
oms_not_enabled | 403 | Order management is not enabled for this user. | API access is not on for your account yet. Ask support to enable it. |
not_shared | 403 | That user has not shared this data with the caller. | Your credential may not do this. Check its scopes, IP allowlist or plan. |
account_not_found | 404 | No linked broker account with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
instrument_not_found | 404 | No instrument with this token (unknown or expired). | Check the id: it may be wrong, gone, or belong to another account. |
basket_not_found | 404 | No saved basket with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
watchlist_not_found | 404 | No watchlist with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
trigger_not_found | 404 | No trigger with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
api_key_not_found | 404 | No API key with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
connection_not_found | 404 | No connected partner app with this client id. | Check the id: it may be wrong, gone, or belong to another account. |
unknown_app | 404 | Unknown or disabled partner app. | Check the id: it may be wrong, gone, or belong to another account. |
endpoint_not_found | 404 | No signal URL with this id (or this secret) for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
webhook_not_found | 404 | No webhook with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
telegram_not_linked | 404 | Telegram is not connected for the caller; start a link first. | Check the id: it may be wrong, gone, or belong to another account. |
activity_not_found | 404 | No activity record with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
strategy_not_found | 404 | No automated strategy with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
version_not_found | 404 | No such version of the strategy. | Check the id: it may be wrong, gone, or belong to another account. |
shared_not_found | 404 | Nothing is shared with the caller here: the share was stopped or the link revoked, or it never existed. | Check the id: it may be wrong, gone, or belong to another account. |
listing_not_found | 404 | No such gallery listing for the caller: withdrawn, taken down, not approved yet (and not theirs), or it never existed. | Check the id: it may be wrong, gone, or belong to another account. |
deployment_not_found | 404 | No deployment with this id for the caller. | Check the id: it may be wrong, gone, or belong to another account. |
backtest_not_found | 404 | No backtest with this id for the caller (results are kept 30 days). | Check the id: it may be wrong, gone, or belong to another account. |
schema_not_found | 404 | No such strategy document format on this server. | Check the id: it may be wrong, gone, or belong to another account. |
order_not_found | 404 | No order with this id (broker order id or order_tag) in the caller's order book today. | Check the id: it may be wrong, gone, or belong to another account. |
request_in_progress | 409 | A request with this Idempotency-Key is still being processed. | Wait a moment, then retry with the same Idempotency-Key. |
trigger_busy | 409 | The trigger changed while it was being updated; retry. | It clashes with the current state. Read the latest state, then decide. |
trigger_fired | 409 | The trigger already fired; its exit is being handled. | It clashes with the current state. Read the latest state, then decide. |
broker_held | 409 | The protection is held by the broker; change the broker order instead. | It clashes with the current state. Read the latest state, then decide. |
watchlist_name_taken | 409 | The caller already has a watchlist with this name. | It clashes with the current state. Read the latest state, then decide. |
webhook_limit_reached | 409 | The webhook limit is reached; delete one first. | It clashes with the current state. Read the latest state, then decide. |
conflict | 409 | A partner app's webhook was changed at the same time as this request; retry. | It clashes with the current state. Read the latest state, then decide. |
already_flat | 409 | There is no open position to exit for this account, instrument and product. | It clashes with the current state. Read the latest state, then decide. |
stale_quote | 422 | This order cannot be sent at market: there is no price for the instrument at all (a price that is not recent is used: the order goes out as a LIMIT protected from it), or the best bid / ask is too far from the last traded price (a thin book); the message says which. Place a LIMIT order instead. Nothing was sent to the broker. | Fix the field named in error.field. Nothing was done. |
bracket_position | 409 | The position is held by a bracket / cover order: exit it with DELETE /v1/orders/bracket/{order_id}, which squares it off at the broker. Nothing was placed. | It clashes with the current state. Read the latest state, then decide. |
position_protected | 409 | The order is a (partly) filled bracket / cover entry: cancelling it would cancel the live stop-loss / target legs of its position. Send legs=true to do it anyway. | It clashes with the current state. Read the latest state, then decide. |
order_not_open | 409 | The order is already finished (filled, cancelled or rejected), so there is nothing to modify or cancel. Nothing was sent to the broker. | It clashes with the current state. Read the latest state, then decide. |
ambiguous_order | 409 | The id names orders in more than one account (or several orders); message lists the accounts. Add account= to choose one. | It clashes with the current state. Read the latest state, then decide. |
strategy_busy | 409 | The strategy changed since it was read (or is archived); read it again and retry. | It clashes with the current state. Read the latest state, then decide. |
strategy_in_use | 409 | The strategy has a deployment that is not stopped; stop it first. | It clashes with the current state. Read the latest state, then decide. |
deployment_finished | 409 | The deployment is stopped; deploy the strategy again to run it. | It clashes with the current state. Read the latest state, then decide. |
deployment_limit_reached | 409 | The most deployments that may run at once are running; stop one first. | It clashes with the current state. Read the latest state, then decide. |
backtest_limit_reached | 409 | The most backtests that may be queued or running at once, or started today, are reached. | It clashes with the current state. Read the latest state, then decide. |
backtest_finished | 409 | The backtest has already finished (done, failed or cancelled); nothing to cancel. | It clashes with the current state. Read the latest state, then decide. |
no_saved_version | 409 | A strategy is shared as a saved version, never its draft: save a version first. | It clashes with the current state. Read the latest state, then decide. |
not_enough_cycles | 409 | A gallery listing needs at least 20 finished trades of the version in the author's runs with the chosen badge. | It clashes with the current state. Read the latest state, then decide. |
listing_pending | 409 | A listing of this strategy is already waiting for review; withdraw it first. | It clashes with the current state. Read the latest state, then decide. |
purchase_required | 409 | A paid gallery template must be bought before it is copied. | It clashes with the current state. Read the latest state, then decide. |
insufficient_credits | 409 | Not enough platform credits for this purchase; nothing was charged. | It clashes with the current state. Read the latest state, then decide. |
purchase_failed | 409 | A gallery purchase did not complete and nothing was charged; retry with a new Idempotency-Key. | It clashes with the current state. Read the latest state, then decide. |
purchase_in_progress | 409 | A gallery purchase with this Idempotency-Key is still being made; retry with the same key. | It clashes with the current state. Read the latest state, then decide. |
not_flat | 409 | A signal strategy's mode can change only when flat: a position held by the signal strategy, or an open trade on the deployment, must close first. Nothing was changed. | It clashes with the current state. Read the latest state, then decide. |
payload_too_large | 413 | The request body is larger than the server accepts; send less (e.g. fewer orders per request). | Read the message; it says what went wrong. |
invalid_request | 422 | The request is well-formed but not valid; field names the input. | Fix the field named in error.field. Nothing was done. |
invalid_document | 422 | The strategy document has problems; message lists each (where in the wizard, what is wrong) and field names the first. | Fix the field named in error.field. Nothing was done. |
not_deployable | 422 | The strategy version cannot be deployed yet (a component or option not released, or an underlying not open); message says why. | Fix the field named in error.field. Nothing was done. |
acknowledgement_required | 422 | A warning must be acknowledged to deploy (e.g. unlimited_loss): add its code to acknowledge. | Fix the field named in error.field. Nothing was done. |
idempotency_key_reused | 422 | This Idempotency-Key was already used with a different request. | Send a new Idempotency-Key for a new request. |
partial_lot_position | 422 | The open position (less exits already working) is not a whole number of lots, so it cannot be fully exited through this route; message says how much remains. | Fix the field named in error.field. Nothing was done. |
not_supported_by_broker | 422 | The account's broker does not offer this (e.g. margin calculation, GTT listing, or GTT on BFO / intraday at Pocketful). | Fix the field named in error.field. Nothing was done. |
gtt_refused | 422 | The broker refused the GTT protecting a position and rules.fallback is reject: nothing was created. | Fix the field named in error.field. Nothing was done. |
archive_day_too_large | 422 | That day's archived activity is too large to serve at once. | Fix the field named in error.field. Nothing was done. |
invalid_client | 422 | OAuth: unknown partner app, or wrong client credentials. | Fix the field named in error.field. Nothing was done. |
invalid_grant | 422 | OAuth: the authorization code or refresh token is invalid, used or expired. | Fix the field named in error.field. Nothing was done. |
unsupported_grant_type | 400 | OAuth token endpoint: grant_type is missing or not supported. | Fix the request as the message says. Retrying it unchanged fails again. |
slow_down | 429 | OAuth token endpoint: too many failed attempts from this source; retry after Retry-After. | Slow down and retry later. |
invalid_scope | 422 | OAuth: a requested scope is unknown or not allowed for the app. | Fix the field named in error.field. Nothing was done. |
temporarily_unavailable | 503 | OAuth: the authorization server is temporarily unavailable. | Temporarily unavailable. Retry with backoff. |
rate_limited | 429 | Too many requests for this credential (or too many failed attempts); retry after the Retry-After header (seconds; error.retry_after_ms has the exact wait). | Wait as long as the message says, then retry. Spread requests out. |
broker_refused | 502 | The broker refused the change. | The broker failed. Nothing may have reached it: check, then retry. |
broker_outcome_unknown | 504 | The broker did not answer in time: it may or may not have applied the change. Check before retrying. | The broker did not answer in time. Check the order book before retrying, then retry with the same Idempotency-Key. |
service_unavailable | 503 | A dependency is temporarily unavailable, or the feature is off on this server; retry later. | Nothing was done. Retry with backoff (1 s, 2 s, 4 s …). |
credits_unavailable | 503 | Platform credits can't be reached right now; nothing was charged. Retry. | Temporarily unavailable. Retry with backoff. |
instruments_unavailable | 503 | The instrument master is not loaded yet; retry shortly. | Temporarily unavailable. Retry with backoff. |
trigger_worker_down | 503 | No trigger exit worker is running; nothing was created. | Temporarily unavailable. Retry with backoff. |
webhooks_disabled | 503 | Webhooks are not enabled on this server. | Temporarily unavailable. Retry with backoff. |
notifications_disabled | 503 | Notifications are not enabled on this server. | Temporarily unavailable. Retry with backoff. |
unavailable | 503 | Stream only: live updates are unavailable; reconnect. | Temporarily unavailable. Retry with backoff. |
Rate limits #
Defaults shown; a deployment can change them.
- Per API key: 20 requests a second by default. Over it, 429
rate_limitedat once; the message says how many ms to wait. - Per partner app and user: the same budget, counted separately for each user who connected the app.
- Failed credentials: after about 30 failures in a minute from one IP, that IP gets 429 until the minute is up.
- Per broker account: orders, modifies, cancels, GTT and convert share one budget (10 a second by default). A burst waits up to 2 s for a slot instead of failing.
- Portfolio reads come from snapshots and never spend the broker budget.
- Signal webhook URLs each have their own limit (429 over it).
Safe retries: Idempotency-Key #
- Required on
POST /v1/ordersandPOST /v1/orders/bracket: 1-128 printable characters, a new UUID per request. Keys are per user. - A retry with the same key and the same body returns the original response unchanged (for 24 hours), with the header
idempotent-replayed: true. Nothing is placed twice. - While the first request is still running a retry gets 409
request_in_progress; the same key with a different body gets 422idempotency_key_reused. - After a timeout or a slice with status
unknown, retry with the same key, never a new one.