Reference

Errors & rate limits

Failures return a JSON object with an error field and a 4xx or 5xx status. This page lists every one, what actually causes it, and whether retrying will help.

Error shape

One consistent envelope, whatever went wrong.

error response
{
  "error": "Bad signature"
}

A 403 for a missing scope carries one extra field naming the scope that was required:

403 insufficient_scope
{
  "error": "insufficient_scope",
  "required_scope": "trade:read"
}
Match on the HTTP status first and the error string only when you need to tell two causes of the same status apart. Wording may be clarified over time; statuses will not change under v1.

Every error

Grouped by status, in ascending order.

StatuserrorCauseWhat to doRetry
400Account is not ready for trading (missing subaccount_id)The account exists but has not finished provisioning on the trading network, so no orders can be routed to it.Wait for provisioning to complete. The account will show as tradable in the dashboard, and you can retry then.Retry with backoff
400<field-specific validation message>The order body failed validation. The message names the first problem found, such as an unknown execution_type, a malformed accountId, or a wrong field type.Check the payload against the Placing orders reference. Unrecognised fields are ignored rather than rejected, so a typo'd field name shows up as a missing-value error.Retry after fixing
400'leverage', 'value', or 'quantity' must be providedExactly one sizing field is required on MARKET and LIMIT orders. Sending none, or sending two, is rejected.Pick a single sizing field per order.Retry after fixing
401Bad signatureThe HMAC did not match. The credential is recognised, so the fault is in the canonical string, the body hash, or the secret.Compare your signer against the worked test vectors on the Authentication page. They isolate which of the three is wrong.Retry after fixing
401Invalid keyNo active key matches X-Vanta-Key-Id: the key id is wrong, or the key has been revoked or deleted.Check the key id, and confirm the key is still listed as active in Settings → Key Management.Do not retry
401Invalid timestampX-Vanta-Timestamp is not a positive finite number. It is most often seconds since the epoch, an ISO date string, or a float.Send whole milliseconds since the Unix epoch as a decimal string.Retry after fixing
401Missing credentialsOne or more of the four auth headers is absent or empty. Note that all four are required on every request, including GETs.Send X-Vanta-Key-Id, X-Vanta-Timestamp, X-Vanta-Nonce and X-Vanta-Signature together.Retry after fixing
401Timestamp invalidThe timestamp is more than 5 minutes away from server time in either direction. Usually a clock drift problem on the caller's machine, or a request that sat in a queue before being sent.Sync your clock (NTP) and sign immediately before sending. Never reuse a signature from an earlier batch.Retry after fixing
403insufficient_scopeThe key does not carry the scope the endpoint requires. The response also includes a required_scope field naming it.Create a new key. Scopes are fixed at creation time and cannot be added to an existing key.Do not retry
403This API key is only valid for a different accountEach key is bound to exactly one prop account. Either the ?accountId= query parameter or the accountId in the request body names a different one.Omit accountId on reads (the key already identifies the account), or send the bound account's id on writes. Use one key per account.Do not retry
404Account not foundThe bound account no longer resolves for the key's owner. It was deleted, transferred, or never fully provisioned.Confirm the account still exists in the dashboard, and re-issue the key against a current account.Do not retry
409NonceThis nonce has already been used by this key. Nonces are stored permanently and are single use for the life of the key. This is the replay protection.Generate a fresh random nonce per request (16 random bytes hex is ample). Never retry a failed request with the same nonce.Retry after fixing
429rate_limitedThe per-key read budget for the current minute is exhausted. The response carries Retry-After plus X-RateLimit-Limit / -Remaining / -Reset.Back off until Retry-After elapses. Poll on a fixed schedule rather than in a tight loop.Retry with backoff
429too_many_failed_requestsThe key accumulated 30 rejected requests within 10 minutes, so order placement is paused for a 15-minute cooldown. Retrying the same failing request in a loop is what triggers this.Fix the request that keeps being rejected — the error you were receiving before the cooldown names the problem. Any successful request resets the failure count. Wait out Retry-After before trying again.Retry after fixing
500Decrypt failedThe stored key secret could not be decrypted server-side. This is a platform fault, not a problem with your request.Contact support with your key id (never the secret).Retry with backoff
502<upstream error message>The upstream trade-history service answered with an error rather than data.Retry with backoff; if it persists, contact support with the timestamp of the call.Retry with backoff
503Account snapshot temporarily unavailable (PTN validator unreachable); retry shortlyThe upstream validator could not be reached. The endpoint fails closed on purpose: it will not return placeholder balances that a polling client would record as real account history.Skip this cycle and retry with backoff. Do not treat it as a zeroed account.Retry with backoff

Rate limits

Read endpoints are capped at 120 requests per minute per key.

The window is fixed rather than sliding: the budget refills at the top of each window rather than gradually. When it is exhausted the response is 429 with these headers:

HeaderMeaning
Retry-AfterSeconds until the window resets. Wait at least this long.
X-RateLimit-LimitRequests permitted per window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix time, in seconds, at which the window resets.

These headers only appear on a 429

Successful responses do not currently carry the rate-limit headers, so you cannot read your remaining budget from a 200. Track your own request rate rather than relying on the server to tell you when you are close.

The limit is per key, so separate strategies on separate keys get separate budgets. Order placement does not consume the read budget and is not rate-limited on volume — but it is guarded against sustained failure: a key that accumulates 30 rejected requests within 10 minutes is answered with 429 and a Retry-After header for a 15-minute cooldown. A single successful request resets the failure count, so a working integration never sees this — fix the failing request rather than retrying it in a loop, and handle 429 on any endpoint.

Retrying safely

Two rules that matter more here than on a typical REST API.

  • Re-sign every retry with a fresh nonce and timestamp. Replaying an identical signed request returns 409, never a duplicate order. That is protection, not an error to code around. Build the request again from scratch on each attempt.
  • Order placement is not idempotent. There is no idempotency key. A request that times out may still have been accepted, so before retrying a write, read GET /positions or GET /orders and check whether it landed. Blind retries open double positions.

A 503 is not a flat account

503 on a read means the upstream validator was unreachable, so the endpoint refuses to answer rather than return placeholder numbers. A polling client that treats it as real data will record a zeroed account and may act on it. Skip the cycle and retry.