Notation
Number encodings (E6 vs decimal strings), symbol grammar, timestamps, casing rules and paging caps.
Three things trip up every new integration: which number encoding a field uses, how symbols are spelled, and which request bodies are snake_case. This page is the whole answer.
Numbers
The API uses two encodings, split by venue. Both are exact — neither is a float.
| Venue | Encoding | Example | Reads as |
|---|---|---|---|
| Perpetuals | decimal string | "price": "86145.55" | 86145.55 USDC |
| Options, prediction | E6 integer (value × 10⁶) | "priceE6": 13150000 | 13.15 USDC |
Any field ending …RateE6 | E6 fraction | "makerFeeRateE6": 200 | 0.0002 = 2 bps |
Any field ending …Bps | basis points | "settlementFeeCapBps": 1250 | 12.50% |
E6 conversion is round(value * 1_000_000) — always integer-valued on the wire, never 1.315e7, never a string.
def to_e6(x: float) -> int: return int(round(x * 1_000_000))
def from_e6(n: int) -> float: return n / 1_000_000Three E6 fields people get wrong:
strikePriceE6is the strike × 10⁶, not the strike. A BTC 85,000 call isstrikePriceE6: 85000000000; an AAOI 93 call is93000000.priceE6must be a whole multiple of the contract'stickSizeE6(options), and a whole multiple of1000on prediction markets. A price that is not on-tick is rejected, not rounded.qtyE6is in contracts × 10⁶, so one contract is1000000.lotSizeE6andminOrderSizeE6are in the same unit.
On perpetuals, size, price and leverage accept either a JSON number or a numeric string — 10 and "10" are equivalent. Responses always come back as strings.
Symbols
| Kind | Grammar | Example |
|---|---|---|
| Perp market | <BASE>-USDC | BTC-USDC, AAPL-USDC, GOLD-USDC |
| Perp alias | <BASE>-PERP (accepted by POST /public/market/config) | BTC-PERP |
| Options contract | <TICKER>-<YYYYMMDD>-<STRIKE>-<C|P> | AAOI-20260925-93-C |
| Options underlying | the perp-style symbol | "underlying": "AAOI-USDC" |
The options ticker segment carries no -USDC suffix, the date is the UTC expiry day, and the strike is written in whole quote units (93 = $93). The underlying field on the same object does carry the suffix — this asymmetry is deliberate: the contract symbol names the option, the underlying names the market you would hedge it in.
Enumerate rather than construct symbols:
curl -s https://api-mainnet.1024ex.com/api/v1/system/info # perp markets
curl -s https://api-mainnet.1024ex.com/api/v1/options/underlyings # options underlyings
curl -s "https://api-mainnet.1024ex.com/api/v1/options/expiries?underlying=BTC-USDC"Time
Every timestamp is UTC milliseconds since epoch, as an integer: 1790094672927. Date-only query parameters (?expiry=) are YYYY-MM-DD in UTC. A handful of legacy fields carry seconds precision (fund createdAt, navHistory[].timestamp); they are deprecated aliases and each has a …Ms twin — prefer the twin.
Check server time before signing if your host clock is suspect — the signature window is ±30 s:
curl -s https://api-mainnet.1024ex.com/api/v1/system/timeCasing
Responses are camelCase, everywhere, without exception.
Request bodies are camelCase too — except three perp bodies whose canonical fields are snake_case for historical reasons:
| Endpoint | Canonical fields |
|---|---|
PUT /api/v1/perp/positions/{market}/leverage | leverage, position_side |
POST / PUT /api/v1/perp/positions/{market}/tpsl | take_profit_price, stop_loss_price, position_side, replace |
DELETE /api/v1/perp/orders/batch | order_ids, market, side |
camelCase aliases (takeProfitPrice, positionSide, orderIds, …) are now accepted on all three, but snake_case is what the reference documents and what is guaranteed. This matters more than it looks: JSON bodies are deserialized with unknown keys ignored, so a misspelled field does not 400 — it returns 200 having done nothing. If a call reports success but changes nothing, check the field names first, then re-read state with the matching GET.
Query parameters are ignored the same way
The same rule applies to the query string, and one pair of sibling endpoints
disagrees on which spelling is the real one:
| Endpoint | Filters on | Silently ignores |
|---|---|---|
GET /perp/markets/{market}/funding-history | start_time / end_time | startTime / endTime |
GET /perp/funding-history?market= | startTime / endTime | start_time / end_time |
Measured on mainnet: asking either endpoint for a window in 2020 returns an
empty list with the spelling it understands, and the most recent rows —
unfiltered, with a 200 — with the other one. If you compute anything over a
time window, assert that the rows you got back are inside the window you asked
for.
Sides and directions
side is "buy" or "sell", case-insensitive, and accepts "long" / "short" as aliases. positionSide (hedge mode) is "long" or "short" and means something different — which of the two positions on that market you are acting on. In one-way mode, omit it.
Paging
History endpoints take limit plus a cursor returned by the previous page; market-data endpoints take hard-capped limit / depth:
| Endpoint | Cap |
|---|---|
GET /perp/markets/{market}/klines | limit 1–1000 (default 500), interval one of 1m 5m 15m 1h 4h 1d |
GET /perp/markets/{market}/orderbook | depth 1–100 (default 20) |
GET /perp/orders/history | limit + cursor, plus market / side / status / start_time / end_time |
Never infer "no more data" from a short page on a capped endpoint — follow the cursor until it comes back empty.
Caps are applied silently. Asking klines for limit=5000 returns 200
with 1000 rows, not a 400. Asking the book for depth=100 on a market quoted
20 levels deep returns 20 levels. Neither is an error, so compare what you got
against what you asked for rather than trusting the request.
GET /perp/markets has no paging at all — limit is accepted and ignored,
and the full list (218 markets today) comes back every time. Filter client-side.
Updated 8 days ago
