Skip to main content
UpsideMAX returns errors at two levels: HTTP-level errors that appear in the top-level status: "error" response, and business-level errors that appear inside response.data.statuses[] even when status is "ok". Both levels are documented here.

HTTP-Level Error Codes

These errors are returned with a non-200 HTTP status code when the server rejects the request before or during processing. The response body contains status: "error", a machine-readable code, and a human-readable message.
A 504 DOWNSTREAM_TIMEOUT is not a failure. It means only that the server stopped waiting for the result. The order, cancel, or other write may already have been applied. Do not treat it as “not applied”, and do not submit a new request until you have confirmed the final state through orderUpdates or a query such as userOrders.

Business-Level Errors

These errors appear inside response.data.statuses[] when the request envelope is valid and authenticated, but the matching engine or risk engine rejects one or more operations within the batch. The outer status is "ok" and the HTTP status is 200.

Troubleshooting

This is the most common error when integrating for the first time. Work through this checklist:
  1. Right signing path — Funds/permission actions (registerAccount, approveAgent, revokeAgent, lockCollateral, unlockCollateral, transferBetweenDeployers) use the EIP-712 typed struct; everything else uses the Agent path. Signing an action via the wrong path always fails. See Authentication.
  2. Typed path — include every struct field in the wire JSON — For a typed action, every field the struct declares must be present in the request JSON, even the business-optional ones. approveAgent in particular must carry agentName ("" when unset) and validUntil (0 for no expiry); if you omit them, the server reads the JSON as-sent and computes a different digest than the one you signed, so recovery fails. Inject the defaults into the action before signing. See Authentication.
  3. Canonical JSON (Agent path) — Keys must be sorted alphabetically with compact separators and no whitespace: json.dumps(action, sort_keys=True, separators=(",", ":")). A single extra space produces a different actionHash.
  4. Correct domain & nonce encoding — Use the domain name="Exchange", version="1", verifyingContract=0x0, with the environment’s chainId (9767 on Devnet, 9788 on Mainnet). The nonce is a big-endian 8-byte suffix inside the Agent actionHash, but a 32-byte uint64 field in a typed struct — mixing these up is a common cause.
  5. No double-hashing — The EIP-712 digest is already the final hash. Pass the 32-byte digest directly to the signing function (prehash = false); do not hash again.
  6. Correct v value — Ensure v = sig.v if sig.v >= 27 else sig.v + 27. Some libraries return v as 0 or 1; you must add 27.
  7. Right private key — Recover the address from your own signature locally and compare it with the address you registered. If they differ, you are signing with a different private key. A valid signature from an address with no registered account returns 400 BAD_REQUEST (signer has no registered account), not SIGNATURE_INVALID.
This is a business-level rejection, not an HTTP error. The request was correctly signed and delivered to the matching engine, but the engine rejected the order.Check response.data.statuses[0].error for the rejection reason. Common causes:
  • size must be positive — You passed "s": "0" or a negative string.
  • price exceeds tick range (maxTicks x tickSize) — The price is outside the contract’s permitted tick range; read maxTicks and tickSize from configs.
  • orderId not found — The oid you tried to cancel doesn’t exist or was already filled.
Always iterate over the entire statuses array when placing batch orders — some items in the batch may succeed while others fail.
A DOWNSTREAM_TIMEOUT (HTTP 504) means the server did not receive the result within its waiting window. It does not mean the request failed, and it does not mean the request was not applied — the outcome is unknown. Reconcile before retrying rather than assuming it was dropped.What to do:
  1. Watch orderUpdates, or query POST /info with userOrders — and orderHistory if the order may already have terminated — to establish the actual state.
  2. Only if the order is genuinely absent, retry with a fresh nonce.
  3. If you continue to see timeouts, check the UpsideMAX status page or contact support with your requestId.
This means the address has already been registered in this environment. You do not need to register again — proceed directly to placing orders.If you are generating a new keypair each time in your test scripts, each new address will need its own registerAccount call. Consider persisting your test wallet’s private key between runs.
Registration requires a valid invitation code. Make sure you:
  1. Place the inviteCode field at the top level of the envelope — not inside the action object.
  2. Do not include inviteCode inside the signed message (it is outside action).
  3. Check whether the code was already used (ALREADY_USED) or is simply wrong/expired (NOT_FOUND).
Contact the UpsideMAX team to obtain a valid invitation code.