Agara

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
}
FieldTypeRequiredNotes
token_idstringyesMarket outcome — discover via Markets
sideBUY | SELLyes
typeLIMIT | MARKETyesMARKET requires FAK or FOK time-in-force; GTC/GTD reject
time_in_forceGTC | FAK | FOK | GTDyesSee time-in-force
price_microstringfor LIMITLimit price in millionths of one collateral unit per share. Number also accepted; see Conventions
shares_microstringdependsRequired for limit BUYs and every SELL; forbidden for market BUYs. Number also accepted
collateral_amount_microstringdependsRequired for market BUYs; forbidden for limit BUYs and every SELL. Number also accepted
post_onlyboolnoReject if the order would take liquidity. See post-only
expiration_unix_secondsintegerfor GTDWhen 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 codeAction
400 malformed_jsonSend valid JSON
413 request_body_too_largeReduce the request body
415 unsupported_media_typeSend Content-Type: application/json
422 validation_failedCorrect the order shape, tick, time-in-force, or whether an expiration is set
422 order_value_below_minimumIncrease the order to at least 0.10 collateral units
422 order_value_above_maximumReduce the order to at most 100,000 collateral units
422 order_size_below_minimumIncrease the market sell to at least 1 share
422 order_size_above_maximumReduce the market sell to at most 100,000 shares
422 order_price_out_of_rangeSend a price_micro above 0 and below 1,000,000
422 order_expiration_too_soonMove the GTD expiration at least 30 seconds out
422 market_closedStop quoting this market
422 insufficient_balanceReconcile collateral and open BUY commitments, then reduce size
422 insufficient_sharesReconcile positions and open SELL commitments, then reduce size
422 post_only_would_crossRefresh the book and choose a maker price
422 order_not_fillableRefresh depth and resize or reprice the immediate order
404 wallet_not_registeredRegister the AGARA wallet before trading
424 wallet_setup_incompleteFinish wallet setup
424 signer_authorization_requiredComplete 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.

FieldWhat it tells you
internal_idThe order's UUID — pass it to GET /trade/v1/orders/{order_id}
exchangeThe exchange the order routed to; these docs cover AGARA
typeLIMIT or MARKET
original_size_microThe size you placed, in micro units. null for market BUYs (which are sized in collateral)
size_matched_microShares matched so far, in micro units
avg_fill_price_microVolume-weighted average price of the fills so far. null before the first fill
statusThe order result. See status
is_terminalWhether matching has finished. Completed FAK partial fills have status: "PARTIALLY_FILLED" and is_terminal: true; resting partial fills have is_terminal: false
failureTyped terminal failure for a FAILED or REJECTED order; null otherwise
expirationExpiration timestamp — meaningful for GTD orders
cancel_requested_atWhen a durable cancellation request was recorded; null if none

Errors

StatusReason
400 invalid_path_parameterorder_id is not a UUID
404 order_not_foundNo 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

StatusReason
404 order_not_foundNo 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.

FieldWhat it tells you
roleWhether your order was the MAKER or TAKER on this fill
sideYour order's side on this fill — BUY or SELL
order_idThe order this fill belongs to (the one you looked up)
price_microThe price your order filled at on this fill
fee_microFee charged to your order for this fill
statusSettlement state of this fill
transaction_hashOn-chain settlement tx; null until settled (and currently always null on this endpoint — use /trade/v1/portfolio/trades for the hash)
fill_idStable 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

StatusReason
400 invalid_path_parameterorder_id is not a UUID
404 order_not_foundNo 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 MATCHED once 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 ends CANCELLED, so wait for PARTIALLY_FILLED or MATCHED.

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

StatusReason
400 invalid_path_parameterorder_id is not a UUID
404 order_not_foundNo visible order has that ID
409 order_not_cancellableThe 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

StatusReason
403 forbidden_scopeAdd orders:cancel_all to the token
404 wallet_not_registeredRegister the AGARA wallet

Time in force

ValueBehavior
GTCGood-til-cancelled. Rests on the book until filled or cancelled
GTDGood-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
FAKFill-and-kill. Matches whatever's available immediately, cancels any remainder
FOKFill-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.

OrderBounded byMinMaxCode underCode over
Limit (buy or sell)notional (price × shares)0.10 collateral units100,000 collateral unitsorder_value_below_minimumorder_value_above_maximum
Market buynotional (the collateral_amount you supply)0.10 collateral units100,000 collateral unitsorder_value_below_minimumorder_value_above_maximum
Market sellshare count1.0 share100,000 sharesorder_size_below_minimumorder_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.