Skip to main content
API Endpoints: Use https://txline.txodds.com/api/ for mainnet or https://txline-dev.txodds.com/api/ for devnet
Prerequisites: Complete Quickstart activation or World Cup Free Tier activation first. The snippets assume jwt is the guest JWT from /auth/guest/start and apiToken is the value returned by /api/token/activate.

SSE Parsing Helper

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.
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));
}

Stream Scores Updates

Connect to the scores stream for real-time updates.
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));
}

Historical Scores

Fetch the complete sequence of score updates for a fixture that started between two weeks and six hours ago.
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}`);
});
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.
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.