Execution
The fill request body, why the response is always pending, and the checks that can still refuse a quote
Every fill is user-tier: send x-app-id and the user's Authorization: Bearer token. The call
returns before the trade finishes. Track it with
Order lifecycle & statuses.
Follow Quickstart: REST for a complete quote-and-fill example. The fields and checks below explain how to handle the execution request.
Fill body
POST /execution/fill requires quoteId from an executable quote
(Quotes & smart routing). The response status is always pending: the trade
is accepted, not done.
| Field | Required | Use |
|---|---|---|
quoteId | Yes | The quote to fill. |
mode | No | live (default) or paper. Must match the quote. |
fallbackToLatest | No | If quoteId has expired, fill the user's latest quote with the same intent. fallbackToLatest.outcomeId is an alias of venueMarketOutcomeId. Also send side and maxSpend or sellShares, and allowedVenues when the quote was scoped. |
signingAddress | No | Self-custody wallet that signs the fill. See Self-custody trading. |
fundingAddresses | No | Other linked wallets this fill may spend. See Self-custody trading. |
approveMode | No | approveMode: "sponsored" or approveMode: "user_broadcast". See Self-custody trading. |
A 404 from GET /execution/status after this call is not final. Retry by following
Errors, retries & idempotency. Do not get a new quote until a step there says to.
Direct orders: Direct orders. Limit orders:
Limit orders. Cancel with POST /execution/orders/cancel; a response can
stay cancel_pending, and a filled or already cancelled order returns 409. Compare these flows in
Choose your integration.
Measure order event delivery
Use the SDK's optional receipt callback to capture when the client handles an order
event. The server timestamp stays in milliseconds. deliveryDelayMs is null
when the clocks disagree or a server timestamp is missing.
const socket = client.createWebSocket({
onOrderEventReceipt: (timing) => recordDeliveryTiming(timing),
onOrderEvent: (event) => updateOrder(event),
});
socket.connect();Receipt telemetry does not delay execution or suppress order notifications if its callback throws. Compare it with backend bridge timing to separate funding time, confirmation delivery and client delivery.
Redeem
Claiming winnings is Market resolution & voids. EVM redemption
completion is the WebSocket event redeem_event.
Redeem winnings for resolved positions.
Checks at fill time
sufwan-local-test checks these again when you fill, so a quote that looked fine can still be refused:
- The user's location against each venue in the route. A blocked venue returns
403. - Your app's venue and category settings. Returns
400quote_app_blocked. - The market is still open. Returns
400quote_market_inactive. - The user's balance still covers the trade. Returns
400quote_insufficient_balance. - Testing-mode trade cap. Returns
403.
Codes and retry rules: Errors, retries & idempotency.
Slippage and price protection
slipCapBps is how far from a reference price you accept, in basis points. 100 is 1%. On a buy
it caps the price per share. On a sell it floors it. maxSpend still bounds the total a buy can
cost, so the cap changes how many shares that spend buys, not how much is spent.
How the cap is used depends on how you place the order. A quote applies it twice. A direct order
with skipQuote applies it once.
With a quote
This covers Get a quote followed by
Execute quote. It also covers
Place an order directly without skipQuote,
which builds a quote for you before placing the order.
| Step | Reference price | What the cap does |
|---|---|---|
| Quoting | The market's midpoint | The quote's average price must stay within the cap of the midpoint. A tight cap can make the quote smaller, or leave nothing to quote. |
| Execution | The quoted price for each leg | The order cannot fill worse than the quoted price plus the cap. |
Your user sees the quoted price before committing. The quote's legBounds[] lists the worst
price each leg accepts as worstPrice, with the cap applied as effectiveSlippageBps.
With a direct order and skipQuote
Place an order directly with skipQuote skips
the router, so there is no quoting step. The order is sized from one price and sent straight to the
venue you name. The cap applies only at execution.
price sent | Reference price |
|---|---|
| Yes | Your price. |
| No | The best ask (buy) or best bid (sell) sufwan-local-test reads when it accepts the order. Your user never saw this price. |
Here the cap only covers the moment between sufwan-local-test accepting the order and the venue filling it. Send
the price your user saw as price, so the cap protects a price they accepted. Use skipQuote only
when your backend needs the lower latency. Otherwise place the same order without it.
{
"venue": "predict",
"venueMarketOutcomeId": "<outcome id on predict>",
"side": "buy",
"maxSpend": 50,
"externalId": "trade-0001",
"skipQuote": true,
"price": 0.62,
"slipCapBps": 300
}The other skipQuote rules are on Direct orders.
When the price moves past the cap
At execution, the order takes only the liquidity inside the cap. If none is left there when the
order reaches the venue, the order fails. The reason says the price moved past the accepted quote
and slippage. Read it as errorReason on
Get execution status, or as errorMessage on
Get user orders. Nothing fills, and the funds are not spent. A direct order's externalId is
used up.
Do not resend the same request. Read the current price, show it to your user, and on
confirmation place a new order with a new externalId, or request a new quote. Retrying unchanged
either fails again or fills at a price your user did not approve.
Choosing a cap
Prices move in steps, so a cap smaller than one step fails on any movement at all. Read the step
from the market's tickSize on List venue markets or
Get live orderbooks. Many markets move in one-cent
steps, and one cent is a different share of each price:
| Price | One cent as basis points |
|---|---|
| 0.90 | 112 |
| 0.62 | 162 |
| 0.50 | 200 |
| 0.25 | 400 |
| 0.10 | 1000 |
A 1% cap (100) therefore cannot absorb a single one-cent move at any price below 1. Set the cap
to at least one step plus a margin for how fast the market moves:
// tickSize from the market, price as a 0-1 decimal
const oneTickBps = Math.ceil((tickSize / price) * 10_000);
const slipCapBps = Math.max(200, oneTickBps + 50);Widen it for fast markets such as live sports, and keep a ceiling your users accept. On cheap
outcomes even one step is a large share of the price, so decide whether to trade those at all
rather than letting the cap grow without limit. Set the cap on sells too: sellShares bounds the
shares sold, not the proceeds.
Venue rules at execution
Omitting slipCapBps, or sending 10000 or more, uses the venue's default. These rules apply to
both quotes and direct orders.
| Venue | Default | How the cap applies |
|---|---|---|
kalshi, pred | 0 | Always zero. Orders never execute worse than the reference price. |
polymarket | 500 | Rounded inward to the market's tickSize, never below the reference price itself. |
predict | 500 | Applied as written. |
myriad | 200 | Applied as written. |
hyperliquid | 50 | Values under 50 are raised to 50. |
prophetx | 500 | The venue prices the order itself within the cap. |