Skip to main content

Errors

Every error returned by the 4Cx API uses the same JSON envelope:
When details.retryAfterSeconds is set, the response also includes a standard Retry-After header.

Programming against errors

  1. Check the HTTP status first.
  2. If error.details.code is present, branch on that (it is stable across releases). The throttle codes documented on Rate limits are the main consumers of this.
  3. For everything else, the HTTP status + endpoint context is enough. Do not parse error.message — those strings are not part of the contract and may change.

Common errors by status

400 — Bad request

Validation failed on the request body or query string. Common triggers:
  • Missing required field (e.g. Username and password are required on /user/login).
  • Invalid gameID (Invalid gameID: <id> on any endpoint that looks up a game).
  • Invalid playerMode (must be "coins" or "cash" — see Switch player mode).
  • Order size below MIN_ORDER_SIZE or above the configured maximum.
What to do: fix the request. 400s are never retried — they will fail the same way every time.

401 — Unauthorized

The token in Authorization (or the auth cookie) is missing, expired, or doesn’t resolve to a user.
What to do: re-authenticate via Log in and retry with the new data.user.auth token. Remember: the API takes the raw token — no Bearer prefix.

403 — Forbidden

You are authenticated but not allowed to act on this resource. Two common shapes:
  • Resource ownership: Not your order: <orderID> when you try to cancel or look up an order another user owns.
  • Account restrictions: USER IS BANNED, Your account <username> has been closed, or a permission flag (e.g. admin-only endpoints).
  • Limits: Order size exceeds maximum limit of 1000.00 on place/edit.
What to do: don’t retry. If you believe the resource should be yours, double-check you logged in as the correct user.

404 — Not found

The path is valid but the resource doesn’t exist — e.g. you looked up an orderID or depositRequestId that was never created (or was deleted).

409 — Conflict

The request was well-formed and you’re authorized, but the state of the market changed between when you read it and when you tried to act on it. This is the most common error class for any trading client and the one most worth handling explicitly. Common triggers on POST /session/v2/place:
  • "Order size exceeds quantity available, which is $X.XX" — the resting offer you tried to take was partially or fully consumed by another taker before your request landed.
  • "Odds have been edited" — the maker edited their order between your read and your take.
  • "Session is expired" / "Session is cancelled" — the order you targeted is no longer takeable.
  • "Game is past start time" / "Game has started" / "Game has been taken OTB or is graded" — the market closed (or moved to in-play) between your read and your write.
  • "You cannot fill your own order: <orderID>" — you are the maker of the order you tried to take. Only enforced in cash mode.
  • "Order size exceeds available credit" / "Exceeds max liability <N>" — your available credit / max liability changed (e.g. a concurrent order on the other side of your book).
What to do: re-read the orderbook before retrying. A blind retry will almost always fail the same way. The correct pattern for a market-taking bot is:
  1. Catch the 409.
  2. Refresh the orderbook for the affected gameID (and orderbook depth on the side you wanted).
  3. Decide whether the new state still matches your strategy. If yes, place a new order against the current best offer; if no, abandon.
Do not treat 409 as a generic rate-limit-style retry — backing off and resending the identical request is a common bot bug that just wastes your place-bucket budget.

423 — Locked

A cancelAll-style operation is currently running for your account, so individual cancels are temporarily locked out. This has structured details and should not be retried like a normal rate limit. See Rate limits → CANCEL_ALL_IN_PROGRESS.

429 — Too many requests

You exceeded the per-second rate limit on a place or cancel endpoint, or the cancel pipeline is saturated. Two distinct details.code values (RATE_LIMITED, CANCEL_QUEUE_FULL) — full retry strategy on Rate limits.

500 — Server error

An unexpected failure on our side. The body is intentionally generic:
What to do: retry with backoff. If you see sustained 500s on a specific endpoint, reach out at hello@4cx.io with the approximate timestamp and your username.