curl -N https://api.totalis.trade/v1/mm/quote-requests/stream \
-H "X-API-Key: $API_KEY" \
-H "Accept: text/event-stream"
// Native EventSource cannot send an X-API-Key header, so read the SSE stream
// with fetch instead (works in browsers and Node 18+).
const res = await fetch('https://api.totalis.trade/v1/mm/quote-requests/stream', {
headers: { 'X-API-Key': API_KEY, Accept: 'text/event-stream' },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let sep;
while ((sep = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
const event = frame.match(/^event: (.+)$/m)?.[1];
const data = frame.match(/^data: (.+)$/m)?.[1];
if (!event || !data) continue;
const payload = JSON.parse(data);
switch (event) {
case 'connected':
console.log('Connected as', payload.mm_id);
break;
case 'quote_request':
// payload.bet_amount is the gross wager; price + size against net user_stake
console.log('New request:', payload.id, payload.user_stake, 'USDC');
break;
case 'quote_request:updated':
// Your prior-version quote is invalidated; submit a new one
console.log('Request updated to version', payload.version, 're-price:', payload.id);
break;
case 'quote_request_expired': {
// reason: 'committed' | 'cancelled' | 'expired'; won: true only if you were selected.
// won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
const { id, reason, won } = payload;
const outcome = reason === 'committed'
? (won === undefined ? 'unknown' : won ? 'won' : 'lost (out-bid)')
: reason;
console.log('Request closed:', id, outcome);
break;
}
case 'cashout_request':
// Distinct from quote_request: the key is payload.cashout_request_id (NOT payload.id).
// Broadcast to every MM, ~10s to bid. Re-value the position and bid ONE all-in
// price_micro (what the user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
console.log('Cashout auction:', payload.cashout_request_id, 'position', payload.position_id);
break;
}
}
}
import requests
import json
url = 'https://api.totalis.trade/v1/mm/quote-requests/stream'
headers = {
'X-API-Key': API_KEY,
'Accept': 'text/event-stream',
}
with requests.get(url, headers=headers, stream=True) as resp:
event_type = None
for line in resp.iter_lines(decode_unicode=True):
if line.startswith('event: '):
event_type = line[7:]
elif line.startswith('data: '):
payload = json.loads(line[6:])
if event_type == 'quote_request':
print(f"New request: {payload['id']}")
# Price the legs and submit a quote
elif event_type == 'quote_request:updated':
print(f"Request {payload['id']} updated to v{payload['version']}, re-price")
# Prior-version quote invalidated; submit a new one
elif event_type == 'quote_request_expired':
# reason: 'committed' | 'cancelled' | 'expired'; won True only if you were selected.
# won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
reason = payload.get('reason', 'expired')
won = payload.get('won')
if reason == 'committed':
outcome = 'unknown' if won is None else ('won' if won else 'lost (out-bid)')
else:
outcome = reason
print(f"Request closed: {payload['id']}: {outcome}")
elif event_type == 'cashout_request':
# Distinct from quote_request: the key is cashout_request_id (NOT id).
# Broadcast to every MM, ~10s to bid. Bid ONE all-in price_micro (what the
# user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
print(f"Cashout auction: {payload['cashout_request_id']} position {payload['position_id']}")
event_type = None
event: connected
data: {"mm_id":"did:privy:cm3mm..."}
event: snapshot_begin
data: {"page_size":500}
event: quote_request
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":25,"user_stake":24.75,"version":1,"request_hash":"sha256:9f86d08...","expires_at":"2026-06-01T18:45:30.000Z"}
event: snapshot_complete
data: {"pages":1,"total":1}
event: quote_request
data: {"id":"f7e8d9c0-1234-5678-abcd-ef0123456789","user_id":"did:privy:cm9xyz...","input_legs":[{"market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"legs":[{"id":"d4e5f6a7-8901-23bc-def4-567890abcdef","event_ticker":"KXETH-26JUN01","market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"bet_amount":10,"user_stake":9.90,"version":1,"request_hash":"sha256:b5c6d7e...","expires_at":"2026-06-01T18:50:00.000Z"}
event: quote_request:updated
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":50,"user_stake":49.50,"version":2,"request_hash":"sha256:3b1f9c2...","expires_at":"2026-06-01T18:46:10.000Z"}
event: quote_request_expired
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","reason":"committed","won":false}
Market maker
Stream quote requests
Subscribe to all active quote requests in real time via Server-Sent Events.
GET
/
v1
/
mm
/
quote-requests
/
stream
curl -N https://api.totalis.trade/v1/mm/quote-requests/stream \
-H "X-API-Key: $API_KEY" \
-H "Accept: text/event-stream"
// Native EventSource cannot send an X-API-Key header, so read the SSE stream
// with fetch instead (works in browsers and Node 18+).
const res = await fetch('https://api.totalis.trade/v1/mm/quote-requests/stream', {
headers: { 'X-API-Key': API_KEY, Accept: 'text/event-stream' },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let sep;
while ((sep = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
const event = frame.match(/^event: (.+)$/m)?.[1];
const data = frame.match(/^data: (.+)$/m)?.[1];
if (!event || !data) continue;
const payload = JSON.parse(data);
switch (event) {
case 'connected':
console.log('Connected as', payload.mm_id);
break;
case 'quote_request':
// payload.bet_amount is the gross wager; price + size against net user_stake
console.log('New request:', payload.id, payload.user_stake, 'USDC');
break;
case 'quote_request:updated':
// Your prior-version quote is invalidated; submit a new one
console.log('Request updated to version', payload.version, 're-price:', payload.id);
break;
case 'quote_request_expired': {
// reason: 'committed' | 'cancelled' | 'expired'; won: true only if you were selected.
// won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
const { id, reason, won } = payload;
const outcome = reason === 'committed'
? (won === undefined ? 'unknown' : won ? 'won' : 'lost (out-bid)')
: reason;
console.log('Request closed:', id, outcome);
break;
}
case 'cashout_request':
// Distinct from quote_request: the key is payload.cashout_request_id (NOT payload.id).
// Broadcast to every MM, ~10s to bid. Re-value the position and bid ONE all-in
// price_micro (what the user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
console.log('Cashout auction:', payload.cashout_request_id, 'position', payload.position_id);
break;
}
}
}
import requests
import json
url = 'https://api.totalis.trade/v1/mm/quote-requests/stream'
headers = {
'X-API-Key': API_KEY,
'Accept': 'text/event-stream',
}
with requests.get(url, headers=headers, stream=True) as resp:
event_type = None
for line in resp.iter_lines(decode_unicode=True):
if line.startswith('event: '):
event_type = line[7:]
elif line.startswith('data: '):
payload = json.loads(line[6:])
if event_type == 'quote_request':
print(f"New request: {payload['id']}")
# Price the legs and submit a quote
elif event_type == 'quote_request:updated':
print(f"Request {payload['id']} updated to v{payload['version']}, re-price")
# Prior-version quote invalidated; submit a new one
elif event_type == 'quote_request_expired':
# reason: 'committed' | 'cancelled' | 'expired'; won True only if you were selected.
# won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
reason = payload.get('reason', 'expired')
won = payload.get('won')
if reason == 'committed':
outcome = 'unknown' if won is None else ('won' if won else 'lost (out-bid)')
else:
outcome = reason
print(f"Request closed: {payload['id']}: {outcome}")
elif event_type == 'cashout_request':
# Distinct from quote_request: the key is cashout_request_id (NOT id).
# Broadcast to every MM, ~10s to bid. Bid ONE all-in price_micro (what the
# user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
print(f"Cashout auction: {payload['cashout_request_id']} position {payload['position_id']}")
event_type = None
event: connected
data: {"mm_id":"did:privy:cm3mm..."}
event: snapshot_begin
data: {"page_size":500}
event: quote_request
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":25,"user_stake":24.75,"version":1,"request_hash":"sha256:9f86d08...","expires_at":"2026-06-01T18:45:30.000Z"}
event: snapshot_complete
data: {"pages":1,"total":1}
event: quote_request
data: {"id":"f7e8d9c0-1234-5678-abcd-ef0123456789","user_id":"did:privy:cm9xyz...","input_legs":[{"market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"legs":[{"id":"d4e5f6a7-8901-23bc-def4-567890abcdef","event_ticker":"KXETH-26JUN01","market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"bet_amount":10,"user_stake":9.90,"version":1,"request_hash":"sha256:b5c6d7e...","expires_at":"2026-06-01T18:50:00.000Z"}
event: quote_request:updated
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":50,"user_stake":49.50,"version":2,"request_hash":"sha256:3b1f9c2...","expires_at":"2026-06-01T18:46:10.000Z"}
event: quote_request_expired
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","reason":"committed","won":false}
Open a Server-Sent Events (SSE) stream of all active quote requests. This is the primary integration path for market makers; use it instead of polling. You receive new requests, version updates, closes, and early cashout auctions.
Sent once on connection. Confirms your identity.
Sent at the start of the initial snapshot, on a new connection or a reconnect. Every active quote request follows before
Sent when a new quote request is created, and once for each active request replayed during the snapshot. This is the event you price.
Sent when a request you already saw moves to a new
Sent when the initial snapshot finishes. The stream then sends incremental updates only.
Sent when a quote request you saw is no longer active. Remove it from your local state.
The winning maker’s identity and odds are never disclosed.
Sent when a user opens an early cashout auction on a position, and once for each active auction replayed during the snapshot. It is broadcast to every market maker, including on positions you do not back. The auction lives about 10 seconds.
Bid with
Every field below is always present.
Legs can carry extra display fields beyond these. The set is additive and differs by event, so
parse leniently and ignore keys you do not use.
The taker-fee rate is read once per connection. On a long-lived stream,
Authentication
API key required. Pass your API key in theX-API-Key header.
X-API-Key: <key>
SSE event types
The stream sends seven event names. Match them exactly:quote_request and quote_request_expired use underscores, but quote_request:updated uses a colon.
| Event | Fires when |
|---|---|
connected | Once, immediately after connecting. |
snapshot_begin | Start of the initial active request snapshot (on connect or reconnect). |
quote_request | A new quote request is created, or an active one is replayed during the snapshot. |
quote_request:updated | A request you’ve already seen bumps to a new version (legs or bet amount edited). |
snapshot_complete | The initial snapshot finishes; the stream switches to incremental updates only. |
quote_request_expired | A previously seen request is no longer active (committed, cancelled, or expired). |
cashout_request | An early cashout auction opens on a position. Broadcast to every market maker. |
connected
Sent once on connection. Confirms your identity.
mm_id is your Privy DID. It is the same value as the user_id returned by auth:success on the WebSocket, and the key of your mm:quotes:{mm_id} channel there.
{
"mm_id": "did:privy:cm3mm..."
}
snapshot_begin
Sent at the start of the initial snapshot, on a new connection or a reconnect. Every active quote request follows before snapshot_complete.
{
"page_size": 500
}
quote_request
Sent when a new quote request is created, and once for each active request replayed during the snapshot. This is the event you price.
{
"id": "1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40",
"user_id": "did:privy:cm3abc...",
"input_legs": [
{"market_ticker": "KXBTC-26JUN01-T72500", "side": "yes", "venue": "kalshi"},
{"market_ticker": "KXBTC-26JUN01-T73000", "side": "no", "venue": "kalshi"}
],
"legs": [
{
"id": "8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6",
"event_ticker": "KXBTC-26JUN01",
"market_ticker": "KXBTC-26JUN01-T72500",
"side": "yes",
"venue": "kalshi"
},
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"event_ticker": "KXBTC-26JUN01",
"market_ticker": "KXBTC-26JUN01-T73000",
"side": "no",
"venue": "kalshi"
}
],
"bet_amount": 25,
"user_stake": 24.75,
"version": 1,
"request_hash": "sha256:9f86d08...",
"expires_at": "2026-06-01T18:45:30.000Z"
}
quote_request:updated
Sent when a request you already saw moves to a new version because the user edited the legs or the bet amount. The payload matches quote_request, with the incremented version and a new request_hash.
Your quote on the prior version is invalidated automatically. It leaves the book immediately and never blocks a new submission, so you do not need to withdraw it. Re-price and submit a new quote with the new version and request_hash.
A listener registered only for
quote_request misses every version bump. Register one for quote_request:updated too, or branch on the event: field in a raw SSE parser.{
"id": "1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40",
"user_id": "did:privy:cm3abc...",
"input_legs": [ "..." ],
"legs": [ "..." ],
"bet_amount": 50,
"user_stake": 49.50,
"version": 2,
"request_hash": "sha256:3b1f9c2...",
"expires_at": "2026-06-01T18:46:10.000Z"
}
snapshot_complete
Sent when the initial snapshot finishes. The stream then sends incremental updates only.
{
"pages": 1,
"total": 87
}
quote_request_expired
Sent when a quote request you saw is no longer active. Remove it from your local state. reason says why it closed:
reason | Meaning |
|---|---|
committed | The user committed to a quote. Check won. Can arrive well before expires_at; there is no minimum auction duration. |
cancelled | The user abandoned the request before committing to any quote. |
expired | The request reached expires_at with no commit. |
won separates a lost auction from an abandoned or timed-out one:
won | Meaning |
|---|---|
true | reason is committed and your quote was the one selected. |
false | Every other close. On a committed close, you were out-bid: the user took another market maker’s quote. |
| absent | A rare committed close with an indeterminate outcome, for example an event produced by a previous server instance during a deploy. Treat it as unknown, not as a loss. |
{
"id": "1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40",
"reason": "committed",
"won": false
}
cashout_request
Sent when a user opens an early cashout auction on a position, and once for each active auction replayed during the snapshot. It is broadcast to every market maker, including on positions you do not back. The auction lives about 10 seconds.
Bid with PUT /v1/mm/cashout-requests/{id}/quote. To pass, submit nothing; there is no decline endpoint.
This event differs from
quote_request in name and payload. It carries cashout_request_id (not id) and the on-chain position_id. Branch on the SSE event: field, or register a dedicated cashout_request listener.{
"cashout_request_id": "c3d4e5f6-7890-abcd-ef12-34567890abcd",
"position_id": "9f86d08818844f86d08818844f86d088",
"legs": [
{
"id": "8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6",
"event_ticker": "KXBTC-26JUN01",
"market_ticker": "KXBTC-26JUN01-T72500",
"side": "yes",
"venue": "kalshi",
"resolution": "unresolved"
}
],
"user_stake": 24.75,
"mm_risk": 74.25,
"total_payout": 99,
"payout_odds": 4.0,
"version": 1,
"expires_at": "2026-06-01T18:45:40.000Z"
}
string
required
The auction id. Pass it as
{id} when you
bid. This payload has no id field, unlike
quote_request.string
required
The on-chain position id (32 hex chars, no
0x). Look it up in your own book to see whether you
are its counterparty, which determines your economics.array
required
The position’s legs. Each carries
id, event_ticker, market_ticker, side (yes / no),
venue, and resolution.resolution is one of unresolved, won, lost, void. It is your only protection against
pricing an already-decided parlay. void overrides the others: any void leg cancels the whole
position at settlement and refunds the stake, so the position is worth roughly user_stake even
when a sibling leg reads lost. With no void leg, one lost leg makes the parlay worthless.number
required
Decimal USDC. The net stake the counterparty underwrites, the same quantity as on
quote_request. It is the reference point for the exit price: a bid above it means the user
leaves in profit, below it at a loss.number
required
Decimal USDC. The counterparty’s locked risk on the position, for pricing context only. Do not
compute the counterparty bid ceiling from it. The server derives that ceiling from
total_payout - user_stake; the two are stored separately and can disagree in the last decimal.number
required
Decimal USDC. What the position pays if every leg wins, and the hard ceiling on any bid:
price_micro <= round(total_payout * 1e6), or the bid is rejected price_above_max_payout.number
required
The accepted quote’s payout multiplier. Pricing context, not an amount, and never a bid term.
On positions created before this field was carried, it is reconstructed from
total_payout / user_stake and can be off in the 5th or 6th decimal. Do not reconcile against it
exactly.integer
required
The auction’s version,
1 today. A cashout auction is never re-versioned mid-flight the way a
forward quote request can be, so there is no cashout_request:updated event to handle.string
required
ISO 8601 close time of the auction, about 10 s out. Bid before it. It is not the position’s
market-end horizon, which this payload omits, so you cannot pre-filter for the near-close cutoff.
A bid on a position close to its market end is rejected
MARKET_NEAR_CLOSE. Treat that as an
ordinary lost auction.Leg fields
Legs use the same field names on every event and both venues. The values are venue specific. The examples above are Kalshi.| Field | Kalshi | Polymarket |
|---|---|---|
id | The Totalis leg id, a UUID. Stable for the life of the leg. Not a venue identifier. | Same. |
venue | kalshi | polymarket |
market_ticker | The Kalshi market ticker, for example KXBTC-26JUN01-T72500. | The condition_id, the 0x prefixed hex string. Resolve a Polymarket leg by this field. |
event_ticker | The Kalshi event ticker, for example KXBTC-26JUN01. | The Gamma event slug, for example will-bitcoin-hit-100k-in-june. A slug, not a ticker. |
side | yes or no | Same. |
current_yes_price, current_no_price | On quote_request legs: the venue’s yes and no prices when the request was created, as probabilities from 0 to 1. A starting point for pricing, not a live feed. | Same. |
resolution | On cashout_request legs only. See above. | Same. |
Reconnection
Every SSE event includes anid: field. Persist it. On reconnect, pass it as the last_event_id query parameter to ask for the events you missed instead of a fresh snapshot. You get a replay only while that id is still retained on the server; otherwise the full snapshot is sent again, starting with snapshot_begin. On production in September 2026, reconnects received the full snapshot even with an id seconds old, so treat every snapshot as authoritative and do not depend on replay. The stream also sends a : keepalive comment about every 3 seconds. If nothing arrives for much longer, reconnect.
GET /v1/mm/quote-requests/stream?last_event_id=1789161257595-0
user_stake reflects the rate in effect when you connected. Reconnect after a fee rate change to get user_stake at the new rate.
Notes
- Price
payout_oddsand size collateral againstuser_stake, the stake net of the taker fee, never the grossbet_amount:user_stake = bet_amount - taker_fee, wheretaker_fee = floor(bet_amount_micro * taker_fee_bps / 10000)in microUSDC. When the fee is off (taker_fee_bpsis 0),user_stake == bet_amount. - The stream excludes quote requests created by your own user account.
Errors
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 403 | FORBIDDEN | The credential does not start with api_ (details.reason: "wrong_api_key_type"), or the key lacks mm:quote. |
curl -N https://api.totalis.trade/v1/mm/quote-requests/stream \
-H "X-API-Key: $API_KEY" \
-H "Accept: text/event-stream"
// Native EventSource cannot send an X-API-Key header, so read the SSE stream
// with fetch instead (works in browsers and Node 18+).
const res = await fetch('https://api.totalis.trade/v1/mm/quote-requests/stream', {
headers: { 'X-API-Key': API_KEY, Accept: 'text/event-stream' },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let sep;
while ((sep = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
const event = frame.match(/^event: (.+)$/m)?.[1];
const data = frame.match(/^data: (.+)$/m)?.[1];
if (!event || !data) continue;
const payload = JSON.parse(data);
switch (event) {
case 'connected':
console.log('Connected as', payload.mm_id);
break;
case 'quote_request':
// payload.bet_amount is the gross wager; price + size against net user_stake
console.log('New request:', payload.id, payload.user_stake, 'USDC');
break;
case 'quote_request:updated':
// Your prior-version quote is invalidated; submit a new one
console.log('Request updated to version', payload.version, 're-price:', payload.id);
break;
case 'quote_request_expired': {
// reason: 'committed' | 'cancelled' | 'expired'; won: true only if you were selected.
// won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
const { id, reason, won } = payload;
const outcome = reason === 'committed'
? (won === undefined ? 'unknown' : won ? 'won' : 'lost (out-bid)')
: reason;
console.log('Request closed:', id, outcome);
break;
}
case 'cashout_request':
// Distinct from quote_request: the key is payload.cashout_request_id (NOT payload.id).
// Broadcast to every MM, ~10s to bid. Re-value the position and bid ONE all-in
// price_micro (what the user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
console.log('Cashout auction:', payload.cashout_request_id, 'position', payload.position_id);
break;
}
}
}
import requests
import json
url = 'https://api.totalis.trade/v1/mm/quote-requests/stream'
headers = {
'X-API-Key': API_KEY,
'Accept': 'text/event-stream',
}
with requests.get(url, headers=headers, stream=True) as resp:
event_type = None
for line in resp.iter_lines(decode_unicode=True):
if line.startswith('event: '):
event_type = line[7:]
elif line.startswith('data: '):
payload = json.loads(line[6:])
if event_type == 'quote_request':
print(f"New request: {payload['id']}")
# Price the legs and submit a quote
elif event_type == 'quote_request:updated':
print(f"Request {payload['id']} updated to v{payload['version']}, re-price")
# Prior-version quote invalidated; submit a new one
elif event_type == 'quote_request_expired':
# reason: 'committed' | 'cancelled' | 'expired'; won True only if you were selected.
# won may be absent on a committed close (indeterminate). Treat as unknown, not a loss.
reason = payload.get('reason', 'expired')
won = payload.get('won')
if reason == 'committed':
outcome = 'unknown' if won is None else ('won' if won else 'lost (out-bid)')
else:
outcome = reason
print(f"Request closed: {payload['id']}: {outcome}")
elif event_type == 'cashout_request':
# Distinct from quote_request: the key is cashout_request_id (NOT id).
# Broadcast to every MM, ~10s to bid. Bid ONE all-in price_micro (what the
# user receives) on PUT /v1/mm/cashout-requests/{id}/quote.
print(f"Cashout auction: {payload['cashout_request_id']} position {payload['position_id']}")
event_type = None
event: connected
data: {"mm_id":"did:privy:cm3mm..."}
event: snapshot_begin
data: {"page_size":500}
event: quote_request
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":25,"user_stake":24.75,"version":1,"request_hash":"sha256:9f86d08...","expires_at":"2026-06-01T18:45:30.000Z"}
event: snapshot_complete
data: {"pages":1,"total":1}
event: quote_request
data: {"id":"f7e8d9c0-1234-5678-abcd-ef0123456789","user_id":"did:privy:cm9xyz...","input_legs":[{"market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"legs":[{"id":"d4e5f6a7-8901-23bc-def4-567890abcdef","event_ticker":"KXETH-26JUN01","market_ticker":"KXETH-26JUN01-T3000","side":"no","venue":"kalshi"}],"bet_amount":10,"user_stake":9.90,"version":1,"request_hash":"sha256:b5c6d7e...","expires_at":"2026-06-01T18:50:00.000Z"}
event: quote_request:updated
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","user_id":"did:privy:cm3abc...","input_legs":[{"market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"legs":[{"id":"8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6","event_ticker":"KXBTC-26JUN01","market_ticker":"KXBTC-26JUN01-T72500","side":"yes","venue":"kalshi"}],"bet_amount":50,"user_stake":49.50,"version":2,"request_hash":"sha256:3b1f9c2...","expires_at":"2026-06-01T18:46:10.000Z"}
event: quote_request_expired
data: {"id":"1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40","reason":"committed","won":false}
Was this page helpful?

