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

# Streaming Data

> Real-time odds and scores updates via Server-Sent Events

<Info>
  **API Endpoints**: Use `https://txline.txodds.com/api/` for mainnet or `https://txline-dev.txodds.com/api/` for devnet
</Info>

<Info>
  **Prerequisites**: Complete [Quickstart activation](/documentation/quickstart) or [World Cup Free Tier activation](/documentation/worldcup) first. The snippets assume `jwt` is the guest JWT from `/auth/guest/start` and `apiToken` is the value returned by `/api/token/activate`.
</Info>

## Stream Expectations

An open SSE connection means your credentials and stream request are accepted. It does not guarantee that a covered fixture is producing data at that exact moment.

If the stream opens but you only see heartbeats or no data messages for a while:

* Check [Scores Schedule](/documentation/scores/schedule) for active or upcoming covered fixtures.
* Keep the stream open during a covered live fixture window.
* Use `/api/scores/historical/{fixtureId}` when you need replay data for a completed fixture within the historical availability window.
* Confirm you are using the correct network host for your subscription.

<Note>
  If a stream returns `401`, renew the guest JWT from the same host and reconnect with the same `X-Api-Token`. If it returns `403`, check that the API token belongs to the same network and subscription bundle.
</Note>

## Runnable Devnet Streams

The repository includes runnable devnet stream scripts that handle activation, API token headers, JWT renewal, and SSE connections:

| Script                                                                                                                           | Stream coverage                                             |
| -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| [`subscription_free_tier.ts`](https://github.com/txodds/tx-on-chain/blob/main/examples/devnet/scripts/subscription_free_tier.ts) | Odds snapshots and odds SSE streams.                        |
| [`subscription_scores.ts`](https://github.com/txodds/tx-on-chain/blob/main/examples/devnet/scripts/subscription_scores.ts)       | Scores snapshots, score validation, and scores SSE streams. |
| [`subscription_scores_v2.ts`](https://github.com/txodds/tx-on-chain/blob/main/examples/devnet/scripts/subscription_scores_v2.ts) | Scores stream handling alongside V2 score validation.       |

See [Runnable Devnet Examples](/documentation/examples/devnet-examples) for the required devnet environment variables and Node.js runtime notes.

## SSE Parsing Helper

```typescript theme={null}
type SseMessage = {
  id?: string;
  event?: string;
  data: string;
  retry?: number;
};

function parseSseBlock(block: string): SseMessage | null {
  const message: SseMessage = { data: "" };

  for (const rawLine of block.split(/\r?\n/)) {
    if (!rawLine || rawLine.startsWith(":")) continue;

    const separatorIndex = rawLine.indexOf(":");
    const field = separatorIndex === -1 ? rawLine : rawLine.slice(0, separatorIndex);
    const value =
      separatorIndex === -1
        ? ""
        : rawLine.slice(separatorIndex + 1).replace(/^ /, "");

    if (field === "data") message.data += `${value}\n`;
    if (field === "event") message.event = value;
    if (field === "id") message.id = value;
    if (field === "retry") message.retry = Number(value);
  }

  message.data = message.data.replace(/\n$/, "");
  return message.data || message.event || message.id ? message : null;
}

async function* readSseMessages(response: Response): AsyncGenerator<SseMessage> {
  if (!response.body) throw new Error("Stream response has no body");

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });

      let separator = buffer.match(/\r?\n\r?\n/);
      while (separator?.index !== undefined) {
        const block = buffer.slice(0, separator.index);
        buffer = buffer.slice(separator.index + separator[0].length);

        const message = parseSseBlock(block);
        if (message) yield message;

        separator = buffer.match(/\r?\n\r?\n/);
      }
    }

    buffer += decoder.decode();
    const message = parseSseBlock(buffer);
    if (message) yield message;
  } finally {
    reader.releaseLock();
  }
}

function parseSseData(data: string) {
  try {
    return JSON.parse(data);
  } catch {
    return data;
  }
}
```

## Stream Odds Updates

Connect to the odds stream for real-time updates.

```typescript theme={null}
const streamUrl = "https://txline.txodds.com/api/odds/stream";
const streamResponse = await fetch(streamUrl, {
  headers: {
    Authorization: `Bearer ${jwt}`,
    "X-Api-Token": apiToken,
    Accept: "text/event-stream",
    "Cache-Control": "no-cache",
  },
});

if (!streamResponse.ok) {
  throw new Error(`Stream failed: ${streamResponse.status}`);
}

for await (const message of readSseMessages(streamResponse)) {
  console.log(message.event ?? "message", parseSseData(message.data));
}
```

Use `https://txline-dev.txodds.com/api/odds/stream` for a devnet subscription.

## Stream Scores Updates

Connect to the scores stream for real-time updates.

```typescript theme={null}
const streamUrl = "https://txline.txodds.com/api/scores/stream";
const streamResponse = await fetch(streamUrl, {
  headers: {
    Authorization: `Bearer ${jwt}`,
    "X-Api-Token": apiToken,
    Accept: "text/event-stream",
    "Cache-Control": "no-cache",
  },
});

if (!streamResponse.ok) {
  throw new Error(`Stream failed: ${streamResponse.status}`);
}

for await (const message of readSseMessages(streamResponse)) {
  console.log(message.event ?? "message", parseSseData(message.data));
}
```

Use `https://txline-dev.txodds.com/api/scores/stream` for a devnet subscription.

Scores stream messages can be used as validation inputs. When a message contains a real sequence field, use its observed `Seq` or `seq` value with `/api/scores/stat-validation`; do not replace it with `0` or a synthetic sequence number.

## Historical Scores

Fetch the complete sequence of score updates for a fixture that started between two weeks and six hours ago.

```typescript theme={null}
import axios from "axios";

const httpClient = axios.create({
  timeout: 30000,
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${jwt}`,
    "X-Api-Token": apiToken
  },
  baseURL: "https://txline.txodds.com",
});

const fixtureId = 17952170;
const historicalScores = await httpClient.get(`/api/scores/historical/${fixtureId}`);

console.log(`Retrieved ${historicalScores.data.length} score updates for fixture ${fixtureId}`);
historicalScores.data.forEach((update, index) => {
  console.log(`${index + 1}. Seq: ${update.seq}, TS: ${update.ts}, State: ${update.gameState}`);
});
```

Use the observed sequence from a historical record when requesting a validation proof for that record.

<Info>
  **Historical Availability**: This endpoint only returns data for fixtures with start times between two weeks and six hours in the past from the current time.
</Info>

<Info>
  **Stream Compression**: To reduce bandwidth usage by up to 70-80%, add `"Accept-Encoding": "gzip"` to your headers. You'll need to decompress the response chunks using `gunzipSync()` from Node's `zlib` module before decoding.
</Info>

## Troubleshooting

For activation, credential, and stream diagnostics, see [Troubleshooting](/documentation/examples/troubleshooting).
