Market Orders (single-contract trades)
A market order is a single-contract order at the best available price. There are three endpoints: estimate (no commit), submit (asynchronous fill), and a state read. The submit returns immediately with a refId, and you either poll or subscribe to webhooks to find out what happens next.
Auth: user-scoped on all three (sub must equal the acting user's UUID, plus subsig). See Authentication.
Required permission:
market-order. All four endpoints below are gated server-side on themarket-orderuser permission (via thex-required-permissionannotation on the spec). A user without it gets403 permission_denied. CallGET /private/v1/users/USERID/permissionsfirst to see whether they're cleared — see User Onboarding.
For multi-leg orders, see Parlays (2–12 leg trades) instead.
1. The flow
POST /private/v1/market-orders/estimate ─► see expected price + fee + available qty
POST /private/v1/market-orders ─► 202 with refId
GET /private/v1/market-orders/{refId} ─► poll status, fill progress, P/L
GET /private/v1/market-orders ─► the caller's order history (token-paginated)
Polling is the simplest option. For lower-latency updates without polling, subscribe to MarketOrderEvent webhooks — see Webhooks (push events).
2. The state model
| Field | Values |
|---|---|
status | pending → completed | canceled | failed |
fillStatus | open → partial → filled |
winningStatus | tbd → profit | loss | push (after the event settles) |
Small naming inconsistency to be aware of: in webhook payloads,
fillStatususesrestinginstead ofopenfor the same state. See Webhooks (push events). Treat them as synonyms.
3. POST /private/v1/market-orders/estimate
POST /private/v1/market-orders/estimateA no-commit quote. Use this to show the user what they'd get before they confirm.
Body (EstimateMarketOrderRequest):
{ "contractId": "183c6ffeb2b4b6772a5afb6336293240", "quantity": 25.00 }quantity >= 0allowed here. Use0to see the best available price without specifying a size.
Response (EstimateMarketOrderResponse):
{
"availableQuantity": 50.00,
"expectedAveragePrice": -110,
"expectedAveragePriceAdjusted": -115,
"expectedPayout": 45.45,
"expectedPayoutAdjusted": 43.10,
"expectedFeeAmount": 1.50,
"maxQuantity": 200.00,
"priceList": [-110, -109, -108]
}Field meanings:
availableQuantity— fillable at the expected average price.maxQuantity— total liquidity across all price levels. Taking the whole thing would mean a worse blended price.- Prices are in American format (
-110,+150). - The
*Adjustedvariants include the ISV fee. priceList— the price levels visible at estimate time. Echo bothexpectedAveragePriceandpriceListback into the submit request — they're a required guardrail (see below).
The common failures are 422 with INVALID_CONTRACT (the contract isn't valid or has gone stale) or with an insufficient-liquidity error.
4. POST /private/v1/market-orders
POST /private/v1/market-ordersSubmit the order. Asynchronous — the server returns 202 immediately with a reference ID, and the actual fill happens in the background.
Body (SubmitMarketOrderRequest):
{
"contractId": "183c6ffeb2b4b6772a5afb6336293240",
"quantity": 25.00,
"expectedAveragePrice": -110,
"priceList": [-110, -109, -108]
}quantity > 0required (strict —0is allowed for estimate but not for submit).expectedAveragePriceandpriceListare required guardrails (BBT-92). Pass back exactly what you got from the prior/estimateresponse. If the live price has moved off the listed levels, the backend rejects the submit so you can re-estimate before committing the user's funds. There's no way to bypass this — the only valid value is what came out of the estimate.
Response (202, MarketOrderResponse): same shape as the GET below, with initial status: "pending", fillStatus: "open", filledQuantity: 0.
The refId is not a UUID — it's a string, possibly prefixed with isv_. Treat it as opaque.
Common failure modes:
402 PaymentRequiredError— insufficient balance (relevant on INDIVIDUAL ISVs; STANDING ISVs see this when the standing fund is constrained).422 INVALID_CONTRACT— unknown or expired contract.422 user_pending_kyc— the user hasn't passed KYC yet.422— the live price has moved off the levels inpriceList. Re-call/estimateto get freshexpectedAveragePrice+priceList, show the new quote to the user, then resubmit.
5. GET /private/v1/market-orders/{refId}
GET /private/v1/market-orders/{refId}Response (MarketOrderResponse):
{
"refId": "isv_xyz",
"contractId": "183c6ffeb2b4b6772a5afb6336293240",
"requestedQuantity": 25.00,
"fee": 0.50,
"quantity": 24.50,
"filledQuantity": 24.50,
"openQuantity": 0,
"expectedAveragePrice": -110,
"currentAveragePrice": -108,
"status": "completed",
"fillStatus": "filled",
"winningStatus": "tbd",
"eventId": 999999,
"marketId": 219,
"strike": 2.5,
"createdAt": "...",
"updatedAt": "..."
}Read these in pairs:
requestedQuantityis what you asked for;feeis what was taken;quantity = requestedQuantity - feeis what actually went on the order.filledQuantity + openQuantity = quantity.expectedAveragePriceis the floor at submission;currentAveragePriceis the actual achieved price so far.eventId,marketId, andstrikeare sourced from the matched trades; they'renulluntil a trade has been spawned (i.e. before any fill activity), andstrikeis alsonullfor markets that don't carry one (moneyline). Use them for downstream reconciliation if you need event/market context without re-fetching from the fixtures API.
6. GET /private/v1/market-orders — list the caller's orders
GET /private/v1/market-orders — list the caller's ordersReturns the caller's market-order history, most-recent first, token-paginated. Each item is the same MarketOrderResponse shape as the GET-by-refId above — no lighter summary view.
Query parameters
| Name | Type | Default | Notes |
|---|---|---|---|
limit | integer | 25 | 1 ≤ limit ≤ 100. |
token | string | unset | Opaque cursor — pass the paging.token from the previous response. |
orderBy | string | createdAt | Field to order by. |
orderDir | enum | desc | asc | desc. |
status | string | unset | Comma-separated MarketOrderStatus values to filter by — any of pending, completed, canceled, failed. |
fillStatus | string | unset | Comma-separated FillStatus values to filter by — any of filled, partial, open. |
dateRangeFilter | enum | all | today | this_week | this_month | all. Filters by createdAt. |
Response (ListMarketOrdersResponse)
ListMarketOrdersResponse){
"marketOrders": [ { "refId": "isv_xyz", "...": "..." } ],
"paging": { "limit": 25, "token": "eyJpZCI6MTAwfQ==" }
}paging.token is absent when there are no more pages — that's how you know to stop.
Failure modes:
401— JWT missing/invalid.500/503— server error / service temporarily unavailable.
7. Money movement is automatic
You don't move money for order flow. ProphetX handles it on its side:
- Order debit at submit — the order's
quantityis deducted from the wallet atomically. - ISV fee — deducted automatically and surfaced as
feeon the order plus anORDER_FEEtransaction in/transactions. You don't compute or post the fee yourself. - Settlement — when the contract settles, payouts (
PAY), refunds (PUSH/VOID), and the resulting balance change are credited to the wallet automatically. ThewinningStatuson the order flips and aContractSettlementEventfires for the contract.
See Wallets for the resulting TransactionType rows.
8. Polling guidance
A few notes to save you time:
- Don't tight-loop. A 1–2 second interval during active fill and longer once
statusstabilizes is plenty. - If you'd rather not poll at all, subscribe to the
/push/market-orderswebhook and let ProphetX push updates. Each event is wrapped in{ id, op, timestamp, data }— see Webhooks (push events). - A terminal
status(completed/canceled/failed) means no further fills.winningStatusmay still flip later when the event settles — that comes through theContractSettlementEventwebhook.
9. Curl
BASE="https://isv-api.staging.prophetx.dev/private/v1"
# 1. Estimate — grab expectedAveragePrice + priceList from this response
curl -X POST "$BASE/market-orders/estimate" \
-H "Authorization: Bearer $JWT_USER" \
-H "Content-Type: application/json" \
-d '{ "contractId":"abc...", "quantity":25 }'
# 2. Submit — echo expectedAveragePrice + priceList back from the estimate
curl -X POST "$BASE/market-orders" \
-H "Authorization: Bearer $JWT_USER" \
-H "Content-Type: application/json" \
-d '{
"contractId":"abc...",
"quantity":25,
"expectedAveragePrice":-110,
"priceList":[-110,-109,-108]
}'
# → 202 { "refId":"isv_xyz", "status":"pending", ... }
# 3. Poll (or wait for the webhook)
curl "$BASE/market-orders/isv_xyz" -H "Authorization: Bearer $JWT_USER"
# 4. History (most recent first; paginate via paging.token)
curl "$BASE/market-orders?limit=25&status=pending,completed&dateRangeFilter=this_week" \
-H "Authorization: Bearer $JWT_USER"