Orders
Place, list, look up, and cancel orders.
API order placement
For API trading, use pre-signed orders.
Server-side signing may slow order ingestion. Follow the
one-time setup and grant
orders:place_signed for signed single-order and batch submissions.
Place an order
This section documents server-signed placement. For API trading, use the signed single-order endpoint or signed batch endpoint.
POST /trade/v1/orders
Scope: orders:place
Request
{
"token_id": "21742633143463906290569050155826241533067272736897614950488156847949938836455",
"side": "BUY",
"type": "LIMIT",
"time_in_force": "GTC",
"price_micro": "600000",
"shares_micro": "1000000",
"post_only": false
}| Field | Type | Required | Notes |
|---|---|---|---|
token_id | string | yes | Market outcome — discover via Markets |
side | BUY | SELL | yes | |
type | LIMIT | MARKET | yes | MARKET requires FAK or FOK time-in-force; GTC/GTD reject |
time_in_force | GTC | FAK | FOK | GTD | yes | See time-in-force |
price_micro | string | for LIMIT | Limit price in millionths of one collateral unit per share. Number also accepted; see Conventions |
shares_micro | string | depends | Required for limit BUYs and every SELL; forbidden for market BUYs. Number also accepted |
collateral_amount_micro | string | depends | Required for market BUYs; forbidden for limit BUYs and every SELL. Number also accepted |
post_only | bool | no | Reject if the order would take liquidity. See post-only |
expiration_unix_seconds | integer | for GTD | When the order auto-expires |
Every field, response shape, and status code for these endpoints is in the Orders API reference.
The order shape determines the amount field. A limit order uses
shares_micro; a market BUY uses collateral_amount_micro; a market SELL uses
shares_micro. Limit orders require price_micro, while market orders forbid
it.
Response
{
"order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"source": "AGARA",
"status": "PENDING",
"pending_operation": "SUBMIT",
"as_of": "2026-05-12T10:23:45.678Z"
}HTTP 202 — this is the accepted PENDING snapshot. The order may already
have advanced or matched before the response arrives. Poll
GET /trade/v1/orders/{order_id} or consume the account stream for current
status.
Errors
Every failure is an HTTP Problem Details body. The most useful placement codes are:
| Status and code | Action |
|---|---|
400 malformed_json | Send valid JSON |
413 request_body_too_large | Reduce the request body |
415 unsupported_media_type | Send Content-Type: application/json |
422 validation_failed | Correct the order shape, tick, time-in-force, or whether an expiration is set |
422 order_value_below_minimum | Increase the order to at least 0.10 collateral units |
422 order_value_above_maximum | Reduce the order to at most 100,000 collateral units |
422 order_size_below_minimum | Increase the market sell to at least 1 share |
422 order_size_above_maximum | Reduce the market sell to at most 100,000 shares |
422 order_price_out_of_range | Send a price_micro above 0 and below 1,000,000 |
422 order_expiration_too_soon | Move the GTD expiration at least 30 seconds out |
422 market_closed | Stop quoting this market |
422 insufficient_balance | Reconcile collateral and open BUY commitments, then reduce size |
422 insufficient_shares | Reconcile positions and open SELL commitments, then reduce size |
422 post_only_would_cross | Refresh the book and choose a maker price |
422 order_not_fillable | Refresh depth and resize or reprice the immediate order |
404 wallet_not_registered | Register the AGARA wallet before trading |
424 wallet_setup_incomplete | Finish wallet setup |
424 signer_authorization_required | Complete signer authorization |
The 422 check is best-effort and runs before we accept the order, so an order
that clearly can't fill is rejected immediately instead of after the fact. It's
not a guarantee: your balance or the book can move between the check and the
match, so an order can still be accepted (202 / PENDING) and then land as a
FAILED or REJECTED order with the reason in its failure field. When that
happens you'll also get an
order_rejected event on the account
stream — or poll GET /trade/v1/orders/{id} until the
status is terminal.
Examples
curl -X POST "$AGARA_BASE_URL/trade/v1/orders" \
-H "Authorization: Bearer $AGARA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"token_id": "2174…36455",
"side": "BUY",
"type": "LIMIT",
"time_in_force": "GTC",
"price_micro": "600000",
"shares_micro": "1000000"
}'import os, requests
resp = requests.post(
f"{os.environ['AGARA_BASE_URL']}/trade/v1/orders",
headers={"Authorization": f"Bearer {token}"},
json={
"token_id": token_id,
"side": "BUY",
"type": "LIMIT",
"time_in_force": "GTC",
"price_micro": str(int(0.60 * 1_000_000)),
"shares_micro": str(1 * 1_000_000),
},
)
resp.raise_for_status()
order_id = resp.json()["order_id"]List your orders
POST /trade/v1/orders/list
Scope: orders:read
Request
{ "limit": 100 }Both limit and cursor are optional; omit cursor for the first
page. See pagination.
Response
{
"orders": [ /* see "Look up one order" below */ ],
"markets": { /* token_id → market metadata; see "Look up one order" */ },
"pagination": {
"next_cursor": "eyJ2IjoxLCJyIjoib3JkZXJzL2xpc3Qi…",
"limit": 100
},
"as_of": "2026-05-12T10:23:45.678Z"
}Orders are sorted newest-first. Both open and terminal orders come
back — filter on status if you only want one or the other. To walk
the full list, pass pagination.next_cursor back as cursor until it
comes back null.
Look up one order
GET /trade/v1/orders/{order_id}
Scope: orders:read
Response
{
"order": {
"internal_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"exchange": "AGARA",
"token_id": "2174…36455",
"condition_id": "0x2174…",
"side": "BUY",
"type": "LIMIT",
"price_micro": "600000",
"original_size_micro": "1000000",
"collateral_amount_micro": "600000",
"size_matched_micro": "400000",
"avg_fill_price_micro": "595000",
"status": "PARTIALLY_FILLED",
"is_terminal": false,
"failure": null,
"expiration": "2026-05-12T10:53:45.678Z",
"created_at": "2026-05-12T10:23:45.678Z",
"cancel_requested_at": null
},
"markets": {
"2174…36455": {
"market_id": "1f9e566d-7823-49fb-a5c5-5a193eba3209",
"market_title": "Will it rain in NYC on May 12?",
"outcome_name": "Yes",
"logo_url": null,
"event_slug": "nyc-weather-may-12",
"state": "ACTIVE",
"neg_risk_id": null,
"display": { "market": {}, "outcome": {} }
}
}
}Both this endpoint and the list endpoint return a markets sidecar: a
map keyed by token_id carrying the market_id, market_title,
outcome_name, logo_url, event_slug, state, and neg_risk_id for
each order's market, so you can label and link orders without a second
lookup. state is where the market is in its lifecycle — a market past
ACTIVE no longer takes orders.
| Field | What it tells you |
|---|---|
internal_id | The order's UUID — pass it to GET /trade/v1/orders/{order_id} |
exchange | The exchange the order routed to; these docs cover AGARA |
type | LIMIT or MARKET |
original_size_micro | The size you placed, in micro units. null for market BUYs (which are sized in collateral) |
size_matched_micro | Shares matched so far, in micro units |
avg_fill_price_micro | Volume-weighted average price of the fills so far. null before the first fill |
status | The order result. See status |
is_terminal | Whether matching has finished. Completed FAK partial fills have status: "PARTIALLY_FILLED" and is_terminal: true; resting partial fills have is_terminal: false |
failure | Typed terminal failure for a FAILED or REJECTED order; null otherwise |
expiration | Expiration timestamp — meaningful for GTD orders |
cancel_requested_at | When a durable cancellation request was recorded; null if none |
Errors
| Status | Reason |
|---|---|
400 invalid_path_parameter | order_id is not a UUID |
404 order_not_found | No visible order has that ID |
Look up an order by hash
GET /trade/v1/orders/by-hash/{order_hash}
Scope: orders:read
For signed orders you know the
order_hash before you submit, so this is how you find an order when
you have its hash but not its order_id — you missed the placement
response, you got a 409 resubmitting, or you're reconciling after a
reconnect. Returns the same order object as
Look up one order.
Errors
| Status | Reason |
|---|---|
404 order_not_found | No visible order has that hash |
List an order's fills
GET /trade/v1/orders/{order_id}/trades
Scope: orders:read
Every fill for one order, newest-first, returned in full — there's no
pagination here, since a single order rarely has many fills. Each row
is your order's own side of the fill: an order can rest as the maker
on some fills and cross as the taker on others, so role is
per-fill, and side, price_micro, and fee_micro are that leg's
values.
Response
{
"trades": [
{
"exchange": "AGARA",
"trade_id": "5f1c8b20-7e3a-4d61-9c0f-1a2b3c4d5e6f",
"fill_id": "4187342",
"order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"token_id": "2174…36455",
"side": "BUY",
"shares_micro": "300000",
"price_micro": "596000",
"fee_micro": "1430",
"role": "TAKER",
"status": "MATCHED",
"transaction_hash": null,
"executed_at": "2026-05-12T10:24:01.123Z"
},
{
"exchange": "AGARA",
"trade_id": "8a2d4f10-3b6c-4e90-8d12-0f9e8a7b6c5d",
"fill_id": "4187280",
"order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"token_id": "2174…36455",
"side": "BUY",
"shares_micro": "100000",
"price_micro": "592000",
"fee_micro": "0",
"role": "MAKER",
"status": "MATCHED",
"transaction_hash": null,
"executed_at": "2026-05-12T10:23:58.004Z"
}
],
"as_of": "2026-05-12T10:24:05.000Z"
}Each row is the shared Fill shape, so it carries the
same fields as /trade/v1/portfolio/trades.
| Field | What it tells you |
|---|---|
role | Whether your order was the MAKER or TAKER on this fill |
side | Your order's side on this fill — BUY or SELL |
order_id | The order this fill belongs to (the one you looked up) |
price_micro | The price your order filled at on this fill |
fee_micro | Fee charged to your order for this fill |
status | Settlement state of this fill |
transaction_hash | On-chain settlement tx; null until settled (and currently always null on this endpoint — use /trade/v1/portfolio/trades for the hash) |
fill_id | Stable id for this fill — matches the fill_id on the live trade stream |
For fills across your whole account rather than one order, use
GET /trade/v1/portfolio/trades.
Errors
| Status | Reason |
|---|---|
400 invalid_path_parameter | order_id is not a UUID |
404 order_not_found | No visible order has that ID |
Cancel an order
DELETE /trade/v1/orders/{order_id}
Scope: orders:cancel
Response
The status code tells you whether the cancel has already finished.
200: the order is cancelled. Either it was cancelled while it was still
PENDING, or it was already cancelled. status matches what a GET of the
order reports: CANCELLED, or PARTIALLY_FILLED for a FAK order whose unfilled
remainder was cancelled after a fill. This request sends nothing to the venue,
so it produces no account stream event of its own. An
order cancelled while PENDING never produces order_accepted or
order_cancelled. If an earlier cancel removed the order from the book, the
order's earlier events, including fills and that cancel's order_cancelled,
can still arrive after this response.
This CANCELLED is not final in two cases, both where the order had already
finished executing when an earlier cancel reached it:
- If it filled completely, it changes to
MATCHEDonce its fills appear on it. - If it was a FAK order that filled only in part, it changes to
PARTIALLY_FILLED, which can happen after its fills appear. A FAK order with fills never endsCANCELLED, so wait forPARTIALLY_FILLEDorMATCHED.
Treat the status as provisional until the order reflects every fill and
order_cancelled you received: each fill_id appears in
GET /trade/v1/orders/{order_id}/trades, and GET /trade/v1/orders/{order_id}
reflects them. A fill can still arrive after that, so if a late fill is
plausible, follow the cancel-and-replace steps under
Common patterns.
{
"order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"status": "CANCELLED",
"pending_operation": "NONE",
"as_of": "2026-05-12T10:25:00.000Z"
}202: the cancel is on its way to the venue. If the order was still being
submitted when your cancel arrived, the cancel is sent as soon as the venue
acknowledges it. status is the order's status when your request was handled.
Watch for order_cancelled on the
account stream. Poll the order until it reaches any terminal status. A fill can
beat the cancel to the venue, so reconcile fills and inventory instead of
expecting the final status to be CANCELLED. An order reported CANCELLED can
still change to MATCHED if fills that beat the cancel filled it completely,
or to PARTIALLY_FILLED if it was a FAK order that filled only in part. Treat
its status as provisional until the order reflects every fill and
order_cancelled you received, and follow the cancel-and-replace steps under
Common patterns if a late fill is still plausible.
{
"order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
"status": "OPEN",
"pending_operation": "CANCEL",
"as_of": "2026-05-12T10:25:00.000Z"
}Errors
| Status | Reason |
|---|---|
400 invalid_path_parameter | order_id is not a UUID |
404 order_not_found | No visible order has that ID |
409 order_not_cancellable | The order already reached another terminal status, such as MATCHED, REJECTED or FAILED, possibly while your cancel was being handled |
Cancel everything open
POST /trade/v1/orders/cancel-all
Scope: orders:cancel_all
Schedules a cancellation sweep for each wallet on your account. No request body.
Response
{
"wallet_ids": ["e7a1…"],
"pending_operation": "CANCEL_ALL",
"as_of": "2026-05-12T10:25:00.000Z"
}Cancellations continue after the response. Don't wait for one
order_cancelled event per order. An
order that is still PENDING is cancelled without one. An order that is
already SUBMITTING is cancelled only if it rests on the book, so it can still
fill first, or end REJECTED or FAILED; reconcile its fills and the status it
reaches. As with a single cancel, a CANCELLED status is provisional: an order
that filled just before cancel-all reached it can still change to MATCHED, so
follow the late-fill guidance in Cancel an order before you
resume trading.
Treat the response's as_of as a conservative
reconciliation upper bound, then poll POST /trade/v1/orders/list and follow
every cursor to the end. Confirm every order sent no later than that time is
reported with is_terminal: true, including completed FAK partial fills.
Each wallet's cancellation sweep can begin before the response
timestamp, so as_of is not the exact cutoff. Verify each candidate order
through the full order list rather than inferring completion from one
active-order snapshot.
Cancel-all is not a fence for a placement still completing outside its cutoff. If a late order appears, cancel it directly or repeat cancel-all, then repeat the fully paginated order check before resuming.
Errors
| Status | Reason |
|---|---|
403 forbidden_scope | Add orders:cancel_all to the token |
404 wallet_not_registered | Register the AGARA wallet |
Time in force
| Value | Behavior |
|---|---|
GTC | Good-til-cancelled. Rests on the book until filled or cancelled |
GTD | Good-til-date. Same as GTC, but cancelled after expiration_unix_seconds, not at that exact time, so it can still fill until it is removed. The expiration must be at least 30 seconds in the future. See GTD for orders still being placed when they expire |
FAK | Fill-and-kill. Matches whatever's available immediately, cancels any remainder |
FOK | Fill-or-kill. Either fully fills right now or doesn't place at all |
Post-only
Set post_only: true if your strategy depends on resting on the book
(maker side) rather than taking liquidity. If your order would cross
the spread, it's rejected and nothing rests. Most of the time this comes back
immediately as 422 post_only_would_cross; if the book moves after that check,
it can instead land as
a REJECTED order (an order_rejected
event, or poll GET /trade/v1/orders/{id}).
Use this when your strategy must never take liquidity or pay a taker fee.
Crossing another account's complementary order can also take liquidity. An eligible cross against only your own order can cancel the older order through self-trade prevention. See Cross-orderbook matching and Self-trade prevention.
Order size limits
While we're in beta, every order is bounded on entry. Limit orders and market buys are bounded by notional in collateral units; market sells are bounded by share count, because the fill price isn't known at placement.
| Order | Bounded by | Min | Max | Code under | Code over |
|---|---|---|---|---|---|
| Limit (buy or sell) | notional (price × shares) | 0.10 collateral units | 100,000 collateral units | order_value_below_minimum | order_value_above_maximum |
| Market buy | notional (the collateral_amount you supply) | 0.10 collateral units | 100,000 collateral units | order_value_below_minimum | order_value_above_maximum |
| Market sell | share count | 1.0 share | 100,000 shares | order_size_below_minimum | order_size_above_maximum |
A limit price_micro must also sit above 0 and below 1,000,000, and a GTD
expiration_unix_seconds must be at least 30 seconds out; those reject with
order_price_out_of_range and order_expiration_too_soon.
Every bound rejects before the order reaches the matching engine, and each one
has its own code above. Do not parse detail; validate the documented bounds
locally and branch on the stable code.
Validate these bounds before submitting. Track this page for changes.
Common patterns
Place and wait for terminal status:
order_id = place_order(...)
while True:
order = get_order(order_id)
if order["is_terminal"]:
break
time.sleep(0.5)Replace a resting bid (no atomic replace — cancel, reconcile, then re-place):
cancel(old_order_id)
wait_until_terminal(old_order_id)
observed_fill_ids = drain_account_fill_events(old_order_id)
old_order, fills, inventory = reconcile_until_observed_fills_are_durable(
old_order_id,
observed_fill_ids,
)
if stream_had_a_gap() or late_fill_is_still_plausible():
keep_market_paused_and_run_full_recovery()
replacement = recompute_quote_and_size(old_order, fills, inventory)
new_order_id = place_order(replacement)The old order can fill while cancellation is in flight. A terminal status plus
one fills read is not a fence: an observed fill can reach the account stream
before REST, and another fill can be projected after cancellation. Wait for
every observed fill_id to appear, then refresh the order, its fills, the fully
paginated order list, positions, summary, and portfolio open orders with
backoff. Do not size the replacement from positions while
unavailable_exchanges includes AGARA. If a stream gap or late-fill
ambiguity remains, stay paused and follow the full
API trading recovery procedure instead of
placing the replacement.
Take only, never rest: use time_in_force: "FAK" — any
unfilled portion vanishes instead of resting.
Maker only: post_only: true. If the order would cross, it's
normally rejected immediately with 422. If the book changes after that
check, the accepted order may instead become REJECTED; back off and re-quote.