Documentation Index
Fetch the complete documentation index at: https://docs.adipredictstreet.com/llms.txt
Use this file to discover all available pages before exploring further.
Authentication
| Code | HTTP | Meaning |
|---|
auth_required | 401 | X-Api-Key header missing on an authenticated endpoint |
api_key_bad_format | 401 | X-Api-Key header didn’t match ps_<env>_<keyId>_<secret> shape |
api_key_unknown_key | 401 | keyId not found in the registry (typo, wrong environment, or revoked long ago and garbage-collected) |
api_key_bad_secret | 401 | Stored hash didn’t match — wrong secret |
api_key_revoked | 401 | Key was explicitly revoked via admin |
api_key_expired | 401 | Key’s expiresAt has passed |
api_key_suspended | 401 | Partner was suspended — all their keys stop working until reactivation |
api_key_ip_denied | 401 | Caller IP not in the key’s ipAllowlist |
api_key_no_associated_wallet | 401 | single_wallet partner has no associatedWallet attached — admin must attach one before authenticated endpoints work |
api_key_user_wallet_required | 401 | multi_wallet partner: X-User-Wallet header missing on an authenticated request |
api_key_user_wallet_invalid | 401 | multi_wallet partner: X-User-Wallet header value isn’t a 0x + 40-hex address |
api_key_scope_missing | 403 | Key lacks the scope required by this endpoint; response body includes requiredScope + have[] |
wallet_banned | 403 | The request’s effective wallet (associated or X-User-Wallet) is on the banned list |
Trading
| Code | HTTP | Meaning |
|---|
invalid_amounts | 400 | price or quantity ≤ 0 or bad format |
invalid_tif | 400 | MARKET+GTC rejected |
bad_signature | 400 | EIP-712 recover failed (see diagnostic hints below) |
order_signed_with_floor_notional | 400 | BUY.makerAmount was FLOOR-rounded; CEIL is required to keep matcher math from overflowing the maker cap on chain. details carries signedMakerAmountWei + expectedCeilMakerAmountWei — re-sign with the latter. See diagnostic hints below. |
fee_too_high | 400 | feeRateBps > MAX_FEE_RATE_BIPS (1000) |
expired | 400 | Order expiration in the past |
invalid_outcome | 400 | Outcome index out of range |
maker_mismatch | 400 | Order.maker doesn’t equal the authenticated wallet OR its bound vault — for multi_wallet partners using X-User-Wallet, maker MUST be the sub-account’s wallet (or the vault auto-deployed for it). See diagnostic hints below |
wallet_mismatch | 401 | Legacy alias of maker_mismatch still surfaced by some withdrawal / vault / position endpoints — same semantics. Treat identically; we are converging the order surface on maker_mismatch and the rest of the API will follow |
insufficient_funds | 200 (envelope) | Balance overdraft |
market_not_open | 409 | Market status ≠ OPEN |
idempotency_race | 500 | Internal race on (wallet, clientOrderId) |
matcher_error | 200 (envelope) | Matcher returned a business error |
exchange_unavailable | 503 | Exchange-service / matcher unreachable |
Orders
| Code | HTTP | Meaning |
|---|
order_not_found | 404 | Order ID doesn’t exist for this wallet |
not_cancellable | 409 | Order already terminal |
forbidden | 403 | Order belongs to a different wallet |
Withdrawals
| Code | HTTP | Meaning |
|---|
invalid_amount | 400 | Amount ≤ 0 |
bad_signature | 400 | User EIP-712 signature invalid |
destination_not_cleared | 200 (envelope) | New destination below EDD threshold |
new_destination_edd | 200 (envelope) | New destination above EDD → MLRO review |
wallet_banned | 200 (envelope) | Destination on banned-wallets list |
aml_blocked | 200 (envelope) | Destination failed AML screen |
invalid_state | 409 | Tried to cancel after SUBMITTED |
withdrawal_not_found | 404 | Withdrawal ID doesn’t exist (or doesn’t belong to the authenticated wallet). Same code on GET /api/me/withdrawals/{id} and POST /api/me/withdrawals/{id}/cancel. |
Rate limits
| Code | HTTP | Meaning |
|---|
rate_limited | 429 | Bucket exhausted; retry after retryAfterSec |
Service-level
| Code | HTTP | Meaning |
|---|
exchange_unavailable | 503 | Upstream service temporarily unavailable — retry with backoff |
exchange_transport_error | 503 | Connection failed |
exchange_malformed_response | 503 | Non-JSON response |
not_implemented | 501 | Feature disabled in this env |
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:
{
"code": "bad_signature",
"message": "EIP-712 signature invalid",
"details": {
"recovered": "0xRecoveredFromYourSig",
"expected_signer": "0xYourAuthenticatedWallet",
"platform_canonical_struct": {
"salt": "12345...",
"maker": "0xVaultAddr",
"signer": "0xYourEoa",
"taker": "0x0000000000000000000000000000000000000000",
"tokenId": "999...",
"makerAmount": "55000000",
"takerAmount": "100000000",
"expiration": "0",
"feeRateBps": "140",
"side": 0,
"signatureType": 1
}
}
}
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:
order.maker MUST equal EITHER
- the authenticated wallet (X-User-Wallet, lowercased), OR
- VaultFactory.vaultOf(authenticated wallet) on this chain
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_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/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:
{
"status": "REJECTED",
"code": "order_signed_with_floor_notional",
"message": "Order signed with FLOOR-rounded BUY notional (makerAmount=34999999 wei). CEIL is required to keep matcher math from overflowing your maker cap on chain — re-sign with makerAmount=ceil(price × qty / 1e6) = 35000000. See the EIP-712 signing reference for the canonical formula.",
"details": {
"signedMakerAmountWei": "34999999",
"expectedCeilMakerAmountWei": "35000000",
"signedRounding": "FLOOR",
"expectedRounding": "CEIL"
}
}
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):
const product = priceWei * qtyWei;
const notionalWei = (product + 999_999n) / 1_000_000n; // CEIL
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 unaffected — SELL.makerAmount is the exact
outcome-token quantity (qtyWei), no rounding involved. Only BUY
hits this gate.