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.
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.
Quote loop shape
A minimal quote loop looks like this:
- Subscribe and buffer the orderbook stream and the account stream for your markets.
- 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.
- Place initial quotes using pre-signed, post-only limit orders.
- On every
orderbookupdate, route ondata.kind. Replace your entire ladder forsnapshot; apply changed levels fordelta. 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. - On each
fill, update inventory and reconsider sizing. - On
sequence_resetor 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 for timeouts, resets, reconnects, and process restarts.
Follow market status and new cycles
Subscribe to market_status by condition
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.
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 to seed current state and find new events through REST.
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.
{
"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 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 for examples.
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
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.
- 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 for the mechanics.
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
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 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.
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
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:
- Track the last frame time for the public market socket and the private account socket independently.
- 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. - Follow the full ambiguity and readiness procedure. 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 and
Errors, retries, and recovery.
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_cancelledframe withreason: "USER", andGET /trade/v1/orders/{order_id}reports it asCANCELLED. - An order still
PENDINGat its expiration is cancelled without a frame, andGET /trade/v1/orders/{order_id}reports it asCANCELLED. - An order still
SUBMITTINGat its expiration can still be placed. You can receiveorder_acceptedand fills; whatever rests is then removed with anorder_cancelledframe. If its submission is rejected or fails instead, it endsREJECTEDorFAILEDwith anorder_rejectedframe and noorder_cancelled, so reconcile whichever terminal status it reaches.
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 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 | 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 | 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 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
The /trade/v1/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 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:
- 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. - For a MARKET BUY, reserve its
collateral_amount_micro. That budget includes the fee headroom and has nooriginal_size_microorprice_micro. - For a MARKET SELL, reserve its unfilled
original_size_microshares. Collateral cannot cover missing SELL inventory.
Read outcome balances through
POST /trade/v1/portfolio/positions/list. 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
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}, or handle the
order_rejected event on the account
stream when it is delivered. The 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 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.
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.