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. None of it is MM-specific in the strict sense — the endpoints work the same way for any caller — but these are the patterns you'll actually use.
Quote loop shape
A minimal quote loop looks like this:
- Subscribe and buffer the orderbook stream and the account stream for your markets.
- Seed state with REST while buffering:
/trade/v1/portfolio/summaryfor available capital,/trade/v1/portfolio/open-orders/listfor any leftover resting orders. - Place initial quotes using post-only limit orders.
- On each orderbook
deltaupdate (the channel sends asnapshotframe first, thendeltaframes), decide whether to re-quote — cancel stale orders, place fresh ones at the new desired prices. - On each
fill, update inventory and reconsider sizing. - On
sequence_resetor stream disconnect, resubscribe, buffer, and re-seed from REST before trusting local state again.
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 event, or poll
/trade/v1/orders/{id}.
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.
See Orders → post-only for the full semantics.
Self-trade prevention
Two resting orders from the same account can't fill each other. By
default the maker side is cancelled when an incoming taker would
cross with your own resting order — your taker order lands as
normal, and a 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_PREVENTIONcancels 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.
See Self-trade prevention for the mechanics.
Cancel-all
POST /trade/v1/orders/cancel-all requests cancellation of every resting
order on your account. 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.
After the 202, wait for cancellation events or reconcile
/trade/v1/portfolio/open-orders/list until the affected orders are terminal. Do not
place replacement quotes under the assumption that the response made the
book empty immediately.
Scope: orders:cancel_all. See
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 worth of orders, fetch
POST /trade/v1/portfolio/open-orders/list with a token_ids array in the JSON body,
then cancel each returned order by ID.
Acceptable for low-N cases; if you're running many markets and need a
faster path, let us know.
Cancel-on-disconnect
We don't auto-cancel resting orders on WebSocket disconnect today. This matters for market makers because a network blip can leave stale quotes resting in the book against a price that's moved.
Use heartbeat-driven recovery on your side:
- Track the last time you received any frame on the account stream.
- If it exceeds your tolerance (say, 30s), assume disconnect and
call
cancel-all. - Reconnect, re-seed, resume.
The platform sends heartbeat frames every ~10s on both streams, so
30s of silence is a strong signal. See
Streaming → reconnect strategy.
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, rely on expiry rather than explicit cancel).
Expired GTD orders are cancelled server-side, and an
order_cancelled frame fires so
your state stays in sync.
Streaming for quote rebalancing
Polling the orderbook works but adds latency. For real-time rebalancing, subscribe to the streams:
| Channel | Use it for |
|---|---|
orderbook | Full depth deltas — for fitting a curve, computing fair-value, or anything that depends on the shape of the book |
best_quote | Top-of-book only — much smaller payload; sufficient for most spread-capture strategies |
trades | Public fill flow — to detect taker pressure and adjust sizing |
account_events | Your own order, fill, position, redemption, and collateral changes — use these for low-latency updates and reconcile with REST |
A typical bot subscribes to best_quote + account_events for the
hot path, and uses REST /trade/v1/orderbook/{token_id} lazily when it needs
full depth (re-seeding after disconnect, sanity-checking on a slow
loop).
Sizing against available capital
The /trade/v1/portfolio/summary response carries your collateral
balance as cash_balance_micro. It is not reduced by resting BUY orders.
Before sizing another BUY, calculate the remaining notional and worst-case
fee requirement of your open BUY commitments from
/trade/v1/portfolio/open-orders/list.
For each open order:
- Calculate its unfilled shares as
original_size_micro - size_matched_micro. - For a BUY, reserve its remaining notional plus the worst-case fee for that quantity. Do not size only from notional.
- For a SELL, reserve the unfilled shares of that specific outcome. Collateral cannot cover missing SELL inventory.
BUY commitments are checked per market. A conservative strategy should also cap aggregate exposure across every market it quotes.
When an order is rejected
We check your order against your live balance and positions before accepting
it, so the rejections MM loops hit most come back synchronously as a 422
on the POST response, with the reason in the error field:
| Reason | 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 |
| Not fillable | A FOK that couldn't fully fill | Re-fetch the book, re-size or re-price |
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
REJECTED order — poll /trade/v1/orders/{id}, or handle the
order_rejected event on the account
stream when it is delivered. A halted or resolved
market is a separate 400 (market isn't accepting orders); the
market_status channel tells you to stop quoting.
Request validation failures — malformed body, an order notional outside the
0.10–100,000 collateral-unit bounds, a bad time-in-force / post-only combination — are a 400,
not a 422.
Don't blanket-retry — most of these need different action, not a re-attempt. See Conventions → error envelope.
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.
- 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.