Conventions
Shared concepts that come up on every endpoint. Worth a skim before your first request — most pitfalls people hit are right here.
Amounts are in micro units
Every amount is in millionths of the natural unit — no floats, no decimals, no rounding ambiguity.
| Field | Means |
|---|---|
price_micro | Millionths of one collateral unit per share. "600000" = 0.60 collateral units |
shares_micro | Millionths of a share. "1000000" = 1 share |
collateral_amount_micro | Millionths of the configured collateral. "1000000" = 1 collateral unit |
fee_micro | Millionths of the configured collateral |
On the wire, micro amounts are JSON strings, not numbers. They hold 64-bit integers that don't fit in JavaScript's safe-integer range (2^53), and silently lose precision if encoded as JSON numbers.
- Responses always return strings:
"price_micro": "600000". - Requests accept both:
"price_micro": "600000"and"price_micro": 600000are both valid. String is recommended; pass number only if you're sure your client doesn't lose precision.
To convert between micro and human units in Python:
One REST exception: the orderbook endpoint returns prices and sizes as
floats in human units (0.60, 1.0) because its main consumer is a
chart renderer. WebSocket streams instead use integer engine units and carry
price_scale / size_scale on each data frame.
Limit orders and market buys must be between 0.10 and 100,000 collateral units. Market sells must be between 1 and 100,000 shares — see Order size limits.
Taker fees
When you take liquidity (your order crosses the spread), you pay a
small fee in collateral. The fee is automatically reserved when you place a
BUY, so your balance has to cover the order's notional plus fee
headroom. If your balance is exactly the notional with nothing extra,
the order rejects with insufficient_balance.
Maker orders (resting on the book) pay no fee.
Fee amounts are returned in fee_micro on fills and trades.
Identifiers
Three identifiers you'll work with:
| Field | What it is | Example |
|---|---|---|
token_id | The market outcome you're trading. Discover via Markets. | "21742633143463906290569050155826241533067272736897614950488156847949938836455" |
order_id | Your order's UUID — use this to look up or cancel. | "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b" |
condition_id | The underlying market (one market has two outcomes, both share a condition). Also from Markets. | "0x21742633…" |
The order_id is what we return from POST /trade/v1/orders and what you
pass to DELETE /trade/v1/orders/{order_id} later. Store it on your side.
Timestamps
- In responses: ISO-8601 UTC strings —
"2026-05-12T10:23:45.678Z". - In requests (e.g.
expiration_unix_seconds): Unix seconds as an integer.
Pagination
List your orders,
open orders,
trades, and activities
page with opaque keyset cursors. The list endpoints take limit and
cursor in the request body; trades and activities take them as query
parameters. Leave cursor off for the first page:
The response carries a next_cursor inside a pagination object. To
fetch the next page, pass it back as cursor:
When next_cursor comes back null, you've reached the end. Treat
the cursor as opaque — don't parse or build one yourself, just echo
it back. A cursor is tied to the request it came from, so keep its
filters the same as you page; change them and start over from
the first page. Default limit is 500 (50 for activities), max 500.
Trades report unavailable_exchanges when AGARA rows could not be loaded for
that page. Retry later to fill the gap.
Positions are the exception: they return your complete current holdings in one response, with no paging to follow. This is intentional — you always get the whole set, never a page of it.
The public markets API (/api/v1, see Markets)
pages the same way, with its own cursor / next_cursor. There too a
cursor is tied to the filters and sort that produced it; reuse it with
different filters or a different sort and the request fails with 400
— drop the cursor and start again from the first page.
Error envelope
Trading API errors return JSON with one field. The HTTP status tells you the
class; the message is for you to read. Public /api/v1 validation errors may
also include structured details.
| Status | What it means | Retry? |
|---|---|---|
400 | Your request is malformed, has a bad value, or failed validation — e.g. an unknown order token, an amount outside its allowed range, a bad time-in-force / post-only combination, or an order that isn't yours | Fix the request |
401 | Token missing, invalid, expired, or revoked | Use a fresh token |
403 | Your token doesn't have the required scope | Create a token with the right scopes |
404 | A resource on that endpoint was not found — for example an order hash or orderbook token | Check the id |
409 | A pre-signed order with the same hash already exists | Don't resubmit |
422 | A well-formed request was rejected against live state — on POST /trade/v1/orders (and the signed variants), your order can't be placed right now: not enough balance or shares, a post-only order that would cross, or a FOK that can't fully fill. Also covers an invalid withdrawal amount/destination. The error field carries the specific reason | Fix and retry |
424 | Your wallet setup or signer authorization is incomplete | Finish setup, then retry |
502, 503 | We're having a problem — try again | Yes, with backoff |
Backoff for 5xx: start at 50 ms, double up to ~2 s, give up after ~4 retries. 4xx errors won't get better by retrying.
Retries on POST /trade/v1/orders. There is currently no server-side
deduplication — a request that fails mid-flight (network timeout,
TLS reset, etc.) and is retried will land twice and create two
orders. Until idempotency keys ship, the safe pattern is:
- After a network failure on a write, don't retry blindly.
- Call
POST /trade/v1/orders/listto see whether your order made it through (filter by recent timestamp + side + price). - Only resubmit if you can't find a match.
Order status
Your order moves through these states. The terminal ones are bold — once your order reaches one, it won't change again.
POST /trade/v1/orders returns immediately with PENDING — that
just means "we got it." Poll GET /trade/v1/orders/{order_id} or read recent
fills from GET /trade/v1/portfolio/trades to watch progress.
A given order ends in exactly one of the five terminal states. Stop
polling at that point — once it's MATCHED, the on-chain settlement of
the resulting trades shows up under GET /trade/v1/portfolio/trades, not on the
order.