# Orders

> Place, list, look up, and cancel orders.

<Callout type="info" title="API order placement">
For API trading, use [pre-signed orders](/docs/api-traders/pre-signed-orders).
Server-side signing may slow order ingestion. Follow the
[one-time setup](/docs/api-traders/pre-signed-orders#one-time-setup) and grant
`orders:place_signed` for signed single-order and batch submissions.
</Callout>

## Place an order [#place-an-order]

This section documents server-signed placement. For API trading, use the
[signed single-order endpoint](/docs/api-traders/pre-signed-orders#sending-an-order)
or [signed batch endpoint](/docs/api-traders/pre-signed-orders#sending-many-orders-at-once).

`POST /trade/v1/orders`

**Scope:** `orders:place`

### Request [#request]

```json
{
  "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](/docs/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](#time-in-force)                                                                                                               |
| `price_micro`             | string                           | for LIMIT | Limit price in millionths of one collateral unit per share. Number also accepted; see [Conventions](/docs/conventions#amounts-are-in-micro-units) |
| `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](#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](/docs/api-reference/orders).

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 [#response]

```json
{
  "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 [#errors]

Every failure is an [HTTP Problem Details](/docs/errors#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`](/docs/account-stream#order_rejected) event on the account
stream — or poll [`GET /trade/v1/orders/{id}`](#look-up-one-order) until the
status is terminal.

### Examples [#examples]

```bash
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"
  }'
```

```python
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 [#list-your-orders]

`POST /trade/v1/orders/list`

**Scope:** `orders:read`

### Request [#request-1]

```json
{ "limit": 100 }
```

Both `limit` and `cursor` are optional; omit `cursor` for the first
page. See [pagination](/docs/conventions#pagination).

### Response [#response-1]

```json
{
  "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 [#look-up-one-order]

`GET /trade/v1/orders/{order_id}`

**Scope:** `orders:read`

### Response [#response-2]

```json
{
  "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](/docs/conventions#order-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 [#errors-1]

| 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 [#look-up-an-order-by-hash]

`GET /trade/v1/orders/by-hash/{order_hash}`

**Scope:** `orders:read`

For [signed orders](/docs/api-traders/pre-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](#look-up-one-order).

### Errors [#errors-2]

| Status                | Reason                         |
| --------------------- | ------------------------------ |
| `404 order_not_found` | No visible order has that hash |

## List an order's fills [#list-an-orders-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 [#response-3]

```json
{
  "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`](/docs/portfolio#trades) 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`](/docs/portfolio#trades).

### Errors [#errors-3]

| 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 [#cancel-an-order]

`DELETE /trade/v1/orders/{order_id}`

**Scope:** `orders:cancel`

### Response [#response-4]

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](/docs/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](#common-patterns).

```json
{
  "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`](/docs/account-stream#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](#common-patterns) if a late fill is still plausible.

```json
{
  "order_id": "9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b",
  "status": "OPEN",
  "pending_operation": "CANCEL",
  "as_of": "2026-05-12T10:25:00.000Z"
}
```

### Errors [#errors-4]

| 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 [#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 [#response-5]

```json
{
  "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`](/docs/account-stream#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](#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 [#errors-5]

| Status                      | Reason                               |
| --------------------------- | ------------------------------------ |
| `403 forbidden_scope`       | Add `orders:cancel_all` to the token |
| `404 wallet_not_registered` | Register the AGARA wallet            |

## Time in force [#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](/docs/api-traders/operations#gtd-time-bounded-orders) 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 [#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`](/docs/account-stream#order_rejected)
event, or poll [`GET /trade/v1/orders/{id}`](#look-up-one-order)).

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](/docs/cross-orderbook-matching)
and [Self-trade prevention](/docs/self-trade-prevention#fok-and-post-only-orders).

## Order size limits [#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 [#common-patterns]

**Place and wait for terminal status:**

```python
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):

```python
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](/docs/api-traders/recovery) 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.
