# Operations

> The operational mechanics for running a quote loop on Agara — post-only, self-trade prevention, cancel-all, and how to wire the streaming surfaces into a robust loop.

This page covers the per-call mechanics that matter most when you're
managing resting orders through the API: placing quotes, controlling
exposure, and keeping your view of the market current.

<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>

## Quote loop shape [#quote-loop-shape]

A minimal quote loop looks like this:

1. **Subscribe and buffer** the [orderbook stream](/docs/streaming) and the
   [account stream](/docs/account-stream) for your markets.
2. **Complete the full quote-readiness state machine** while buffering. This
   includes the active-subscription + REST-watermark orderbook fence; the fully
   paginated order, positions, summary, trades, activities, and open-order REST
   seed; reconciliation of buffered account events; and the market-lifecycle
   risk decision. Do not place from a smaller subset of those checks.
3. **Place** initial quotes using [pre-signed](/docs/api-traders/pre-signed-orders),
   [post-only](/docs/orders#post-only) limit orders.
4. On every [`orderbook`](/docs/streaming#orderbook) update, route on
   `data.kind`. Replace your entire ladder for `snapshot`; apply changed levels
   for `delta`. Current v1 sends full snapshots for book changes, so a loop that
   reacts only to deltas will miss live moves. Then decide whether to
   **re-quote** — cancel stale orders and place fresh ones at the new desired
   prices.
5. On each [`fill`](/docs/account-stream#fill), **update inventory** and
   reconsider sizing.
6. On [`sequence_reset`](/docs/streaming#sequence_reset) or stream
   disconnect, **pause placements, buffer, and re-seed both public and account
   state** before trusting local state again.

Use the complete [quote-readiness state
machine](/docs/api-traders/recovery#quote-readiness-state-machine) for
timeouts, resets, reconnects, and process restarts.

### Follow market status and new cycles [#follow-market-status-and-new-cycles]

Subscribe to [`market_status` by condition](/docs/streaming#condition-scoped-status-updates)
for every market you quote. Stop placing on `market_halted`, `market_resolved`,
or `payouts_resolved`, reconcile open orders, and refresh market state before
resuming after `market_resumed`. A proposal (`outcome_proposed`, or
`payouts_proposed` for a split) is not a final resolution. A split resolution
arrives only as these `payouts_*` kinds, never as a winner with an empty token,
so route on `kind`.

For recurring events, also subscribe to
[`market_status` by event](/docs/streaming#event-scoped-lifecycle-updates).
`current_market_changed` supplies the newly active market and outcome IDs;
`market_resolution_completed` supplies the resolved market, its winner or split
payouts, and replacement details. Start the quote-readiness procedure for a new
active market before quoting it. An upcoming or merely expected replacement is
not ready to trade. Keep reconciling orders and positions for the previous
cycle.

Event subscriptions send future changes without an initial snapshot or replay.
Use the [market discovery workflow](/docs/markets#follow-newly-active-and-resolved-markets)
to seed current state and find new events through REST.

## Post-only [#post-only]

Set `post_only: true` on any limit order you don't want to take
liquidity. If your price would cross the book on arrival, the order is
rejected rather than filled as a taker. Most of the time this comes back
immediately as a `422` on the `POST` response; if the book moves after that
check, it instead surfaces asynchronously as a `REJECTED` order — an
[`order_rejected`](/docs/account-stream#order_rejected) event, or poll
[`/trade/v1/orders/{id}`](/docs/orders).

Use this on every quote in your loop. Without it, a fast move in the
book between your decision and your submission silently turns you
from maker to taker and makes the fill subject to taker fees.

```json
{
  "token_id": "21742…36455",
  "side": "BUY",
  "type": "LIMIT",
  "time_in_force": "GTC",
  "price_micro": "300000",
  "shares_micro": "100000000",
  "post_only": true
}
```

See [Orders → post-only](/docs/orders#post-only) for the full semantics.

An order can take liquidity on the complementary outcome too. For example,
BUY YES can match BUY NO. The requested outcome's orderbook already includes
this complementary liquidity at its effective price. Use that combined depth
when choosing a post-only price, and track your own quotes on both outcomes;
see [Cross-orderbook matching](/docs/cross-orderbook-matching) for examples.

## Self-trade prevention [#self-trade-prevention]

Orders from the same account cannot fill each other. The **maker side is
always cancelled** when matching encounters your own eligible resting order.
The incoming order continues matching, and an
[`order_cancelled`](/docs/account-stream#order_cancelled)
frame fires for the cancelled maker with `reason: "SELF_TRADE_PREVENTION"`.

This is usually what you want — you'd rather cancel an old stale
quote than match against yourself and waste fees on a no-op trade.
But it does mean your re-quote loop needs to account for it:

* If you cancel-then-place, the new place may STP-cancel an older
  order you didn't realize was still resting. Log the
  `SELF_TRADE_PREVENTION` cancels so you can audit later.
* If a new order crosses your own older resting order, the older maker
  order is cancelled. The new order continues and may match another
  account or rest on the book.
* Track your quotes on both outcomes: complementary BUY/BUY and SELL/SELL
  matches use the same rule. Post-only does not prevent these cancellations.

See [Self-trade prevention](/docs/self-trade-prevention) for the
mechanics.

## Cancel-all [#cancel-all]

`POST /trade/v1/orders/cancel-all` schedules cancellation of every nonterminal
order visible when each wallet's sweep begins. It returns `202`; cancellation
completes asynchronously. Use it as a kill switch:

* **Disconnects.** If you cannot trust your local book, request cancellation,
  reconnect, then reconcile before resuming.
* **Process restart.** Bot crashed, you don't trust your in-memory
  state — cancel everything from a fresh process, reconcile.
* **End of session.** Stop quoting without leaving stale liquidity behind.

Don't wait for one [`order_cancelled`](/docs/account-stream#order_cancelled)
frame 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. 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
[Orders → cancel an order](/docs/orders#cancel-an-order) before you resume.

After the `202`, treat its `as_of` as a conservative reconciliation upper bound
and page `POST /trade/v1/orders/list` to the end. Confirm every order sent no
later than that time has `is_terminal: true`, including completed FAK partial
fills and orders that had not reached the book. Each
wallet's sweep can begin before the response timestamp, so do not assume
`as_of` is the exact cutoff. Do not rely on portfolio open orders alone or
assume that the response made the book empty. Cancel a late order or repeat
cancel-all, then repeat the full order check.

Scope: `orders:cancel_all`. See
[Orders → cancel everything open](/docs/orders#cancel-everything-open).

There's no cancel-by-market or cancel-by-token-id endpoint today. If you need
to clear one market's orders, fully page `POST /trade/v1/orders/list`, filter
the result by `token_id`, and cancel every nonterminal match by ID. The
portfolio open-orders view also shows active orders, but the full order list
lets the same recovery ledger follow each ID into a terminal state. This
per-order approach is acceptable for low-N cases; if you're running many
markets and need a faster path, let us know.

## Cancel-on-disconnect [#cancel-on-disconnect]

We don't auto-cancel resting orders on WebSocket disconnect today.
A network blip can leave your quotes resting in the book while the market
price moves.

Use heartbeat-driven recovery on your side:

1. Track the last frame time for the public market socket and the private
   account socket independently.
2. If either socket exceeds your tolerance (say, 30s), closes, or reports a
   reset or error that makes its view untrusted, pause placements and request
   `cancel-all`. Do not treat that request as a fence for a placement that may
   still be completing.
3. Follow the full [ambiguity and readiness procedure](/docs/api-traders/recovery).
   Cancel a late-discovered order or repeat cancel-all, then resume only after
   the ambiguous placement and both stream views are reconciled.

The platform sends `heartbeat` frames every \~10s on both streams, so
30s of silence is a strong signal. See
[Market stream → reconnect strategy](/docs/streaming#reconnect-strategy) and
[Errors, retries, and recovery](/docs/api-traders/recovery).

## GTD (time-bounded orders) [#gtd-time-bounded-orders]

`time_in_force: "GTD"` plus `expiration_unix_seconds` rests an order
until it fills or expires. Useful for:

* Orders you want to forget about (no need to cancel — they expire on
  their own).
* Strategies where stale liquidity is worse than no liquidity (set a
  short TTL on every quote as a backstop, and cancel explicitly when a fill
  after expiry would hurt).

If a fill after `expiration_unix_seconds` would hurt your strategy, cancel the
order yourself well before it expires. An expired GTD order is not removed at
the exact expiration time, and it can still fill until it is removed.

* An expired order resting on the book is removed with an
  [`order_cancelled`](/docs/account-stream#order_cancelled) frame with
  `reason: "USER"`, and `GET /trade/v1/orders/{order_id}` reports it as
  `CANCELLED`.
* An order still `PENDING` at its expiration is cancelled without a frame, and
  `GET /trade/v1/orders/{order_id}` reports it as `CANCELLED`.
* An order still `SUBMITTING` at its expiration can still be placed. You can
  receive `order_accepted` and fills; whatever rests is then removed with an
  `order_cancelled` frame. If its submission is rejected or fails instead, it
  ends `REJECTED` or `FAILED` with an `order_rejected` frame and no
  `order_cancelled`, so reconcile whichever terminal status it reaches.

See [Orders → time in force](/docs/orders#time-in-force).

## Streaming for quote rebalancing [#streaming-for-quote-rebalancing]

Polling the orderbook works but adds latency. For real-time
rebalancing, subscribe to the streams:

| Channel                                    | Use it for                                                                                                                                                                                                                                                                                                                   |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`orderbook`](/docs/streaming#orderbook)   | Full-depth updates. Current v1 sends a replacement snapshot for every book change; clients must still route by `data.kind` and apply a delta if one is received. Use the REST-watermark seed plus this channel for quote readiness, fitting a curve, computing fair value, or anything that depends on the shape of the book |
| [`best_quote`](/docs/streaming#best_quote) | Optional top-of-book signal with a smaller payload. It carries the best prices and no depth, so never use it as your only book source                                                                                                                                                                                        |
| [`trades`](/docs/streaming#trades)         | Public fill flow — to detect taker pressure and adjust sizing                                                                                                                                                                                                                                                                |
| [`account_events`](/docs/account-stream)   | Your own order, fill, position, redemption, and collateral changes — use these for low-latency updates and reconcile with REST                                                                                                                                                                                               |

A trading bot should use `orderbook` + `account_events` as its
authoritative hot path. Add `best_quote` as a convenience signal if useful,
and use REST `/trade/v1/orderbook/{token_id}` when recovering or verifying
local state.

## Sizing against available capital [#sizing-against-available-capital]

The [`/trade/v1/portfolio/summary`](/docs/portfolio#summary) response carries
your collateral balance as `cash_balance_micro`. It is not reduced by an order
that can still become active. Before sizing another BUY, fully page
[`POST /trade/v1/orders/list`](/docs/orders#list-your-orders) and calculate the
remaining notional and worst-case fee requirement of every nonterminal BUY
commitment, including a pre-book order. The portfolio open-orders view also
includes active AGARA commitments; whichever endpoint you use, follow every
cursor and include the pre-book statuses.

For each nonterminal commitment:

1. For a LIMIT order, calculate unfilled shares as
   `original_size_micro - size_matched_micro`. A LIMIT BUY consumes its
   remaining notional plus worst-case fee headroom; a LIMIT SELL consumes those
   outcome shares.
2. For a MARKET BUY, reserve its `collateral_amount_micro`. That budget includes
   the fee headroom and has no `original_size_micro` or `price_micro`.
3. For a MARKET SELL, reserve its unfilled `original_size_micro` shares.
   Collateral cannot cover missing SELL inventory.

Read outcome balances through
[`POST /trade/v1/portfolio/positions/list`](/docs/portfolio#positions). Do not
size an AGARA SELL while `unavailable_exchanges` includes `AGARA`; retry the
unpaginated positions read before using it.

BUY commitments are checked per market. A conservative strategy
should also cap aggregate exposure across every market it quotes.

## When an order is rejected [#when-an-order-is-rejected]

We check your order against your live balance and positions before accepting
it, so common quote-loop rejections come back **synchronously** as a `422`
on the `POST` response. Branch on the stable `code` field:

| Code                    | What it means                                      | Typical reaction                                                         |
| ----------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ |
| `post_only_would_cross` | The book moved against your quote before it rested | Re-fetch top of book, re-quote                                           |
| `insufficient_balance`  | Not enough collateral to back a BUY                | Check `cash_balance_micro` and your open orders, retry with smaller size |
| `insufficient_shares`   | Not enough tokens to back a SELL                   | Reconcile inventory, retry with smaller size                             |
| `order_not_fillable`    | A FOK that couldn't fully fill                     | Re-fetch the book, re-size or re-price                                   |
| `market_closed`         | The market no longer accepts orders                | Stop quoting and reconcile open orders                                   |

The check is best-effort. Your balance or the book can move between it and the
match, so an order can still be accepted (`202` / `PENDING`) and then land as a
`FAILED` or `REJECTED` order with a typed `failure` — poll
[`/trade/v1/orders/{id}`](/docs/orders), or handle the
[`order_rejected`](/docs/account-stream#order_rejected) event on the account
stream when it is delivered. The [`market_status`](/docs/streaming#market_status) channel
tells you when to stop quoting.

Malformed JSON is `400 malformed_json`. A bad time-in-force / post-only
combination, or any other malformed request shape, is `422 validation_failed`.
An order notional outside the 0.10–100,000 collateral-unit bounds is
`422 order_value_below_minimum` or `422 order_value_above_maximum`; the other
[order bounds](/docs/orders#order-size-limits) each have their own code too.

Don't blanket-retry — most of these need different action, not a
re-attempt. Follow the response's recovery instruction in
[Errors and recovery](/docs/errors).

## Settlement considerations [#settlement-considerations]

This deployment settles AGARA fills on Base using
`USDC`.

* **Settlement state.** Keep matched and settled exposure separate. Do not
  depend on an undocumented confirmation time.
* **Resolution risk.** Reduce or stop quoting when your strategy cannot price
  the approach to resolution safely.
* **Token rotation.** PATs don't expire by default, but rotation is a
  good operational practice. See
  [Authentication → TTL](/docs/authentication#ttl).
* **Withdrawals.** Funds remain in the AGARA account until you withdraw them.
  A self-custody client can submit a supported same-chain withdrawal in an
  owner-signed account batch. Every path settles asynchronously.

The signed-in web flow can also quote supported cross-chain withdrawal routes.
Request a fresh supported-assets response and quote before confirming.
