> ## Documentation Index
> Fetch the complete documentation index at: https://docs.totalis.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Stream quote requests

> Subscribe to all active quote requests in real time via Server-Sent Events.

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.

## Authentication

API key required. Pass your API key in the `X-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](/solana/guides/websocket#authentication), and the key of your `mm:quotes:{mm_id}` channel there.

```json theme={null}
{
  "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`.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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](/solana/api-reference/quote-service-mm/submit-quote) a new quote with the new `version` and `request_hash`.

<Warning>
  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.
</Warning>

```json theme={null}
{
  "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.

```json theme={null}
{
  "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. |

The winning maker's identity and odds are never disclosed.

```json theme={null}
{
  "id": "1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40",
  "reason": "committed",
  "won": false
}
```

### `cashout_request`

Sent when a user opens an [early cashout auction](/solana/guides/cashout-auctions) 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`](/solana/api-reference/quote-service-mm/cashout-quote). To pass, submit nothing; there is no decline endpoint.

<Warning>
  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.
</Warning>

```json theme={null}
{
  "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"
}
```

Every field below is always present.

<ResponseField name="cashout_request_id" type="string" required>
  The auction id. Pass it as `{id}` when you
  [bid](/solana/api-reference/quote-service-mm/cashout-quote). This payload has no `id` field, unlike
  `quote_request`.
</ResponseField>

<ResponseField name="position_id" type="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.
</ResponseField>

<ResponseField name="legs" type="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.
</ResponseField>

<ResponseField name="user_stake" type="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.
</ResponseField>

<ResponseField name="mm_risk" type="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.
</ResponseField>

<ResponseField name="total_payout" type="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`.
</ResponseField>

<ResponseField name="payout_odds" type="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.
</ResponseField>

<ResponseField name="version" type="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.
</ResponseField>

<ResponseField name="expires_at" type="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.
</ResponseField>

## 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. |

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.

## Reconnection

Every SSE event includes an `id:` 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
```

The taker-fee rate is read once per connection. On a long-lived stream, `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_odds` and size collateral against `user_stake`, the stake net of the [taker fee](/solana/limits-and-fees#fees), never the gross `bet_amount`: `user_stake = bet_amount - taker_fee`, where `taker_fee = floor(bet_amount_micro * taker_fee_bps / 10000)` in microUSDC. When the fee is off (`taker_fee_bps` is 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`. |

<RequestExample>
  ```bash curl theme={null}
  curl -N https://api.totalis.trade/v1/mm/quote-requests/stream \
    -H "X-API-Key: $API_KEY" \
    -H "Accept: text/event-stream"
  ```

  ```javascript JavaScript theme={null}
  // 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;
      }
    }
  }
  ```

  ```python Python theme={null}
  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
  ```
</RequestExample>

<ResponseExample>
  ```text SSE stream theme={null}
  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}
  ```
</ResponseExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.