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 byPOST /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
retryAfterSecthen retry. - 5xx — exponential backoff with jitter; 3 attempts max for
idempotent calls. Non-idempotent writes use
clientOrderId/noncedeadlinefor 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:
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_mismatch —
VaultFactory.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/sign → POST /api/me/vault/deploy/submit.
The HTTP status differs by surface:
POST /api/orders/placereturns 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_mismatchas 401 (UnauthorizedException).
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:
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):
SELL.makerAmount is the exact
outcome-token quantity (qtyWei), no rounding involved. Only BUY
hits this gate.