API Endpoints: Use https://txline.txodds.com/api/ for mainnet or https://txline-dev.txodds.com/api/ for devnet
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.