Skip to content

Trading

wss://inflection.exchange/ws/trade

Sign the handshake with your API key, as a GET of /ws/trade (see API keys). Sign each place, modify and cancel with a session key (see Signing orders).

On connecting you get auth_success, then open_orders with everything you have resting. After that you get an update for each thing that happens to your orders, market_closed for every market that stops trading, and market_settled for every market that settles.

Name each order with your own client_order_id. Do not reuse one in a market, even after the order filled or was cancelled. Every update about the order carries it back.

A revoked key’s socket gets an error and is closed.

You can also ask for a withdrawal on this socket with withdraw. Sign it with a session key, like an order. The answer comes back to the session that asked, as withdrawal_reserved, withdrawal_refused or withdrawal_unknown.

place. Place an order.

A limit order needs price_bps. A market order takes whatever is on the book up to quantity and never rests: set time_in_force to ioc or fok.

For a market order, price_bps is its worst price. It fills at that price or better, and drops the rest as order_unfilled, like an ioc order. Leave price_bps out, or send 0, to take any price. A fok market order that cannot fill completely inside its worst price is rejected with market_would_not_fully_execute.

quantity_unit: cash sizes a buy in money instead of lots: quantity is then units (1,000,000 to the dollar). Only bids can be sized in cash.

time_in_force is gtc (rests until cancelled or the market closes), ioc (fills what it can now, drops the rest), fok (fills completely or not at all), or {"gtd": {"expiry_ms": ...}} (rests until then, at least 4 seconds ahead of the gateway’s clock). A gtd order never fills at or after its expiry_ms. An expiry_ms less than 4 seconds ahead is rejected with expiry_too_soon.

post_only rejects the order if any of it would fill on arrival.

Your account must have the collateral for the worst case of the order, including the most taker fee its fills can pay, or it is rejected with insufficient_collateral.

A place whose max_fee_bps is below the market’s taker_fee_rate_bps is rejected with max_fee_below_market_rate. A market that charges a taker fee therefore rejects max_fee_bps: 0. Each fill then carries its fee. See Markets.

Sign the order with a session key. The signature covers every field of the order. Use a new client_order_id for each order in a market: the exchange never takes the same id twice, so it never takes the same signed order twice. See Signing orders.

Field Type Required
type "place" yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
side bid | ask yes bid buys the outcome, ask sells it.
outcome up | down yes
price_bps integer no For the named outcome, in basis points of $1. A limit order needs it, from 1 to 9999, on the 100 bps (1¢) grid. For a market order it is the worst price, from 1 to 9999. 0 or absent means no worst price.
quantity integer yes Lots, or units with quantity_unit: cash.
quantity_unit lots | cash no Default lots.
order_type limit | market no Default limit.
time_in_force gtc | ioc | fok or object no Default gtc.
post_only boolean no Default false.
max_fee_bps integer no The most fee you accept on each fill, in basis points of the fill’s cost. 0 accepts no fee. Sign at least the market’s taker_fee_rate_bps: a lower number is rejected with max_fee_below_market_rate. The signature covers it. Default 0.
session_key string yes The address of the session key that signed the message: 0x and 40 hex digits.
signature string yes An ECDSA P-256 SHA-256 signature of the message’s EIP-712 digest: 0x, then r and s, each 32 bytes in 64 hex digits. s is at most n/2 (low s).
{
"type": "place",
"client_order_id": 1760270400123456,
"market_id": 10350205052,
"side": "bid",
"outcome": "up",
"price_bps": 5500,
"quantity": 1000,
"max_fee_bps": 0,
"session_key": "0x9f2e642877a9411849763c250e5bb36cc57fedea",
"signature": "0xc113dfe8a89e1c5c696c3a68dc1b535b9f27c85d2bb0df62aba8fadcd843ecdb48f6d41a55c1a80685bddff6887a480d0393e9b3ed154e01da2cf32bc23ca791"
}

modify. Change a resting order’s price or size.

quantity is the new remaining size. Lowering the size at the same price keeps your place in the queue. Anything else replaces the order: it loses its place and is matched again at the new price, so it can fill.

version is the version this change makes: the order’s current version plus 1. A place makes version 0, and each change the exchange applies adds 1. A fill does not change it. Read the current version from the version of the order’s last order_modified, or from its entry in open_orders. If version is not the current version plus 1, the change is rejected with version_changed. This is how the exchange takes each signed change once.

Sign the change with a session key. See Signing orders.

Field Type Required
type "modify" yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
version integer yes The order’s current version plus 1.
price_bps integer yes Price for the named outcome, in basis points of $1. Markets trade on a 100 bps (1¢) grid.
quantity integer yes
session_key string yes The address of the session key that signed the message: 0x and 40 hex digits.
signature string yes An ECDSA P-256 SHA-256 signature of the message’s EIP-712 digest: 0x, then r and s, each 32 bytes in 64 hex digits. s is at most n/2 (low s).
{
"type": "modify",
"client_order_id": 1760270400123456,
"market_id": 10350205052,
"version": 1,
"price_bps": 5400,
"quantity": 500,
"session_key": "0x9f2e642877a9411849763c250e5bb36cc57fedea",
"signature": "0xd073ef1f766d2cb660f5b3caa7ac9108715f88e09034a004f925ff1ca7a69050750de77a09adaf97d2c004657b1c9966990b2f7f1e8b3127319aa6bb889d7ded"
}

cancel. Cancel a resting order.

Sign the cancel with a session key. See Signing orders.

Field Type Required
type "cancel" yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
session_key string yes The address of the session key that signed the message: 0x and 40 hex digits.
signature string yes An ECDSA P-256 SHA-256 signature of the message’s EIP-712 digest: 0x, then r and s, each 32 bytes in 64 hex digits. s is at most n/2 (low s).
{
"type": "cancel",
"client_order_id": 1760270400123456,
"market_id": 10350205052,
"session_key": "0x9f2e642877a9411849763c250e5bb36cc57fedea",
"signature": "0xbbddff3bd911ba5f06dd0848619606534a2a14b3fe3b014fbc0037d78e325d2e18bad8c36b9120e8d5da7fe93e4cc9fd7c5dfacbd3f634d0fc435f5e6bb9da87"
}

withdraw. Ask to take cash out of the exchange.

Sign the withdrawal with a session key. The signed message is Withdraw(uint64 accountId, uint64 toAccountId, uint64 amount, uint64 nonce) with toAccountId 0. See Signing orders.

amount is in ledger units, which have 6 decimals. 25000000 is 25 cash. nonce is your account’s next withdrawal number. Read it as next_nonce from GET /accounts/{id}/withdrawals. A nonce is 1 or more, and below 2^53.

The message has no account id. The gateway uses the account of your session. A field it does not know, such as account_id or wallet, gets an error.

The gateway answers with withdrawal_reserved, withdrawal_refused or withdrawal_unknown. A withdrawal counts against the same rate limit as an order.

Field Type Required
type "withdraw" yes
amount integer yes The cash to take out, in ledger units. It must be more than 0.
nonce integer yes The next_nonce of your account’s withdrawals.
session_key string yes The address of the session key that signed the message: 0x and 40 hex digits.
signature string yes An ECDSA P-256 SHA-256 signature of the message’s EIP-712 digest: 0x, then r and s, each 32 bytes in 64 hex digits. s is at most n/2 (low s).
{
"type": "withdraw",
"amount": 25000000,
"nonce": 1,
"session_key": "0x9f2e642877a9411849763c250e5bb36cc57fedea",
"signature": "0x562db066015b0d4943bf68f8d4393b3ee7be0dc810a90f0333e8a505133b9d943d57549273883bf13dbfc39c6e03138d44f6b703cb0a40759a934c5147058aa0"
}

The answer to a withdraw. Only the session that asked gets it.

withdrawal_reserved. The exchange holds the amount for your withdrawal.

Field Type Always sent
type "withdrawal_reserved" yes
account_id integer yes
amount integer yes The amount held, in ledger units.
digest string yes The digest that you signed, as 0x and 64 hex digits.
nonce integer yes The nonce of the withdrawal.
{
"account_id": 7,
"amount": 25000000,
"digest": "0x5c9da89a3eec7ce74e1d9803a4c933e9317c909945bec1c01b607b35cc4480d2",
"nonce": 1,
"type": "withdrawal_reserved"
}

withdrawal_refused. The withdrawal was refused. Nothing is held.

reason says why:

reason Means
signature_required session_key or signature is missing.
session_key_unknown The gateway has no live key with this address on your account.
signature_invalid The signature does not match the withdrawal and the key, or its s is above n/2.
session_key_expired The key has expired, or it expires in 5 seconds or less.
zero_amount amount is 0.
nonce_out_of_range nonce is 0 or 2^53 or more.
rate_limited The account sent too many orders and withdrawals. Wait, then send it again.
unavailable The exchange cannot check the withdrawal now. Send it again later.
insufficient_collateral Your cash, less what your orders and other withdrawals hold, is less than amount.
awaiting_the_vault Some of your fills are not yet confirmed by the vault. Send it again in about 25 seconds.
withdrawals_not_available This exchange runs without the vault, so it pays no withdrawals.
no_linked_wallet Your account has no linked wallet to pay.
nonce_not_next nonce is not your account’s next number. The answer carries next_nonce.
digest_used The vault has already used this digest.
Field Type Always sent
type "withdrawal_refused" yes
account_id integer yes
nonce integer yes The nonce of the withdrawal that was refused.
reason signature_required | session_key_unknown | signature_invalid | session_key_expired | zero_amount | nonce_out_of_range | rate_limited | unavailable | insufficient_collateral | awaiting_the_vault | withdrawals_not_available | no_linked_wallet | nonce_not_next | digest_used yes
next_nonce integer no Only with nonce_not_next. Sign the withdrawal again with this nonce.
{
"account_id": 7,
"nonce": 1,
"reason": "zero_amount",
"type": "withdrawal_refused"
}

withdrawal_unknown. The gateway lost its link to the ledger before it had an answer.

The withdrawal may or may not be reserved. Do not send it again at once. Read GET /accounts/{id}/withdrawals first, to see whether the ledger has it.

Field Type Always sent
type "withdrawal_unknown" yes
account_id integer yes
nonce integer yes The nonce of the withdrawal that has no answer.
{
"account_id": 7,
"nonce": 1,
"type": "withdrawal_unknown"
}

Sent when you connect, and when something is wrong with a message you sent.

auth_success. First message after the handshake.

Field Type Always sent
type "auth_success" yes
account_id integer yes
time_ms integer yes The gateway’s clock when it let you in, in Unix milliseconds. Use it to check that the expiry_ms of a gtd order is at least 4 seconds ahead of the gateway’s clock.
{
"account_id": 1,
"time_ms": 1700000000000,
"type": "auth_success"
}

open_orders. Everything you have resting, sent once after auth_success.

Each order has its version. Use it to modify an order that you placed before you connected.

Field Type Always sent
type "open_orders" yes
orders list of OpenOrder yes
{
"orders": [
{
"client_order_id": 7,
"expiry_ms": 1700000300000,
"market_id": 1,
"outcome": "up",
"price_bps": 5000,
"quantity": 25,
"remaining_quantity": 10,
"side": "bid",
"version": 2
}
],
"type": "open_orders"
}

error. A message could not be read, or the key was revoked.

Field Type Always sent
type "error" yes
message string yes
{
"message": "something went wrong",
"type": "error"
}

What happens to your orders, in the order it happened.

order_accepted. The engine took the order. Fills, a rest or an unfilled remainder follow.

Field Type Always sent
type "order_accepted" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
{
"account_id": 1,
"client_order_id": 7,
"market_id": 1,
"seq": 1,
"type": "order_accepted"
}

order_rested. What was left after filling is now on the book.

Field Type Always sent
type "order_rested" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
side bid | ask yes bid buys the outcome, ask sells it.
outcome up | down yes
price_bps integer yes Price for the named outcome, in basis points of $1. Markets trade on a 100 bps (1¢) grid.
quantity integer yes Lots resting.
expiry_ms integer or null yes
{
"account_id": 1,
"client_order_id": 7,
"expiry_ms": null,
"market_id": 1,
"outcome": "up",
"price_bps": 5000,
"quantity": 10,
"seq": 3,
"side": "bid",
"type": "order_rested"
}

fill. Some or all of your order traded.

Field Type Always sent
type "fill" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
side bid | ask yes bid buys the outcome, ask sells it.
outcome up | down yes
price_bps integer yes The price it traded at, which can be better than your limit.
quantity integer yes Lots in this fill.
remaining_quantity integer yes What is left of the order after it. 0 means it is done.
fee integer no The fee this fill cost you, in units (1,000,000 to the dollar). A negative number is a rebate paid to you. It is always sent, except for a fill that the exchange could not charge.
liquidity taker | maker no taker if your order took this fill from the book, maker if it was resting. Always sent with fee.
{
"account_id": 1,
"client_order_id": 7,
"fee": 875,
"liquidity": "taker",
"market_id": 1,
"outcome": "up",
"price_bps": 5000,
"quantity": 10,
"remaining_quantity": 0,
"seq": 7,
"side": "bid",
"type": "fill"
}

order_modified. Your change took effect.

retained_priority: true means the order kept its place. false means it was replaced, and fills or a new rest may follow.

version is the order’s version after the change. Your next modify of the order signs and sends this plus 1. Every connection of your account gets this message.

Field Type Always sent
type "order_modified" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
side bid | ask yes bid buys the outcome, ask sells it.
outcome up | down yes
price_bps integer yes Price for the named outcome, in basis points of $1. Markets trade on a 100 bps (1¢) grid.
quantity integer yes 100 lots make one share.
retained_priority boolean yes
version integer yes The order’s version after this change. It is the version of the modify.
{
"account_id": 1,
"client_order_id": 7,
"market_id": 1,
"outcome": "up",
"price_bps": 4500,
"quantity": 30,
"retained_priority": false,
"seq": 5,
"side": "bid",
"type": "order_modified",
"version": 1
}

order_cancelled. A resting order, or part of one, left the book.

reason is one of client_request, expired (a gtd order reached its time; this can come when another order would have matched it, or when you modify it), market_closed, self_trade (your new order would have traded with this one, so this one was cancelled), replaced (by a modify) or held_for_withdrawal (the exchange cancelled the order because your cash is held for a pending timed exit and no longer covers it).

Field Type Always sent
type "order_cancelled" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
cancelled_quantity integer yes 100 lots make one share.
reason client_request | expired | market_closed | self_trade | replaced | held_for_withdrawal yes
price_bps integer yes Price for the named outcome, in basis points of $1. Markets trade on a 100 bps (1¢) grid.
side bid | ask yes bid buys the outcome, ask sells it.
outcome up | down yes
{
"account_id": 1,
"cancelled_quantity": 10,
"client_order_id": 7,
"market_id": 1,
"outcome": "up",
"price_bps": 5000,
"reason": "market_closed",
"seq": 4,
"side": "bid",
"type": "order_cancelled"
}

order_unfilled. Part of an order could neither fill nor rest, and was dropped.

reason is immediate_or_cancel, market_no_liquidity (a market order found nothing more to take, at its worst price or better), market_remainder_below_one_lot (a cash-sized market order had less than one lot’s worth left) or limit_remainder_below_one_lot (a cash-sized limit order had too little left to rest one lot at its price).

Field Type Always sent
type "order_unfilled" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
unfilled_quantity integer yes
quantity_unit lots | cash yes
reason immediate_or_cancel | market_no_liquidity | market_remainder_below_one_lot | limit_remainder_below_one_lot yes
{
"account_id": 1,
"client_order_id": 7,
"market_id": 1,
"quantity_unit": "lots",
"reason": "immediate_or_cancel",
"seq": 6,
"type": "order_unfilled",
"unfilled_quantity": 4
}

order_rejected. The order, change or cancel was refused. Nothing happened.

From the engine: price_off_tick, zero_quantity, quantity_too_large, book_full, unknown_order, duplicate_client_order_id, market_not_open, post_only_would_cross, fill_or_kill_would_not_fill, market_would_not_fully_execute, unsupported_quantity_unit, market_cannot_rest, not_expirable, not_yet_expired, expiry_passed, version_changed.

market_not_open means the market is not taking orders: it is not listed, or it is closed or settled. A place or modify is also refused from the market’s end, by the exchange’s own clock. A cancel still works from the market’s end until the market closes. expiry_passed means a gtd order’s expiry_ms is already past. version_changed means the version of a modify is not the order’s current version plus 1.

From the gateway, before the engine sees it (with no seq): market_not_open (the same cases as above), market_halted (trading is halted: a place or modify is refused, and a cancel is still taken), expiry_too_soon (the expiry_ms of a gtd order is less than 4 seconds ahead of the gateway’s clock), max_fee_below_market_rate (the max_fee_bps of a place is below the market’s taker rate; the message also carries taker_fee_rate_bps, the rate to sign at least), insufficient_collateral, rate_limited, client_order_id_too_large, market_id_too_large, an invalid price or an expiry_ms above 2^63 − 1, or the exchange being briefly unavailable. Those last are sentences, not codes.

From the gateway’s check of the signature (with no seq):

reason Means
signature_required session_key or signature is missing.
session_key_unknown The gateway has no live key with this address on your account. It was never registered, it is another account’s, or it was revoked. The request can also be before the key’s issued_at_ms. Or the gateway has had no list of keys for 5 seconds: a place or modify is refused, and you send it again. A cancel from a key the gateway already held still works.
session_key_expired The key has expired, or it expires in 5 seconds or less.
signature_invalid The signature does not match the message and the key, or its s is above n/2.
Field Type Always sent
type "order_rejected" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer no The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
account_id integer yes
client_order_id integer yes Your name for the order. The exchange takes each one once in a market: a second place with the same id is rejected, even if the first order has filled or was cancelled.
reason string yes
taker_fee_rate_bps integer no Only with max_fee_below_market_rate: the market’s taker rate. Sign a max_fee_bps of at least this.
{
"account_id": 1,
"client_order_id": 7,
"market_id": 1,
"reason": "expiry_too_soon",
"type": "order_rejected"
}

market_closed. The market’s window ended and it no longer trades.

Your resting orders in it are cancelled. Your positions stay open until market_settled.

Field Type Always sent
type "market_closed" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
{
"market_id": 1,
"seq": 9,
"type": "market_closed"
}

market_settled. The market settled. outcome says how.

Comes after market_closed, once the vault has resolved the market. Your positions pay out in your account.

outcome is up or down for the winner. Each lot of the winner pays 10,000 units, and each lot of the other outcome pays nothing.

outcome is void when no price could decide the market. Each lot you hold pays 5,000 units ($0.005), on either side, and your fills stand. A share of 100 lots pays $0.50.

Field Type Always sent
type "market_settled" yes
market_id integer yes The market’s market_id from the markets API. The markets service builds it from the market’s series and end time. An id is never reused.
seq integer yes The event’s sequence number. Unique and increasing across the exchange, so you will see gaps.
outcome up | down | void yes How a market settled. up and down name the winner. A lot of the winner pays 10,000 units, and a lot of the other outcome pays nothing. With void no price decided the market, and every lot, Up or Down, pays 5,000 units.
{
"market_id": 1,
"outcome": "up",
"seq": 10,
"type": "market_settled"
}