Core concepts

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.

FieldRequiredUse
quoteIdYesThe quote to fill.
modeNolive (default) or paper. Must match the quote.
fallbackToLatestNoIf 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.
signingAddressNoSelf-custody wallet that signs the fill. See Self-custody trading.
fundingAddressesNoOther linked wallets this fill may spend. See Self-custody trading.
approveModeNoapproveMode: "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.

Execute quote.

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 400 quote_app_blocked.
  • The market is still open. Returns 400 quote_market_inactive.
  • The user's balance still covers the trade. Returns 400 quote_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.

StepReference priceWhat the cap does
QuotingThe market's midpointThe 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.
ExecutionThe quoted price for each legThe 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 sentReference price
YesYour price.
NoThe 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:

PriceOne cent as basis points
0.90112
0.62162
0.50200
0.25400
0.101000

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.

VenueDefaultHow the cap applies
kalshi, pred0Always zero. Orders never execute worse than the reference price.
polymarket500Rounded inward to the market's tickSize, never below the reference price itself.
predict500Applied as written.
myriad200Applied as written.
hyperliquid50Values under 50 are raised to 50.
prophetx500The venue prices the order itself within the cap.

On this page