The cheapest way to add live sports content to your site isn't a 12 MB sportsbook frontend — it's a small widget that fetches data and renders it. This post walks through the entire thing in about 50 lines of vanilla JavaScript, no React, no Webpack, no service workers. It runs on a static HTML page.
By the end, you'll have a live-updating list of soccer matches with prematch odds (1x2, total goals, both teams to score) refreshing every 10 seconds. The same pattern works for any of the 25 sports in the Euro365 catalog.
Sign up at the developer portal. You'll get an API key starting with ek_ in your inbox immediately after email verification. The free tier covers 100 requests/minute — enough to build, test, and run this widget on a small site without ever upgrading.
example.com). This locks your key to your own origin so a leaked key can't be reused elsewhere. Optional but recommended before going live.Start with the simplest possible page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Soccer Matches</title>
<style>
body{font-family:system-ui,sans-serif;background:#0a0f1a;color:#e6edf3;padding:24px}
.match{padding:14px;border-bottom:1px solid #1c2433;display:grid;grid-template-columns:1fr auto;gap:12px}
.teams{font-weight:600}
.meta{color:#8b949e;font-size:13px;margin-top:4px}
.odds{display:flex;gap:6px;font-family:'SF Mono',monospace;font-size:13px}
.odd{background:#0c1622;border:1px solid #1c2433;padding:4px 10px;border-radius:4px}
</style>
</head>
<body>
<h2>Soccer — upcoming matches</h2>
<div id="matches">Loading…</div>
<script>
const KEY = 'ek_your_key_here';
const API = 'https://api.euro365.bet';
// …code below…
</script>
</body>
</html>
The /v1/prematch endpoint returns the upcoming events for a given sport. Soccer is sport ID 1 — see /v1/sports for the full list.
async function fetchMatches() {
const res = await fetch(`${API}/v1/prematch?sport=1&limit=10`, {
headers: { 'x-api-key': KEY }
});
if (!res.ok) throw new Error('API ' + res.status);
const json = await res.json();
return Object.entries(json.data); // [[eventId, eventData], …]
}
Each entry looks like this (truncated):
{
"s5-18938270": {
"h": "Ferro Carril Oeste",
"a": "CA Central Norte",
"t": [93703, "Primera B Nacional", 7],
"c": [32, "Argentina", 69, "ar"],
"ts": 1779649200,
"map": { "s5_p18938270": {"p": 0} }
}
}
The fields you care about are h (home team), a (away team), t[1] (tournament), c[1] (country), ts (Unix timestamp), and map (the keys to fetch live-priced outcomes with).
function renderMatches(matches) {
const container = document.getElementById('matches');
container.innerHTML = matches.map(([id, m]) => {
const kickoff = new Date(m.ts * 1000).toLocaleString();
return `
<div class="match" data-id="${id}">
<div>
<div class="teams">${m.h} vs ${m.a}</div>
<div class="meta">${m.c[1]} · ${m.t[1]} · ${kickoff}</div>
</div>
<div class="odds" id="odds-${id}">…</div>
</div>`;
}).join('');
}
Each event's map object lists which odds keys to fetch. Pass them comma-separated to /v1/odds?ids=…:
async function fetchOdds(matchIds) {
const res = await fetch(`${API}/v1/odds?ids=${matchIds.join(',')}`, {
headers: { 'x-api-key': KEY }
});
return (await res.json()).data;
}
function renderOdds(matchId, oddsData) {
const slot = document.getElementById(`odds-${matchId}`);
if (!slot) return;
// Pull the three 1x2 outcomes if present
const o = oddsData[Object.keys(oddsData)[0]] || {};
const ones = ['2001','2002','2003'] // home / draw / away outcome IDs
.map(k => o[k] ? `<span class="odd">${(o[k]/1000).toFixed(2)}</span>` : '')
.join('');
slot.innerHTML = ones || '<span class="odd">—</span>';
}
1850 means 1.85. This is the same convention used by every aggregation feed we know of.async function tick() {
try {
const matches = await fetchMatches();
renderMatches(matches);
const mapKeys = matches.flatMap(([, m]) => Object.keys(m.map || {}));
if (mapKeys.length) {
const odds = await fetchOdds(mapKeys);
matches.forEach(([id]) => renderOdds(id, odds));
}
} catch (e) {
document.getElementById('matches').textContent = 'Could not load: ' + e.message;
}
}
tick();
setInterval(tick, 10000); // refresh every 10s
That's the whole widget. Count the JS lines — it's right at 50 including the helper functions. Drop it on any static page, run a local server (python3 -m http.server), and you'll see the list live-updating.
A 10-second refresh on the prematch list is overkill — the list changes once or twice per minute. Pull the list every 60 seconds and the odds every 10:
setInterval(refreshList, 60000); // prematch list: 1× per minute
setInterval(refreshOdds, 10000); // odds: 6× per minute
// Total: ~7 req/min — well under 100/min free tier.
For live (in-play) matches, polling is wasteful. Open a WebSocket connection to wss://api.euro365.bet/ws?api_key=ek_… and you'll get pushed updates the moment odds change. We'll cover this in a follow-up post.
If you just need a live match tracker on your page, skip the JS entirely and embed our drop-in iframe widget. Same data, one line of HTML. See the widget demo ›
Before deploying to production:
The same code pattern works for tennis (sport=5), basketball (sport=2), or any of the 25 sports we cover. Swap the sport ID, optionally swap the outcome IDs for the markets you care about (see soccer main markets for the full list of IDs), and you're set.