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

# Quickstart

> Connect, authenticate, subscribe, and apply the stream end to end

This guide takes you from a stream key to a live, gap-free orderbook in one
connection. The examples are Node.js with the [`ws`](https://github.com/websockets/ws)
library, but the protocol is plain JSON over a standard WebSocket, so you can
use any WebSocket client.

## Prerequisites

* A ParlayX **stream API key** (`parx_stream_…`). See
  [Authentication](/streaming/authentication#stream-keys).
* One or more `MarketStream` values for the markets you want. See
  [identifying markets](/streaming/overview#step-2-identify-the-markets-you-want).

## 1. Connect and authenticate

Connect to:

```
wss://wss.parlayx.com/v1/orderbook
```

Authentication happens during the WebSocket upgrade — there is no separate
login message. Supply your key one of three ways, in order of preference:

| # | Method                            | How                                                |
| - | --------------------------------- | -------------------------------------------------- |
| 1 | **Subprotocol** (preferred)       | `Sec-WebSocket-Protocol: parlayx.v1, key.<rawKey>` |
| 2 | **Authorization header**          | `Authorization: Bearer <rawKey>`                   |
| 3 | **Query parameter** (last resort) | `?api_key=<rawKey>`                                |

<Warning>
  Prefer the subprotocol or header. The `?api_key=` query parameter is supported
  as a fallback for clients that can't set headers, but query strings are
  commonly written to logs and proxies along the way — avoid it when you can.
</Warning>

```js theme={null}
import WebSocket from "ws";

const KEY = process.env.PARLAYX_STREAM_KEY;

// Preferred: pass the key as a subprotocol.
const ws = new WebSocket("wss://wss.parlayx.com/v1/orderbook", [
  "parlayx.v1",
  `key.${KEY}`,
]);

// Alternative: Authorization header.
// const ws = new WebSocket("wss://wss.parlayx.com/v1/orderbook", {
//   headers: { Authorization: `Bearer ${KEY}` },
// });
```

If the key is missing or invalid, the upgrade is rejected with HTTP `401`
before the connection opens. A `503` means the gateway is at capacity —
reconnect, and you'll land on fresh capacity. A `429` means your account is at
its [connection limit](/streaming/connection#connection-limits) — close a
connection you no longer need instead of retrying.

## 2. Receive `welcome`

Immediately after the upgrade succeeds, the server sends a `welcome` frame:

```json theme={null}
{
  "type": "welcome",
  "v": 1,
  "serverTimestamp": 1748020034123,
  "customerId": "9a4b7e2c-1a64-4dca-9d6e-1b8c2d3f4a5b",
  "connectionId": "1d3a9c44-cf21-4f33-9211-2b9b1adf67e8",
  "activeConnections": 1,
  "limits": { "maxSubscriptionsPerConnection": 200, "maxConnections": 20, "connectionTtlSeconds": 28800 }
}
```

Keep the `connectionId` — quote it in any support request so we can find your
session in our logs. The `limits` tell you how many subscriptions this
connection accepts and how long it will live before the server closes it for a
[scheduled reconnect](/streaming/connection#connection-lifetime-and-goodbye).

## 3. Subscribe

Send a `subscribe` frame listing the markets you want. The only required
fields are `type` and `markets`; `channels` defaults to `["book"]`.

```js theme={null}
ws.on("open", () => {
  // Wait for `welcome`, then subscribe — see the message handler below.
});

function subscribe(ws) {
  ws.send(JSON.stringify({
    type: "subscribe",
    requestId: "01HZX0RJQ4Z6K7Y9V2N0WP3M8C",  // optional correlation token
    markets: [
      { venue: "polymarket", outcomeId: "71045620195867637" },
      // Limitless outcomeId is the outcome token id from market discovery —
      // these two entries are the slug's YES and NO tokens.
      { venue: "limitless", kind: "clob", marketId: "btc-100k-weekly", outcomeId: "84329471234058196" },
      { venue: "limitless", kind: "clob", marketId: "btc-100k-weekly", outcomeId: "97318264051273948" },
    ],
  }));
}
```

For each market in the batch you get back **either** a `subscribed` ack
**or** an `error` (for example, `UNKNOWN_MARKET`) — one reply per market, so a
single bad entry never sinks the rest of the batch.

```json theme={null}
{
  "type": "subscribed",
  "v": 1,
  "serverTimestamp": 1748020034200,
  "market": { "venue": "polymarket", "outcomeId": "71045620195867637" },
  "channel": "book",
  "subscriptionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "snapshotPending": true
}
```

Cache the mapping from `subscriptionId` to whatever local state you keep for
that market — every subsequent frame for this subscription is keyed by that id,
and it's also the handle you pass to [unsubscribe](/streaming/subscriptions#unsubscribe).
`snapshotPending: true` tells you a `snapshot` is on its way.

## 4. Apply the stream

A book subscription delivers a `snapshot` first, then `delta` frames:

```js theme={null}
const books = new Map();        // subscriptionId -> { bids, asks }
const lastSeq = new Map();      // subscriptionId -> last seq seen

ws.on("message", (raw) => {
  const f = JSON.parse(raw.toString());
  switch (f.type) {
    case "welcome":
      subscribe(ws);
      break;

    case "snapshot":
      // Replace local state wholesale.
      books.set(f.subscriptionId, { bids: f.bids, asks: f.asks });
      lastSeq.set(f.subscriptionId, f.seq);
      break;

    case "delta": {
      // Apply each change; size 0 deletes the level.
      const book = books.get(f.subscriptionId);
      if (!book) break;
      for (const c of f.changes) {
        const side = c.side === "bid" ? book.bids : book.asks;
        const i = side.findIndex((l) => l.price === c.price);
        if (c.size === 0) { if (i >= 0) side.splice(i, 1); }
        else if (i >= 0) side[i].size = c.size;
        else side.push({ price: c.price, size: c.size });
      }
      lastSeq.set(f.subscriptionId, f.seq);
      break;
    }

    case "status":
      // e.g. "resyncing" — see the reconnect/resync guide.
      break;

    case "error":
      console.error("stream error", f.code, f.message);
      break;
  }
});
```

That's the whole opening sequence. `snapshot` replaces your book; `delta`
mutates it. Because of the [sequencing contract](/streaming/orderbook#sequencing-and-resync),
your apply loop needs no gap-handling logic of its own — if we ever could not
send a contiguous delta, we send a fresh `snapshot` instead.

## Optional: the trade tape

To also receive each fill on a market as it executes, subscribe with the
`trade` channel (each `(market, channel)` pair is its own subscription, with
its own `subscriptionId`):

```js theme={null}
ws.send(JSON.stringify({
  type: "subscribe",
  markets: [{ venue: "polymarket", outcomeId: "71045620195867637" }],
  channels: ["book", "trade"],   // or ["trade"] for the tape alone
}));
```

Then add a case to the handler:

```js theme={null}
    case "trade":
      // One frame per fill: price, size, and the taker (aggressor) side.
      console.log(f.subscriptionId, f.price, f.size, f.takerSide, f.tradeId);
      break;
```

The tape needs no state on your side: there is no snapshot
(`snapshotPending: false` on the ack), and frames flow from the moment you
subscribe — earlier fills are not replayed. See the
[`trade` frame reference](/streaming/trades) for the full semantics.
The trade channel is available on Polymarket; Limitless publishes no public
trade feed, so a trade subscribe there is rejected with `UNSUPPORTED_CHANNEL`.

## 5. Keep the connection healthy

* The server sends WebSocket pings every 30 seconds; your client library
  answers them automatically. You can also send an application-level
  [`ping`](/streaming/connection#keepalive) and get a `pong` back.
* Watch for [`goodbye`](/streaming/connection#connection-lifetime-and-goodbye)
  frames and socket closes, and reconnect. Connections are closed at the
  `connectionTtlSeconds` lifetime from `welcome`, so schedule a proactive
  reconnect before then.
* On a `status` frame with `state: "resyncing"`, drop that subscription's local
  book and trust the next `snapshot`. See
  [reconnect & resync handling](/streaming/orderbook#sequencing-and-resync).

## Next

The **WebSocket API** reference documents the full wire contract, one page per
concern: [Authentication](/streaming/authentication),
[Connection lifecycle](/streaming/connection),
[Subscriptions & markets](/streaming/subscriptions),
the [Orderbook](/streaming/orderbook) and [Trades](/streaming/trades) channels,
[Errors & status](/streaming/errors), and the full
[Message catalog](/streaming/messages).
