Describes stx-python 0.8.0, the current release.
A combo is one market over two or more legs. Players ask makers for a quote on a set of legs and a stake, makers quote what the player wins, and the player accepts the best quote. The exchange then places one ordinary limit order for each side on the combo’s market. Read Combos for the model and Combo negotiation channels for the wire protocol: every frame, field and refusal reason. This page shows what the SDK gives you.
Both channels are on STXWebSocket, so they are async only, and both need a read_write API key.
What you get back
Section titled “What you get back”Every command returns a typed model from stx.combos, and every push arrives as a ChannelMessage whose data is a typed model chosen by its event. msg.payload is still the raw object. A refused command raises a typed exception (see Errors).
| Channel | Command | Returns |
|---|---|---|
rfq |
request_quote(legs, amount) |
QuoteRequest |
rfq |
snapshot(rfq_id) |
RfqSnapshot (its best_quote is a BestQuote) |
rfq |
accept(rfq_id, quote_id, amount) |
ComboPlacement |
rfq |
cash_out(market_id) |
CashOut |
rfq |
cancel(rfq_id) |
CancelledRequest |
combo_quote |
quote(rfq_id, amount, expires_in_ms) |
ComboQuote |
combo_quote |
withdraw_quote(rfq_id, quote_id) |
WithdrawnQuote |
| Channel | msg.event |
msg.data |
|---|---|---|
rfq |
best_quote |
BestQuote |
rfq |
placed |
ComboPlacement |
rfq |
negotiation_closed |
NegotiationClosed |
combo_quote |
rfq_opened |
RfqOpened (its legs are ComboLeg) |
combo_quote |
quote_accepted |
QuoteAccepted |
combo_quote |
placed |
MakerPlacement |
combo_quote |
negotiation_closed |
NegotiationClosed |
combo_quote |
rfq_closed |
RfqClosed |
Money and quantities are strings, never floats. expires_at is integer microseconds since the Unix epoch: 1792557584350270 is 2026-10-21 04:39:44.350270 UTC.
Walkthrough
Section titled “Walkthrough”One combo from request to cash out, captured with stx-python 0.7.1 on US demo on 2026-10-21. Every output below is what the SDK returned or delivered, with nothing changed: the field the code prints, or model_dump() of the whole object. A player stakes $5.00 on two legs, BTC up and ETH up over the same 15 minutes. Maker A quotes a $10.00 win and maker B quotes $8.00. Each account has its own AsyncSTX client and socket, opened with async with client.websocket() as ws:; below they are player_ws, maker_ws and maker_b_ws.
1. Join
Section titled “1. Join”rfq = await player_ws.rfq()quotes = await maker_ws.combo_quote()print(rfq.join_reply.external_id)print(quotes.join_reply.external_id)9c87ec1c-588e-4512-9b91-d71653e414583914a397-b553-4510-8b54-d6a6c0a096b0join_reply is a ComboJoinReply. external_id is the account’s pseudonym: the first line is the player’s, which makers see on every request and quote. The second is maker A’s own, which it would show only as a player; nobody sees a maker’s. The join reply carries more than external_id, and the SDK keeps fields it does not model, so print the field you need rather than the whole reply.
2. Player requests a quote
Section titled “2. Player requests a quote”request = await rfq.request_quote( [ ("70e7b102-1dd7-4236-9665-6f645303fcb1", "won"), # BTC 15 min, up ("4e3243fe-b3f5-4264-a586-73d6b512eb86", "won"), # ETH 15 min, up ], amount="5.00",){"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a"}The request lives five minutes. Nothing is quoted yet, so a snapshot shows an empty best quote:
await rfq.snapshot(request.rfq_id){ "rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": null, "status": "open", "side": "buy", "amount": "5.0000", "quantity": null, "expires_at": 1792557584350270, "best_quote": {"rfq_id": null, "market_id": null, "quote_id": null, "amount": null, "expires_at": null}}market_id is None on a buy until it is placed: the combo’s market is found or created at the bind. Inside a snapshot, best_quote never carries rfq_id or market_id; read them from the snapshot itself.
3. Makers receive the request
Section titled “3. Makers receive the request”msg = await quotes.next() # msg.event == "rfq_opened", msg.data is an RfqOpened{ "rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": null, "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "side": "buy", "amount": "5.0000", "quantity": null, "max_price": "1.0000", "tick_size": "0.0001000", "expires_at": 1792557584350270, "legs": [ {"market_id": "70e7b102-1dd7-4236-9665-6f645303fcb1", "outcome": "won", "short_name": "BTC 15 min"}, {"market_id": "4e3243fe-b3f5-4264-a586-73d6b512eb86", "outcome": "won", "short_name": "ETH 15 min"} ]}On buy, amount is the player’s stake and quantity is None. Every maker joined to combo_quote gets the same push, including the player’s own account if it is joined there.
4. Makers quote what the player wins
Section titled “4. Makers quote what the player wins”await maker_b_quotes.quote(msg.data.rfq_id, "8.00", 120_000)quote = await quotes.quote(msg.data.rfq_id, "10.00", 60_000, client_order_id="docs-sdk-m1"){"quote_id": "73968997-833d-4cfe-bbfb-b56816a51ea7", "amount": "8.0000", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458"}{"quote_id": "d675be64-f34d-40a6-b454-f004bbb1d272", "amount": "10.0000", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458"}client_order_id is copied onto the maker’s order if the quote is accepted. A quote cannot be changed: withdraw it and quote again.
q = await quotes.quote(msg.data.rfq_id, "9.00", 60_000)await quotes.withdraw_quote(msg.data.rfq_id, q.quote_id){"quote_id": "a478269d-0789-4d40-8e5d-37a4712873ad", "amount": "9.0000", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458"}{"withdrawn": "a478269d-0789-4d40-8e5d-37a4712873ad"}5. Player sees the best quote
Section titled “5. Player sees the best quote”msg = await rfq.next() # msg.event == "best_quote", msg.data is a BestQuote{"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": null, "quote_id": "d675be64-f34d-40a6-b454-f004bbb1d272", "amount": "10.0000", "expires_at": 1792557344586656}Only the best live quote is pushed: maker B’s $8.00 is never shown while maker A’s $10.00 stands. expires_at here is the quote’s expiry, 60 seconds out. Every socket the player has joined to rfq gets the push. When the last live quote is withdrawn or expires, best_quote arrives again with quote_id, amount and expires_at set to None.
6. Player accepts
Section titled “6. Player accepts”accept takes the amount you saw as a floor. Above the best, it raises STXComboPriceMovedException and the request stays open:
try: await rfq.accept(request.rfq_id, msg.data.quote_id, "25.00")except stx.STXComboPriceMovedException as exc: print(exc) print(exc.best_quote)accept on rfq:c7a6abb5-5e79-489d-951f-c043d4377fac refused: price_moved{"rfq_id": null, "market_id": null, "quote_id": "d675be64-f34d-40a6-b454-f004bbb1d272", "amount": "10.0000", "expires_at": 1792557344586656}At the best:
placed = await rfq.accept(request.rfq_id, msg.data.quote_id, msg.data.amount){"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "order_id": "af1d2b26-0a5a-410f-be98-8607b5774163", "quantity": "14.00", "price": "0.3334"}The exchange turns the amounts into an order on the combo’s market. The price is max_price × stake / (stake + win) = 5 / 15 = 0.33333, rounded up to the tick: 0.3334. The size is whole contracts, floor(5 / 0.3334) = 14. The player pays $4.67 and collects $14.00 if both legs win. The reply is what was placed, not what filled. accept can take up to 30 seconds and waits 40 by default (timeout=).
7. Both sides are told
Section titled “7. Both sides are told”The player gets placed on rfq, the same ComboPlacement as the reply. Maker A gets quote_accepted first, then placed with its own order at the same price and size:
{"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": null, "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "quote_id": "d675be64-f34d-40a6-b454-f004bbb1d272"}{"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "quote_id": "d675be64-f34d-40a6-b454-f004bbb1d272", "order_id": "9a71ba22-0277-49a6-a3d2-66be484036e2", "quantity": "14.00", "price": "0.3334"}quote_accepted means the bind is being attempted, not that it succeeded: placed or negotiation_closed follows. Maker B, whose quote lost, gets negotiation_closed for its quote:
{"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "reason": "placed_elsewhere", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "quote_id": "73968997-833d-4cfe-bbfb-b56816a51ea7"}and every maker gets rfq_closed:
{"rfq_id": "3c9a69c5-6a48-4655-9a20-25fd5ddd157a", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "reason": "placed"}The request is gone now: snapshot and accept on it raise STXComboException with reason == "not_found". From here b66e1763-... is an ordinary market: fills, positions and settlements arrive on the orders, fills, positions and settlements channels.
8. Player cashes out
Section titled “8. Player cashes out”out = await rfq.cash_out("b66e1763-caa7-49a3-9ec5-2287e81d0610"){"rfq_id": "6d2d176a-589a-4cbc-9e72-9feba03f3a89", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "quantity": "14.00"}A cash out is a new request with its own rfq_id, and the exchange sets the size. Right after a bind the position may not be recorded yet, and cash_out raises STXComboException with reason == "nothing_to_sell"; retry after a second. Makers get rfq_opened with side sell:
{ "rfq_id": "6d2d176a-589a-4cbc-9e72-9feba03f3a89", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "side": "sell", "amount": null, "quantity": "14.00", "max_price": "1.0000", "tick_size": "0.0001000", "expires_at": 1792557587434511, "legs": [ {"market_id": "4e3243fe-b3f5-4264-a586-73d6b512eb86", "outcome": "won", "short_name": "ETH 15 min"}, {"market_id": "70e7b102-1dd7-4236-9665-6f645303fcb1", "outcome": "won", "short_name": "BTC 15 min"} ]}On sell, quantity is the size, amount is None, and market_id is the combo. The legs come in the combo’s own order, which need not be the order the player sent. Makers quote what they pay for all 14 contracts. A quote worth more than the size can pay ("21.000", $1.50 a contract) is refused with price_out_of_range.
await maker_b_quotes.quote(out.rfq_id, "3.73", 60_000)await quotes.quote(out.rfq_id, "4.20", 60_000, client_order_id="docs-sdk-m2")The player’s best_quote pushes now carry market_id, first maker B’s quote, then maker A’s better one:
{"rfq_id": "6d2d176a-589a-4cbc-9e72-9feba03f3a89", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "quote_id": "495076df-6cb6-4484-981a-f85598939c07", "amount": "3.7300", "expires_at": 1792557347659883}{"rfq_id": "6d2d176a-589a-4cbc-9e72-9feba03f3a89", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "quote_id": "e5b7980a-9a35-4e70-84b7-21fd51083ba5", "amount": "4.2000", "expires_at": 1792557348010005}Because the amount is a floor, accepting at the first amount seen ($3.73) takes the best live quote, $4.20:
sold = await rfq.accept(out.rfq_id, best.quote_id, "3.73"){"rfq_id": "6d2d176a-589a-4cbc-9e72-9feba03f3a89", "market_id": "b66e1763-caa7-49a3-9ec5-2287e81d0610", "order_id": "d4c305e1-820a-4e47-9626-2409b4a7b075", "quantity": "14.00", "price": "0.3000"}On a cash out the price is amount / quantity = 4.20 / 14 = 0.3000, rounded down to the tick. The pushes are the same as for the buy: placed for the player, quote_accepted and placed for maker A, negotiation_closed with placed_elsewhere for maker B, rfq_closed with placed for every maker. Once the position is sold, another cash_out raises STXComboException with reason == "nothing_to_sell".
How a request ends
Section titled “How a request ends”negotiation_closed on rfq tells the player a request ended without an order. On combo_quote it is sent once per quote and carries the maker’s quote_id and the player’s external_id. rfq_closed tells every maker the request ended, for any reason. All captured on the same run:
| What happened | Player gets (rfq) |
Quoting maker gets (combo_quote) |
Other makers |
|---|---|---|---|
| Accepted and placed | the accept reply, placed |
quote_accepted, placed, rfq_closed placed |
negotiation_closed placed_elsewhere if they quoted, rfq_closed placed |
rfq.cancel(rfq_id) |
CancelledRequest, negotiation_closed cancelled |
negotiation_closed cancelled, rfq_closed cancelled |
rfq_closed cancelled |
| The channel that opened it leaves or drops | negotiation_closed cancelled on every rfq socket, the one that left included |
negotiation_closed cancelled, rfq_closed cancelled |
rfq_closed cancelled |
| Five minutes pass | negotiation_closed expired |
negotiation_closed expired, rfq_closed expired |
rfq_closed expired |
| A leg stops trading before the accept | accept raises leg_not_quotable, negotiation_closed leg_not_quotable |
quote_accepted, negotiation_closed leg_not_quotable, rfq_closed leg_not_quotable |
rfq_closed leg_not_quotable |
| The player cannot fund it | accept raises player_refused, negotiation_closed player_refused |
quote_accepted, negotiation_closed player_refused, rfq_closed player_refused |
rfq_closed player_refused |
| The maker cannot fund it | accept raises STXComboPriceMovedException with an empty best_quote; the request stays open |
quote_accepted, negotiation_closed maker_refused |
nothing |
A cancel:
await rfq.cancel("0ae284d1-afa8-4084-8289-7e0c76f49be6"){"cancelled": "0ae284d1-afa8-4084-8289-7e0c76f49be6"}and the player’s negotiation_closed, where external_id and quote_id are None on the player’s side:
{"rfq_id": "0ae284d1-afa8-4084-8289-7e0c76f49be6", "market_id": null, "reason": "cancelled", "external_id": null, "quote_id": null}A leg closing: a request on the BTC and ETH markets of 04:30 to 04:45 UTC, opened at 04:42:30 and accepted at 04:45:20, after both legs closed. accept raises and the player is told:
accept on rfq:c7a6abb5-5e79-489d-951f-c043d4377fac refused: leg_not_quotable{"rfq_id": "7928dea2-b12e-4778-843e-9671629f185a", "market_id": null, "reason": "leg_not_quotable", "external_id": null, "quote_id": null}Maker A, whose quote was accepted, gets quote_accepted and then:
{"rfq_id": "7928dea2-b12e-4778-843e-9671629f185a", "market_id": null, "reason": "leg_not_quotable", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "quote_id": "8cef87c9-0a0d-4746-bfb4-49a6b31d42d6"}After the legs close, request_quote on them and cash_out of the combo both raise leg_not_quotable.
A player that cannot fund the accept: the player held 11 contracts and opened two cash outs of them, d91050c4-... and 3f418a9e-..., each for all 11. The first sold. Accepting the second found nothing left to sell:
try: await rfq.accept("3f418a9e-e76f-4408-87c4-af59e815b1bb", "7a6872f8-d9e0-45b9-8605-0c90a4e37257", "3.30")except stx.STXComboException as exc: print(exc) print(exc.reason, exc.detail, exc.raw_reason)accept on rfq:c7a6abb5-5e79-489d-951f-c043d4377fac refused: player_refused:nothing_to_sellplayer_refused nothing_to_sell player_refused:nothing_to_sellA maker that cannot fund the accept: maker A quoted a $1,000,000.00 win on a $20.00 stake, which it could not cover. The player’s accept raised STXComboPriceMovedException with every field of best_quote None, because the failed quote was dropped, and the request stayed open with nothing quoted. Maker A got quote_accepted and then:
{"rfq_id": "20013a61-1e35-488e-b804-58010179ace2", "market_id": null, "reason": "maker_refused", "external_id": "9c87ec1c-588e-4512-9b91-d71653e41458", "quote_id": "fda28220-562c-4403-91cb-c8f474baac4c"}A maker’s quote can also end on its own: a quote placed with expires_in_ms=400 gave maker A negotiation_closed with reason expired and its quote_id, while the request stayed open for other quotes.
import asyncio
import stxfrom stx import AsyncSTX
async def main(): async with AsyncSTX() as client: async with client.websocket() as ws: try: rfq = await ws.rfq() # as a player quotes = await ws.combo_quote() # as a maker except stx.STXCombosUnavailableException: print("Combos are not available on this environment.") return print(rfq.join_reply.external_id)
asyncio.run(main())9c87ec1c-588e-4512-9b91-d71653e41458ws.rfq() returns an RfqChannel and ws.combo_quote() a ComboQuoteChannel. One account can join both channels, and can join rfq from several sockets; each socket gets the pushes.
On an exchange without combos the join raises STXCombosUnavailableException, and the socket and your other channels are unaffected. A read-only key is refused with STXComboException, whose reason is the exchange’s sentence:
STXComboException: phx_join on rfq:bc2d4b34-5d21-4a2a-8af6-d1ad3846ac3f refused: This API key does not have write accessRequest a quote and accept it
Section titled “Request a quote and accept it”import asyncio
import stxfrom stx import AsyncSTXfrom stx.combos import ComboLeg
async def main(): async with AsyncSTX() as client: async with client.websocket() as ws: rfq = await ws.rfq() request = await rfq.request_quote( [ ComboLeg(market_id="70e7b102-1dd7-4236-9665-6f645303fcb1", outcome="won"), ComboLeg(market_id="4e3243fe-b3f5-4264-a586-73d6b512eb86", outcome="won"), ], amount="5.00", ) async for msg in rfq: if getattr(msg.data, "rfq_id", None) != request.rfq_id: continue if msg.event == "best_quote" and msg.data.amount is not None: best = msg.data print(f"win {best.amount} on a 5.00 stake") try: placed = await rfq.accept(request.rfq_id, best.quote_id, best.amount) except stx.STXComboPriceMovedException as exc: print("best is now", exc.best_quote.amount) continue print(placed.market_id, placed.price, placed.quantity) break if msg.event == "negotiation_closed": print("closed:", msg.data.reason) break
asyncio.run(main())win 10.0000 on a 5.00 stakeb66e1763-caa7-49a3-9ec5-2287e81d0610 0.3334 14.00- Legs are
ComboLegobjects, mappings withmarket_idandoutcome, or(market_id, outcome)pairs.outcomeis"won"or"lost". At least two legs, each a different market; too many raisecombo_maximum_legs_exceeded. amountis a string orDecimalin whole cents. A float raisesTypeError: amount must be a decimal string such as '25.00', got floatbefore anything is sent.best_quotepushes for every open request arrive on the same channel, so filter onrfq_idwhen you have more than one.- After the accept the combo is an ordinary market: follow it on the
orders,fills,positionsandsettlementschannels, addressed byplaced.market_id. snapshot(rfq_id)reads the request and the live best at once, without waiting for a push, andcancel(rfq_id)ends it.- A request belongs to the channel that opened it. Leaving the channel or losing the connection cancels it, and a reconnect does not restore it.
To sell what you hold of a combo, call cash_out(market_id) and accept the same way:
out = await rfq.cash_out("b66e1763-caa7-49a3-9ec5-2287e81d0610")print(out.rfq_id, out.quantity)6d2d176a-589a-4cbc-9e72-9feba03f3a89 14.00Quote as a maker
Section titled “Quote as a maker”import asyncio
from stx import AsyncSTX
async def main(): async with AsyncSTX() as client: async with client.websocket() as ws: quotes = await ws.combo_quote() async for msg in quotes: if msg.event == "rfq_opened" and msg.data.side == "buy": request = msg.data names = ", ".join(leg.short_name for leg in request.legs) print(f"{names}: stake {request.amount}, tick {request.tick_size}") quote = await quotes.quote( request.rfq_id, "10.00", 60_000, client_order_id="docs-sdk-m1" ) print("quoted", quote.quote_id) elif msg.event == "placed": print("placed at", msg.data.price, "x", msg.data.quantity) elif msg.event == "negotiation_closed": print("quote", msg.data.quote_id, "ended:", msg.data.reason)
asyncio.run(main())BTC 15 min, ETH 15 min: stake 5.0000, tick 0.0001000quoted d675be64-f34d-40a6-b454-f004bbb1d272placed at 0.3334 x 14.00rfq_openedarrives for every request opened after you join; earlier ones are not replayed. Onbuy,amountis the player’s stake and your quote is what the player wins. Onsell(a cash out),quantityis the size and your quote is what you pay for all of it. Price against the request’smax_priceandtick_size.expires_in_msis an int of milliseconds; anything else raisesTypeError: expires_in_ms must be an int of milliseconds. To change a quote,withdraw_quote(rfq_id, quote_id)and quote again.quote_acceptedcomes before the exchange re-checks the legs and both sides’ funds, so it can be followed bynegotiation_closed(leg_not_quotable,player_refused,maker_refused) instead ofplaced.- A quote belongs to the channel that placed it: leaving the channel or losing the connection withdraws it.
A complete maker that quotes every request until Ctrl-C and reports how each one ends is examples/combo_maker.py; Quote combos as a maker walks through it.
Errors
Section titled “Errors”A refused command raises STXComboException. str(exc) names the command, the topic and the exchange’s reason, reason is the reason, and command is the refused command. A reason with a detail is split on the first colon: player_refused:nothing_to_sell gives reason == "player_refused" and detail == "nothing_to_sell", and raw_reason keeps the original.
try: await rfq.accept(request.rfq_id, best.quote_id, best.amount)except stx.STXComboPriceMovedException as exc: print("best moved to", exc.best_quote.amount) # the request is still openexcept stx.STXComboException as exc: print(exc.reason, exc.detail)| Exception | When |
|---|---|
STXCombosUnavailableException |
joining on an environment without combos; reason == "unmatched topic" |
STXComboPriceMovedException |
accept found the best below your amount; best_quote is the live best, with every field None when nothing is live |
STXComboException |
any other refused join or command |
All three extend STXChannelException. A reply that does not arrive in time raises STXChannelException as on other channels; the command can still take effect, so check with snapshot. Joining a channel twice on one socket raises STXChannelException with Already joined rfq:<user_id>.
Every refusal below was raised by stx-python 0.7.1 on US demo, as STXComboException unless noted. For example, str(exc) for the first row is request_quote on rfq:c7a6abb5-5e79-489d-951f-c043d4377fac refused: combo_minimum_two_markets_needed.
| Call | reason |
|---|---|
request_quote with one leg |
combo_minimum_two_markets_needed |
request_quote with six legs |
combo_maximum_legs_exceeded |
request_quote with a leg id that is not a UUID |
malformed_id |
request_quote with an outcome other than won or lost |
unknown_outcome |
request_quote with a market that does not exist, or a combo as a leg |
unknown_leg |
request_quote with both outcomes of one market |
duplicate_legs |
request_quote with amount="0" |
non_positive_amount |
request_quote with amount="5.001" |
sub_cent_amount |
request_quote with amount="five" |
malformed_money |
request_quote for more than the balance |
insufficient_assets |
request_quote on legs that stopped trading |
leg_not_quotable |
snapshot, cancel or accept on a request that ended or never existed |
not_found |
snapshot, cancel or accept on another player’s request |
not_the_requester |
accept above the best |
price_moved (STXComboPriceMovedException) |
accept that the player cannot fund |
player_refused, with a detail such as nothing_to_sell |
accept after a leg stopped trading |
leg_not_quotable |
cash_out of an ordinary market or an unknown id |
unknown_combination |
cash_out of a combo whose legs closed |
leg_not_quotable or market_not_quotable |
cash_out with nothing held |
nothing_to_sell |
quote or withdraw_quote on a request that ended or never existed |
not_found |
quote on your own request |
cannot_quote_own_request |
quote with amount="0" |
non_positive_amount |
quote outside what the size can pay, or "0.0000001" |
price_out_of_range |
quote that buys less than one contract |
below_one_contract |
quote with expires_in_ms=0 |
expires_at_required |
quote with a client_order_id over 255 characters |
malformed_client_order_id |
withdraw_quote of a quote that is gone or not yours |
unknown_quote |
| joining with a read-only key | This API key does not have write access |
Every reason, including the ones the SDK’s typed arguments rule out, is listed in Combo negotiation channels.

