When to Use
Use this skill when a bot authenticates against OKX v5 private endpoints, sizes or submits orders on a Unified Account, or monitors cross-margin liquidation risk between balance polls. It covers four surfaces that are easy to get subtly wrong:
- Signing — the
OK-ACCESS-SIGNprehash istimestamp + METHOD + requestPath + body, HMAC-SHA256 over the raw secret key, Base64-encoded. GET query parameters are part ofrequestPath, not the body. - Environment separation — OKX demo trading is the
x-simulated-tradingheader plus a demo-specific API key, not a separate host. The header and the key can be mismatched independently, so both are made explicit here. - Margin arithmetic — in multi-currency margin mode (
acctLv = 3) collateral is haircut by a tiered discount rate applied marginally by currency amount, and the maintenance margin ratio drives OKX's 300% risk alert and 100% liquidation. - Order payloads —
clOrdIdis the only idempotency handle OKX offers,pxapplies to a specific subset of order types, andszmeans contracts for derivatives.
When NOT to Use
- As a replacement for
GET /api/v5/account/balance. OKX liquidates against its ownadjEqandmgnRatio. This engine is a local approximation for pre-trade gating and between-poll alerting; when the two disagree, OKX wins. - For portfolio margin mode (
acctLv = 4). Portfolio margin computes maintenance margin per risk unit with offsets across instruments; the linear model here does not represent it and will overstate available margin. - For isolated-margin risk. Isolated positions carry their own margin and are excluded from cross-margin adjusted equity — model them separately.
- As an HTTP client. No transport, no retries, no rate limiting, no order-state
reconciliation. See
order-placement-idempotencyandmulti-broker-rate-limit-handling. - For OKX spread trading, algo orders, or block trades. Those endpoints take
different payload shapes that
build_order_payloaddeliberately rejects.
Prerequisites
- An OKX v5 API key, secret key, and passphrase, with the trade permission and an
IP allowlist. Demo trading needs a separate demo key — a live key with
x-simulated-trading: 1fails. - Account mode set to multi-currency margin (
acctLv = 3) for the margin model to apply. - A clock synchronised to within 30 seconds of OKX server time (
GET /api/v5/public/time), or every signed request is rejected with error 50102. - Live discount tiers from
GET /api/v5/public/discount-rate-interest-free-quota. Tiers are revised periodically; a hard-coded schedule silently over-values collateral. ctVal,lotSz,minSz, andtickSzfromGET /api/v5/public/instrumentsfor every instrument traded —szis meaningless withoutctVal.- Durable storage for
clOrdIdvalues, written before the order is submitted.
Workflow
- Build the timestamp, then sign: format the timestamp as ISO 8601 UTC with
exactly three fractional digits (
2020-12-08T09:08:57.715Z). Epoch seconds or milliseconds are the single most common cause of error 50102, soparse_timestamprejects them locally rather than letting OKX do it. Signtimestamp + METHOD + requestPath + bodywith the secret key as raw UTF-8 — do not Base64-decode it first, as you would for Coinbase. - Sign the exact bytes you will send:
requestPathmust carry the query string for GETs, andbodymust be the same serialised string the HTTP client transmits. Re-serialising the dict for the wire (different key order, different separators) produces a signature over a body that was never sent, and authentication fails with no hint that the body was the problem. - State the environment explicitly: construct the engine with
simulated_trading=Trueonly alongside demo credentials. The header is emitted on every request as0or1, so a promotion from demo to live is a visible, greppable change rather than an omitted header. - Value collateral through the tier schedule, not a flat factor: discount rates are brackets on the currency amount and apply marginally. 100 BTC does not get the top-tier rate on all 100 BTC; it consumes each bracket in turn. If a holding exceeds the schedule, the engine raises rather than valuing the remainder — a stale schedule must fail loudly, not quietly inflate equity.
- Never discount a liability: a negative currency equity is a borrowing. A haircut applied to it would shrink the liability and inflate the margin ratio, which is the exact direction that hides a liquidation. Negative equity is counted at full USD magnitude.
- Compute the ratio against the right denominator: OKX's maintenance margin
ratio is
Adjusted equity / (Maintenance margin + Liquidation fees), and adjusted equity is discounted equity minus frozen assets and estimated open-order fees. Passequity_deductions_usdandliquidation_fee_usd; leaving them at zero yields an upper bound, and the report says so inwarnings. - Classify against OKX's own thresholds:
> 300%isSAFE,100% < r <= 300%isMARGIN_WARNING(OKX warns to reduce positions), and<= 100%isLIQUIDATION_RISK_CALL(OKX cancels open orders, then force-liquidates). A ratio landing exactly on a threshold belongs to the riskier bucket. - Mint and persist a
clOrdIdbefore submitting:build_order_payloadrequires one. Write it to durable storage first, then submit. On a timeout, do not resend with a fresh id — reuse the same one, or queryGET /api/v5/trade/order?clOrdId=...to discover whether the original was accepted. - Size in the instrument's own units: for FUTURES/SWAP/OPTION,
szis the number of contracts.1onBTC-USDT-SWAPis one contract ofctVal0.01 BTC, not 1 BTC. Round tolotSzand price totickSzbefore building the payload. - Attach
posSidein hedge mode: long/short position mode requiresposSideon FUTURES/SWAP orders. The builder validates the value but cannot see the account's position mode, so it cannot tell you that one was needed.
Full procedure: see
references/workflows.md. Standards reference: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Epoch timestamps in
OK-ACCESS-TIMESTAMP: OKX wants ISO 8601 UTC with milliseconds.1607418537715is rejected with 50102 — the same error a genuinely skewed clock produces, so the format bug is routinely misdiagnosed as a clock bug. Check the format first, then compare againstGET /api/v5/public/time. - Base64-decoding the secret key before signing: correct for Coinbase, wrong for OKX. The OKX secret is used as raw UTF-8 bytes. This produces a well-formed 44-character signature that is simply never valid.
- Signing a body you did not send: the signature covers the exact request-body string. Serialise once, sign that string, and post that same string.
- Dropping the query string from
requestPath: OKX counts GET parameters as part of the requestPath. Signing/api/v5/account/balanceand sending/api/v5/account/balance?ccy=BTCfails authentication. - Applying the discount rate to negative equity: multiplying a $10,000 liability by a 0.9 haircut reports it as $9,000, overstating adjusted equity and the margin ratio. The error is invisible in a healthy account and appears precisely when the account is leveraged.
- Treating the discount rate as one flat number per currency: OKX's schedule is tiered by currency amount and applied marginally. Using the first tier's rate over a large holding over-values it; using the last tier's rate under-values it. Both are wrong, and the first is the dangerous direction.
- Hard-coding a discount schedule: OKX revises tiers by announcement. A pinned table keeps returning confident numbers after the revision lands.
- Reporting discounted equity as adjusted equity: OKX subtracts assets frozen in isolated-margin and options-closing orders and estimated open-order fees, and adds liquidation fees to the denominator. Omitting both makes every reported ratio optimistic — safe to display, unsafe to gate on.
- Comparing the API's
mgnRatioagainst a 300 threshold:mgnRatiofromGET /api/v5/account/balanceis a ratio, not a percentage.2.5is 250%, deep in warning territory, and reads as safely below 300 if the units are confused. - Retrying a timed-out order without a
clOrdId: an HTTP timeout says nothing about whether OKX accepted the order. Without a stable client order ID, the retry is a second order, not a retry. - Using a hyphenated UUID as
clOrdId: OKX accepts case-sensitive alphanumerics up to 32 characters.uuid4()in its standard form is 36 characters with hyphens and is rejected;uuid4().hexis exactly 32 valid characters. - Reading
szas a base-currency quantity on derivatives:szis contracts for FUTURES/SWAP/OPTION. OnBTC-USDT-SWAP(ctVal0.01 BTC),sz=1is 0.01 BTC — and a caller who meant 1 BTC and passed1is 100× under-sized, while one who "corrected" it to 100 without checkingctValon a different instrument is 10× over. - Emitting sizes in scientific notation:
str(1e-8)is'1e-08', which OKX will not parse. Format quantities as plain fixed-point decimal strings. - Sending
pxon a market order, or omitting it on a limit order: OKX appliespxonly tolimit,post_only,fok, andioc. - Omitting
posSidein hedge mode: long/short position mode requires it on FUTURES/SWAP, and each side must be configured separately for isolated margin.
Verification
- Sign OKX's documented example request (
GET /api/v5/account/balance?ccy=BTCat2020-12-08T09:08:57.715Z) and confirm the result matches an HMAC-SHA256 Base64 value derived independently from RFC 2104 primitives, and that permuting the prehash fields changes it. - Confirm
1607418537715,2020-12-08T09:08:57Z, and2020-12-08 09:08:57.715Zall raise before a request is built, and that a relativerequestPathis refused. - Confirm
get_auth_headersemitsx-simulated-trading: 0by default and1only for an engine constructed withsimulated_trading=True. - Reproduce OKX's published worked example: 100 BTC at $60,000 under the tiers 0–20 @ 0.98, 20–25 @ 0.975, 25–30 @ 0.97, 30–50 @ 0.965, 50–70 @ 0.96, 70–90 @ 0.955, 90–110 @ 0.95 gives $5,785,500 discounted equity against $6,000,000 gross. Confirm a 200 BTC holding against the same schedule raises rather than being valued.
- Confirm a −10,000 USDT balance with
discount_factor=0.9contributes exactly −$10,000, not −$9,000. - Confirm the status ladder at the exact boundaries: 300% is
MARGIN_WARNINGand 100% isLIQUIDATION_RISK_CALL, notSAFEandMARGIN_WARNING. - Confirm a negative
maintenance_margin_usdraises instead of reportingSAFE, and that NaN or infinite equity inputs raise rather than being scored. - Confirm
build_order_payloadcannot be called withoutcl_ord_id, rejects a hyphenated UUID, rejects a limit order with no price and a market order with one, and renders1e-8as"0.00000001". - Run
python -m unittest discover -s skills/okx-unified-account-api/scriptsand confirm a 100% pass rate.
Related Skills
order-placement-idempotencysandbox-vs-production-endpoint-driftminimum-fill-size-and-lot-rounding-logicmulti-broker-rate-limit-handlingclock-drift-monitoring-alerting-thresholdsperpetual-futures-funding-rate-handlingkraken-websocket-v2-auth-and-subscriptionsbinance-futures-testnet-to-mainnet-promotion