Skip to main content

Authentication

Trading

Orders

Crypto markets

Returned by the crypto read surfaces — GET /api/markets/{symbol}/crypto-price-history, GET /api/crypto-series/{id}/windows, and the crypto filters on GET /api/events.

Withdrawals

Withdrawal security (2FA & whitelist)

Gate codes returned by POST /api/withdrawals/request when the platform enforces withdrawal security (SSO partners exempt). See 2FA and Address whitelist. Codes returned by the management endpoints under /api/me/withdrawal-security/*:

Faucet (testnet)

Rate limits

Service-level

Handling strategy

  • 4xx codes — fix the client.
  • 200 with code — business logic reject; surface to user.
  • 429 — wait retryAfterSec then retry.
  • 5xx — exponential backoff with jitter; 3 attempts max for idempotent calls. Non-idempotent writes use clientOrderId / nonce
    • deadline for safe retries.

bad_signature diagnostic hints

When POST /api/orders/place rejects with code: bad_signature, the response body’s details block carries enough context to diff your client struct against what the platform reconstructed. This turns a 24-hour blind debug into a 30-second visual diff:
recovered ≠ expected_signer ⇒ the digest you signed differs from what the platform reconstructs. Compare your client-side struct field-by-field against platform_canonical_struct. The most common mismatch is feeRateBps: per-market value lives at market.feeTakerBps (GET /api/markets/{slug}) and MUST be signed verbatim — signing 0n while the platform reconstructs with 140 gives different digests and recovers a different address. recovered: null ⇒ signature wasn’t a valid EIP-712 sig at all (missing, malformed hex, wrong length). Re-sign and resend. Both addresses are checksum-cased — partner clients can compare with recovered === expected_signer directly. Every value in platform_canonical_struct is either user-supplied (already known to the partner) or platform-public market metadata, so no privacy concerns from logging the response.

maker_mismatch diagnostic hints

maker_mismatch (and its legacy alias wallet_mismatch on the non-order surfaces) is the platform’s authorization check that the off-chain order signer can actually instruct the on-chain custody contract that will be debited at settlement. Specifically:
Three patterns produce this rejection in the wild: 1. Signing-wallet ↔ authenticated-wallet drift. The most common. You’re signing as EOA A while authenticating as wallet B. This always fails — even when A === VaultFactory.vaultOf(B) on chain. The check is per-request: X-User-Wallet is the principal, order.maker is the counterparty in the signed payload, and the two are compared after resolving the principal’s vault. Reuse of an operator’s session to sign for another user’s vault is rejected by design — signer-of-record and authenticated-of-record are bound 1:1 even when the EOA is technically authorised on multiple vaults on-chain. Each end-user vault must be addressed under its own authenticated session. 2. Stale local vaultOf cache during the deploy/observe race. If you’ve recently rotated to vault-direct signing (order.maker == vault), make sure your local vaultOf(wallet) cache is fresh. Order placement happens during a brief window where the on-chain vault may already exist but the off-chain mirror hasn’t yet observed the VaultDeployed event. The placement handler re-reads the chain on cache miss, so the platform-side resolution is correct — but a stale value held by your client will pre-fail the check on your side before the request even reaches us. 3. Vault-not-yet-deployed. Legitimate maker_mismatchVaultFactory.vaultOf(wallet) resolves to null and only the plain-wallet form of order.maker is accepted. Deploy the vault first, then retry. The vault-deploy signedOp flow lives at POST /api/me/vault/deploy/signPOST /api/me/vault/deploy/submit. The HTTP status differs by surface:
  • POST /api/orders/place returns 400 (current order endpoint) — rejected at the EIP-712 maker resolution step before the order ever reaches the matcher.
  • Legacy withdrawal / vault / position endpoints still throw the alias wallet_mismatch as 401 (UnauthorizedException).
Treat both as the same condition. Diagnose with the three patterns above; the fix in all three cases is on the client side.

order_signed_with_floor_notional diagnostic hints

When POST /api/orders/place rejects with code: order_signed_with_floor_notional, the user signed a BUY order whose makerAmount was computed with integer FLOOR division ((priceWei * qtyWei) / 1_000_000n in JS — BigInt division truncates toward zero). For boundary tuples where price × qty doesn’t divide cleanly into 6-decimal wei, FLOOR underflows the canonical CEIL by 1 wei. Why we reject: FLOOR-signed BUY orders sit on the book with zero wei of headroom against the matcher’s per-fill CEIL math. On the closing tail fill of a multi-leg cross-outcome batch, the cumulative maker_fill_amount_wei overshoots the signed makerAmount by 1 wei → chain reverts MakingGtRemaining (selector 0xe2cc6ad6) → the TAKER absorbing this order (a different user, not the FLOOR signer) sees the failure. Rejecting at placement prevents the poison from reaching the book at all. Response envelope shape:
Fix: patch the makerAmount to details.expectedCeilMakerAmountWei and re-sign + re-submit. The SDK shouldn’t need to recompute the formula client-side — details carries the exact integer to use. Canonical CEIL formula (TypeScript / Python identical semantics):
The +1 wei vs FLOOR is sub-cent and economically meaningless, but the chain math cares about every wei. See Rounding rule section for the full asymmetric CEIL-BUY / FLOOR-SELL story. SELL orders are unaffectedSELL.makerAmount is the exact outcome-token quantity (qtyWei), no rounding involved. Only BUY hits this gate.