Euro365 Sports Odds API
Real-time pre-match and live odds, scores, and event data for 25+ sports. Authenticate with an API key, then consume via REST, WebSocket, or the legacy RDSTN passthrough.
Also available on the RapidAPI marketplace — same endpoints, monthly card billing. Subscribe via RapidAPI ›
New — Match Explorer.
Pick any live event and see every API call needed to integrate it end-to-end — fixture, markets, odds, WebSocket subscribe, scores. Every match has a shareable permalink: https://api.euro365.bet/docs/explore?event=<id>.
Open Explorer →
Base URL
https://api.euro365.bet
How integration works
- Request an API key from the operator (see Support).
- Send the key as the
X-API-Keyheader on every request. - Pull periodic snapshots with REST, or open a single WebSocket for push updates.
- For drop-in compatibility with existing RDSTN-based backends, POST to
/rdstn.
GET /v1/odds?ids=… — the WebSocket frames are your refresh trigger, not the price payload.
Quickstart — 60 seconds #
# 1. List sports that currently have events
curl -H "X-API-Key: $KEY" https://api.euro365.bet/v1/sports
# 2. Pull all live soccer events
curl -H "X-API-Key: $KEY" "https://api.euro365.bet/v1/live?sport=1"
# 3. Pick an event id from the response, fetch its odds
curl -H "X-API-Key: $KEY" "https://api.euro365.bet/v1/odds?ids=s2-43.133873042"
# 4. Cheap liveness probe
curl -H "X-API-Key: $KEY" https://api.euro365.bet/v1/status
That's the whole integration. Read on for response shapes (Event object, Odds shape) and for the push-update WebSocket if you need sub-second updates.
Authentication #
Every authenticated request must include your API key. Two equivalent ways:
Header (recommended)
X-API-Key: ek_yourapikeyhere
Query string
?api_key=ek_yourapikeyhere
The key is also accepted on the WebSocket handshake as a query parameter (wss://api.euro365.bet/ws?api_key=…).
Errors
401 Invalid or disabled API key— missing, unknown, or admin-disabled key.429 Rate limit exceeded— see rate limits.
Rate limits #
Limits are per API key, evaluated over a rolling 60-second bucket. Your specific limit is set on key issuance and visible in your account profile. A typical integration runs at 120,000 req/min headroom; ask if you need more.
When the bucket is full the server replies with HTTP 429 and a JSON body:
{
"error": "Rate limit exceeded",
"limit": 120000,
"window": "60s",
"retry_after": 37
}
The retry_after value is seconds until the current minute bucket resets. Back off, then retry — do not hammer.
WebSocket connection limit
A single API key may hold up to 5 concurrent WebSocket connections. Additional handshakes are closed with {"error":"Too many WebSocket connections for this API key"}. Pool connections in your backend rather than opening one per worker.
Error model #
Successful responses return HTTP 200 with a JSON body containing "success": true and a "data" field. Errors return a non-2xx status with an "error" string.
| Status | Meaning | What to do |
|---|---|---|
400 | Missing or malformed parameter (e.g. no ids). | Check the parameter table for the endpoint. |
401 | Invalid or disabled API key. | Verify the key, contact support if it should be active. |
404 | Resource not found (unknown event id). | Re-fetch from /v1/live or /v1/prematch. |
429 | Rate limit exceeded. | Honor retry_after. |
500 | Server error. | Retry with exponential backoff; report if persistent. |
Example error bodies
// 400 — missing parameter
{ "error": "Missing ids parameter" }
// 401 — bad key
{ "error": "Invalid or disabled API key" }
// 429 — see Rate limits section for full body
{ "error": "Rate limit exceeded", "limit": 120000, "window": "60s", "retry_after": 37 }
Match deep-links #
Every event your integration touches has a stable, public explorer URL:
https://api.euro365.bet/docs/explore?event=<event-id>
The explorer pre-fills every snippet on the page (curl, Node, Python, PHP, WebSocket) with that exact event id and runs the calls live, so anyone you share the link with can reproduce the integration end-to-end in seconds — no setup, no key required (a domain-locked demo key is built in).
The convention is simple enough to construct from any client. Given an id from /v1/live, /v1/prematch, or a live_update frame, append it to the explorer URL:
// Node — build a shareable explorer URL for any event id
const explorer = (id) => `https://api.euro365.bet/docs/explore?event=${encodeURIComponent(id)}`;
console.log(explorer("s2-43.133873042"));
// → https://api.euro365.bet/docs/explore?event=s2-43.133873042
Use this pattern in your own admin UI, monitoring dashboards, ticket links, or in support handoffs — anyone with the URL gets a working reproduction of the API integration for that exact match.
?event=133873042) or the binding-prefixed form (?event=s2_l43.133873042) — same fallback rules as /v1/event.
REST endpoints #
All REST endpoints require authentication unless noted. Responses are JSON unless noted.
Example response
{
"success": true,
"data": [
{ "id": 1, "name": "Soccer", "live_count": 128, "prematch_count": 3142 },
{ "id": 2, "name": "Basketball", "live_count": 22, "prematch_count": 410 }
]
}
Query parameters
| sport | integer | Sport ID (see reference). Omit for all sports. optional |
Example
curl "https://api.euro365.bet/v1/live?sport=1" \
-H "X-API-Key: ek_yourapikeyhere"
Response — with ?sport=N (flat)
{
"success": true,
"ts": 1779281235365,
"sport": 1,
"data": {
"s2-43.133873042": { /* event object */ },
"s2-43.133874666": { /* event object */ }
}
}
Response — without sport (grouped by sport)
{
"success": true,
"ts": 1779281235365,
"sport": null,
"data": {
"1": { "s2-43.133873042": { /* event */ }, /* … */ },
"2": { /* basketball events */ },
"5": { /* tennis events */ }
}
}
ts is the unix-ms timestamp of the last upstream refresh. See Event object for the per-event payload.
Query parameters
| sport | integer | Sport ID. Omit for all sports. optional |
Response shape mirrors /v1/live but without the ts field.
Query parameters
| id | string | Event ID. Accepts the full key (s2-43.133873042) or just the numeric tail (133873042). required |
| sport | integer | Narrow search to one sport (slightly faster). optional |
Example
curl "https://api.euro365.bet/v1/event?id=s2-43.133873042" \
-H "X-API-Key: ek_yourapikeyhere"
Response
{
"success": true,
"data": { /* event object — see Event object reference */ }
}
Returns HTTP 404 + { "error": "Event not found" } if the id is unknown.
Query parameters
| ids | string | Comma-separated list of event IDs (max 100 per request). required |
Example
curl "https://api.euro365.bet/v1/odds?ids=s2-43.133873042,s2-43.133874666" \
-H "X-API-Key: ek_yourapikeyhere"
Response shape
{
"success": true,
"data": {
"s2-43.133873042": {
"1001": { // market group id (1001 = soccer Match Result / 1X2)
"s": { // line key — "s" = single line; "s<line>" = AH/total rung (e.g. "s+0.5", "s2.5"); "s<h>:<a>" = correct-score / HT-FT
"2001": [125, 0, null, 1779279999726], // outcome 2001 (Home) — price 1.25
"2002": [550, 0, null, 1779279999726], // outcome 2002 (Draw) — price 5.50
"2003": [1300, 0, null, 1779279999726],// outcome 2003 (Away) — price 13.00
"s": 0, // 0 = open, 1 = suspended
"ls": 1779279999726 // last-seen unix ms (line-level)
}
},
"1004": { // another market group — see "Market groups" below
"s0:3": { /* outcomes + s + ls */ },
"s0:4": { "ls": 1779280144053 } // empty line — last seen only, no current prices
}
}
}
}
Decoding the outcome tuple
Each outcome is a 4-element array [price, flags, special, ts]:
| Index | Field | Meaning |
|---|---|---|
0 | price | Decimal odds × 100. Divide by 100 to render: 125 → 1.25, 1300 → 13.00. |
1 | flags | Bitfield. 0 = open. Non-zero values flag suspensions / placeholder rows; treat any non-zero as “do not accept”. |
2 | special | Line value for handicap / total markets (e.g. "-1.5", "+0.5", "2.5"), auto-populated from the line-key suffix. null for plain markets (1X2, BTTS, DC, etc.) and for the bare "s" single-line key. |
3 | ts | Unix-ms timestamp of the last price change for this outcome. |
Line keys
The line key encodes which rung of a multi-line market the prices belong to. Three shapes:
"s"— the single / main line. Used for 1X2, BTTS, Double Chance, DNB, and the consolidated main line of AH (1005) and Goals O/U (1007).specialisnull."s<line>"— handicap / total rungs:"s+0.5","s-0.25","s2.5","s-1.25". Strip the leadingsto read the line value; the same string is mirrored in the outcomespecialfield. Used by Asian Handicap by-line (1011), Goals O/U ladders (1018), 1H Asian HC (1054), Total O/U (1080), and similar."s<home>:<away>"— per-scoreline keys for Correct Score / HT-FT-style markets (1004,1012):"s0:0","s2:1","s0:3", etc. Not a numeric line;specialcarries the literal scoreline (e.g."2:1").
Empty line objects (only ls, no outcomes) mean the rung is known to exist upstream but currently has no priced outcomes — skip them.
s: 1 (or every outcome's flag != 0) is locked — render it greyed out and reject bet attempts. Suspensions are common during goals / VAR / corner kicks and typically clear within a few seconds.
/v1/live when all you need is the scoreboard.Example
curl https://api.euro365.bet/v1/scores \
-H "X-API-Key: ek_yourapikeyhere"
Response
{
"success": true,
"data": {
"s2-43.133842454": { "home": 2, "away": 1, "status": "ended", "ts": 1779220746925 },
"s5-18921619": { "home": 87, "away": 83, "status": "ended", "ts": 1779223562305 },
"s2-43.133873042": { "home": 1, "away": 0, "status": "live", "ts": 1779281000000 }
}
}
| Field | Type | Meaning |
|---|---|---|
home | integer | Home team score (goals, points, sets, etc — sport-dependent). |
away | integer | Away team score. |
status | string | One of: "live" (in-play), "ended" (final), "scheduled" (not started). |
ts | integer | Unix-ms timestamp of the last score update. |
win / lose / void / pending results for ended events. The engine is deterministic from the final score — for every market line that was active on a finished event, the response tells you how each outcome resolved. Use this to settle bets you accepted against our pricing.V1 coverage (full-time score-derivable markets):
- 1X2 / Match Winner, Double Chance, Draw No Bet
- Total Goals Over/Under, Asian Total, Odd / Even, Goal-band markets
- Both Teams To Score (BTTS)
- Correct Score (incl. "other")
- European Handicap and Asian Handicap (whole / half lines; quarter lines marked
voidin V1) - Home/Away Team total goals (O/U, Exact, O/E)
- 1X2 + BTTS combos and 1X2 + Totals combos
Currently pending in V1 (engine returns pending with a reason string — you'll need to settle these client-side, or wait for the V2 endpoint):
- 1st-half / 2nd-half / HT-FT / Highest-Scoring-Half markets (need half-time score — coming in V2)
- Corner, card, free-kick, shot, foul and other stat-driven markets (need detailed match stats — coming in V3)
- Scorer markets, "Last team to score", "Wins the rest of the match" (need play-by-play data)
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
ids | string | yes | Comma-separated list of event ids (max 100). Aliases: event_id, fixture_id. |
Example
curl "https://api.euro365.bet/v1/settlements?ids=s2-43.134044570" \
-H "X-API-Key: ek_yourapikeyhere"
Response (for a 1-1 final result)
{
"success": true,
"version": "v1",
"policy_url": "https://api.euro365.bet/docs/#settlement-policy",
"data": {
"s2-43.134044570": {
"fixture_id": "s2-43.134044570",
"status": "ended",
"score": { "home": 1, "away": 1 },
"settled_at": 1779687757000,
"coverage": { "covered": 192, "void": 14, "pending": 67, "markets": 25 },
"markets": {
"1001": { // 1X2
"name": "1x2",
"lines": {
"s": {
"line": null,
"outcomes": {
"2001": { "name": "1", "result": "lose" },
"2002": { "name": "x", "result": "win" },
"2003": { "name": "2", "result": "lose" }
}
}
}
},
"1018": { // Total Goals O/U - all lines
"name": "Total Goals - Over / Under",
"lines": {
"s2.5": { "line": "2.5", "outcomes": { /* over=lose, under=win */ } },
"s1.5": { "line": "1.5", "outcomes": { /* over=win, under=lose */ } }
}
},
"1003": { // Correct Score
"name": "Anytime Correct Score",
"lines": { "s": { "outcomes": { /* "1:1" = win, everything else = lose */ } } }
},
"1009": { // Draw No Bet — voids on a draw
"name": "Draw No Bet",
"lines": { "s": { "outcomes": {
"2001": { "name": "1", "result": "void", "reason": "push" },
"2003": { "name": "2", "result": "void", "reason": "push" }
} } }
},
"1128": { // Total Corners — pending in V1
"name": "Total Corners - Over / Under",
"lines": { "s9.5": { "outcomes": {
"2472": { "name": "over", "result": "pending", "reason": "match stats not yet published" },
"2473": { "name": "under", "result": "pending", "reason": "match stats not yet published" }
} } }
}
}
}
}
}
Result values
| Result | Meaning | How to pay out |
|---|---|---|
win | Outcome was correct against the final score. | Pay out at the offered price. |
lose | Outcome was incorrect. | Keep the stake. |
void | Push, suspended at last read, or Asian-quarter half-line. reason explains why. | Refund the stake (price = 1.00). |
pending | Engine can't resolve this market from final score alone. reason explains what's missing. | Settle client-side, or wait for V2 / V3 to expand coverage. |
Read the binding Settlement Policy below for tie-breaking rules, abandoned matches, VAR reversals and dispute handling.
{
"success": true,
"live_events": 312,
"prematch_events": 9874,
"last_update": 1747300000000
}
TG_O/U, HT_TG_O/U, 1x2_T20MM) are auto-translated to friendly English (e.g. “Total Goals - Over / Under”, “Home Team - Total Goals - Over / Under”, “1x2 - Till The 20th Minute Of The Match”) — the raw code is preserved in a separate code field so you can still display or correlate it. Use /v1/market-groups for the UI tab/category layout (Goals, Teams, Halves, Corners, Cards, Interval, etc.).Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
sport | int | no | Restrict the dictionary to markets used by a single sport (see Sport IDs). Soccer-only client → ?sport=1 returns ~550 markets instead of the full 4,873. |
nested | 0/1 | no | When 1, the response also includes data.markets_full with outcomes scoped per market — useful when an outcome id appears in several markets with different meaning. Implies groups=1 when combined with ?sport=. |
groups | 0/1 | no | When 1 and combined with ?sport=, every market in markets_full carries a groups field listing the UI tab keys it belongs to (e.g. main, goal, team, interval) for both prematch and live contexts. Sport filter is required because groups are sport-scoped. |
Example
# full catalog (default — every sport)
curl https://api.euro365.bet/v1/markets \
-H "X-API-Key: ek_yourapikeyhere"
# soccer-only, nested form
curl "https://api.euro365.bet/v1/markets?sport=1&nested=1" \
-H "X-API-Key: ek_yourapikeyhere"
Response
{
"success": true,
"data": {
"sport": 1, // only present when ?sport= is passed
"markets": { // mid → friendly name (always present)
"1001": "1x2",
"1005": "Asian Handicap",
"1018": "Total Goals - Over / Under",
"1019": "Both Teams To Score"
/* … hundreds more */
},
"codes": { // mid → raw upstream short-code (only for markets where upstream
"1480": "TG_O/U", // shipped a short-code — `markets[mid]` shows the translated
"8856": "TG_O/E" // friendly form, `codes[mid]` keeps the original tag)
/* … up to ~720 entries fleet-wide */
},
"outcomes": {
"2001": "1",
"2002": "X",
"2003": "2",
"2081": "over",
"2082": "under"
/* … */
},
"markets_full": { // only present when ?nested=1 (or ?groups=1)
"1018": {
"name": "Total Goals - Over / Under",
"groups": { // only when ?sport= is also set
"prematch": ["main", "goal"],
"live": ["main", "goal"]
},
"outcomes": { "2081": "over", "2082": "under" }
},
"1480": { // example of a short-code market that got translated
"name": "Total Goals - Over / Under",
"code": "TG_O/U", // raw upstream code preserved here
"groups": { "prematch": ["main"], "live": ["main"] },
"outcomes": { "…": "…" }
}
/* … */
}
}
}
Rendering tip: use markets[mid] (or markets_full[mid].name) as the label your end users see. The code / codes[mid] field is for debugging, correlation, or compact UI badges — e.g. Total Goals - Over / Under [TG_O/U]. About 720 of the 4,873 markets carry a code; the rest have only the friendly name.
Conditional requests
The endpoint emits a weak ETag and honours If-None-Match, returning 304 Not Modified with an empty body when the catalog hasn’t changed since your last fetch. Recommended for clients that poll — you only pay the bandwidth when the dictionary actually updates.
curl -sI "https://api.euro365.bet/v1/markets" -H "X-API-Key: …"
# ETag: W/"eba4-…"
curl -i "https://api.euro365.bet/v1/markets" \
-H "X-API-Key: …" \
-H 'If-None-Match: W/"eba4-…"'
# HTTP/2 304
See Market & outcome dictionary for a quick-glance table of the most common IDs.
Query parameters
| Param | Type | Required | Description |
|---|---|---|---|
sport | int | no | Return the layout for a single sport. Omit to receive all sports keyed by sport id (heavier — only do this once at startup). |
Example
# soccer tab layout
curl "https://api.euro365.bet/v1/market-groups?sport=1" \
-H "X-API-Key: ek_yourapikeyhere"
Response
{
"success": true,
"data": {
"sport": 1,
"prematch": [
{ "key": "main_markets", "name": "Main", "markets": [1001, 1018, 1007, "…"] },
{ "key": "goal_markets", "name": "Goals", "markets": [1018, 1007, 1019, "…"] },
{ "key": "period_markets", "name": "Halves / Periods", "markets": [1014, 1054, 1211, "…"] },
{ "key": "team_markets", "name": "Teams", "markets": [1737, 1720, 7670, "…"] },
{ "key": "interval_markets","name": "Interval", "markets": [1505, 9912, 9914, "…"] }
/* … 8 more tabs */
],
"live": [ /* same shape, sometimes different bucket count */ ]
}
}
Each bucket’s markets array is a list of market ids you can resolve to names via GET /v1/markets. The same market id can appear in several buckets (e.g. 1018 belongs to both Main and Goals). For the reverse lookup (given a market id, list the tab keys it belongs to) use GET /v1/markets?sport=N&nested=1 and read markets_full[mid].groups.
{ "status": "ok", "uptime": <seconds> }.WebSocket stream # Open live in Explorer →
Open one connection per backend instance to receive a snapshot followed by pushed live updates whenever the poller refreshes (≈ once per second under normal load).
snapshot and live_update frames deliver the event list (teams, kickoff, score, status, map bindings) using the same Event object shape — they do not include market prices. For prices, call GET /v1/odds?ids=… for the ids you care about. Treat each live_update frame as a "data ticked, refresh now" trigger.
Endpoint
wss://api.euro365.bet/ws?api_key=ek_yourapikeyhere
Messages from server
On connect — full event-list catalogue (fixtures & scores, no odds):
{
"type": "snapshot",
"ts": 1747300000000,
"live": { /* eventId -> event-meta (no prices) */ },
"prematch": { /* eventId -> event-meta (no prices) */ }
}
On each live tick — refreshed event list (no odds); call /v1/odds?ids=… on the ids you display:
{
"type": "live_update",
"ts": 1747300001234,
"data": { /* eventId -> event-meta (no prices) */ }
}
Recommended integration loop
- Open WS → receive
snapshot→ render fixture list fromsnapshot.live/snapshot.prematch. - Pick the event ids currently on screen.
- Call
GET /v1/odds?ids=<batch>→ render prices. - On each
live_updateframe, re-callGET /v1/odds?ids=<same batch>to refresh prices. (Polling/v1/oddsevery 1–2 s without the WS works too — same result, slightly more bandwidth.)
Heartbeat
The server pings every 30 seconds. Standard WebSocket clients respond to ping frames automatically. If you implement a custom client, respond with a pong, or your connection will be terminated within ~60 seconds.
Example (Node.js, ws)
const WebSocket = require('ws');
const ws = new WebSocket('wss://api.euro365.bet/ws?api_key=ek_yourapikeyhere');
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.type === 'snapshot') initState(msg.live, msg.prematch);
else if (msg.type === 'live_update') applyUpdate(msg.data);
});
ws.on('close', () => setTimeout(reconnect, 2000));
Live push relay #
A single, low-latency fan-out of the live-odds feed. The API server maintains one aggregated source connection and rebroadcasts each frame verbatim to authenticated clients, adding ≈ 0 ms (median 0 ms, < 15 ms worst case on a LAN). Use this in preference to running many parallel direct connections from your own backends: the relay delivers the complete feed from one shared session.
Endpoint
wss://api.euro365.bet/pt-ws?api_key=ek_yourapikeyhere
Protocol
Binary-safe text framing. Fields are separated by the 0x1F unit-separator byte (shown below as \x1F). The framing is stable and lightweight — frame type, header, and JSON body — so any text-aware WebSocket client can parse it.
On connect the server sends, in order: a ready signal, a synthesized full event-list snapshot per sport (so the client's channel→event index is complete immediately), then recent odds frames:
# ready
INFO\x1FPUB
# event-list snapshot / live deltas (one per sport channel)
P\x1Fbm11_el1\x1F{"<eventId>":{"map":{...},...}}
# odds frames
P\x1Fs5_l18912496\x1F{"<marketId>":{...prices...}}
To subscribe, send SUB frames (the relay forwards them upstream and also self-subscribes from event-list deltas):
SUB\x1Fbm11_el1;bm11_el2;bm11_el5
SUB\x1F<mapKey>:a;<mapKey>:a
Heartbeat
The relay answers WebSocket ping with pong and pings upstream itself. Implement exponential-backoff reconnect (1 s → 30 s); on reconnect you receive a fresh full snapshot, so no manual resync is needed.
/rdstn (or REST), then apply relay frames on top.
Example (Node.js, ws)
const WebSocket = require('ws');
const SEP = '\x1F';
const ws = new WebSocket('wss://api.euro365.bet/pt-ws?api_key=ek_yourapikeyhere');
ws.on('message', (raw) => {
const [type, header, body] = String(raw).split(SEP);
if (type === 'INFO' && header === 'PUB') subscribe();
else if (type === 'P') applyFrame(header, JSON.parse(body));
});
ws.on('close', () => setTimeout(reconnect, 2000));
{
"upstream_ready": true,
"downstream_clients": 5,
"odds_channels": 80,
"el_sports": 9,
"el_events": 66,
"odds_cached": 207
}
RDSTN LEGACY #
RDSTN is a batched, command-based wire format kept for backwards compatibility. New integrations should use REST or WebSocket instead — they are simpler, JSON-native, and fully documented above. RDSTN is documented here only for existing backends already speaking it: pointing them at api.euro365.bet is a hostname change.
Request format
Body is a JSON array of [op, args] tuples. The server processes every key requested across all commands and returns a flat array where each entry maps one key to its current value.
POST /rdstn HTTP/1.1
Host: api.euro365.bet
X-API-Key: ek_yourapikeyhere
Content-Type: application/json
[
["ga", ["bm1_el1", "bm1_ep1"]],
["g", ["bm1_12345"]]
]
Operations
| Op | Args | Returns |
|---|---|---|
ga / gf / g / go | [key, …] | One result object per key, in order. |
sl | — | Live sport list: { sportId: "[id, name, count]", … }. |
sp | — | Pre-match sport list: same shape as sl. |
Key patterns
| Pattern | Meaning |
|---|---|
bm{N}_el{sportId} | All live events for a sport (e.g. bm1_el1 = live soccer). |
bm{N}_ep{sportId} | All pre-match events for a sport. |
bm{N}_sl | Live sport list (counts). |
bm{N}_sp | Pre-match sport list (counts). |
bm{N}_{eventId} | Market config for one event ID. |
Example
curl https://api.euro365.bet/rdstn \
-H "X-API-Key: ek_yourapikeyhere" \
-H "Content-Type: application/json" \
--data '[["ga",["bm1_el1"]]]'
Response shape:
[
{ "bm1_el1": { /* eventId: eventObject, … */ } }
]
Event object #
All event-bearing endpoints (/v1/live, /v1/prematch, /v1/event, /v1/odds, the WebSocket snapshot and live_update frames, and the legacy /rdstn passthrough) return events using the same compact shape. Keys are short to minimise payload size on high-frequency live streams.
Example
{
"s2-43.133874554": {
"c": [66, "Georgia", 43, "ge"],
"t": [570, "Erovnuli Liga", 2],
"g": 65,
"ts": 1779278400,
"h": "Meshakhte Tkibuli",
"a": "Torpedo Kutaisi",
"co": 1091,
"map": { "s2_l43.133874554": { "p": 0 } },
"s": 0,
"ms": 0,
"live": true,
"sport": 1
}
}
Field reference
| Field | Type | Meaning |
|---|---|---|
c | array | Country tuple: [country_id, name, source_sport_id, iso2]. Example: [66,"Georgia",43,"ge"]. Use the iso2 code for flag rendering. |
t | array | Tournament tuple: [tournament_id, name, priority]. priority is an upstream ordering hint (smaller = higher visibility); it is not a sport id. |
g | integer | Group / display-bucket code from the upstream feed (e.g. league tier, knockout stage). Treat as opaque. |
ts | integer | Scheduled kickoff time, unix seconds. |
h | string | Home team / participant name. |
a | string | Away team / participant name. |
co | integer | Upstream category-order code (used internally to sort live boards). Treat as opaque. |
map | object | Market map keyed by channel id (see Event-id format). Each value is a market block; p is the price/lock indicator (0 = available, non-zero = suspended/locked depending on builder). |
s | integer | Score code. 0 means 0–0 / not started. Live scores update via the live_update WebSocket frame or by polling /v1/scores. |
ms | integer | Match-status / live-minute hint from the upstream feed. Optional — may be absent on pre-match events. |
live | boolean | true if the event is currently in-play. |
sport | integer | Normalised Euro365 sport id (see Sport IDs). 1 = Soccer, 2 = Basketball, etc. |
Event-id format
Event ids look like s2-43.133874554:
s2— provider prefix (constant).43— source channel id (sport stream binding).133874554— canonical event id — unique, stable, the only part you really need.
Inside an event's map you'll see the same id with an extra binding letter:
s2_l43.133874554— the live channel binding (_l).s2_p43.133874554— the pre-match binding (_p). Pre-match events often carry several map entries, one per source.
All forms are interchangeable as inputs: /v1/event?id=… and /v1/odds?ids=… accept the full key, the binding-prefixed key, or just the numeric tail.
c, t, g, co, map, h, a, s, ms) keep WebSocket frames small for high-frequency live updates. Map them to friendly names inside your client adapter.
Market & outcome dictionary
The integer keys inside /v1/odds responses (market groups at the outer level, outcome ids inside each line) resolve to human names via GET /v1/markets — 4,873 markets / 17,227 outcomes across 25 sports, refreshed hourly. About 720 markets that the upstream catalog ships as short-codes (TG_O/U, HT_TG_O/U, 1x2_T20MM, etc.) are auto-translated to friendly English at the gateway; the original code is preserved in codes[mid] / markets_full[mid].code so you can render badges like Total Goals - Over / Under [TG_O/U]. For the tab/category layout that drives the event-page tab strip (Goals, Teams, Halves / Periods, Interval, Corners, Cards, …) use GET /v1/market-groups. Cache both endpoint responses on startup; use ?sport=N if you only handle one sport (e.g. soccer returns ~550 markets, tennis ~40). The table below is just a quick-glance subset of the most common soccer markets:
Markets
| ID | Name | ID | Name | ID | Name |
|---|---|---|---|---|---|
1001 | Match Winner | 1014 | HT Result | 1054 | 1H Asian HC |
1004 | HT/FT | 1015 | HT O/U | 1080 | Total O/U |
1005 | Asian HC | 1018 | Goals O/U | 1081 | Total O/E |
1007 | Over/Under | 1019 | BTTS | 1279 | BTTS |
1009 | Double Chance | 1020 | Double Chance | 1286 | Total Games |
1011 | Odd/Even | 1021 | DNB | 1287 | Game Winner |
1012 | Correct Score | ||||
Outcomes
| ID | Name | ID | Name | ID | Name | ID | Name | ||
|---|---|---|---|---|---|---|---|---|---|
2001 | 1 | 2004 | Over | 2008 | Odd | 2058 | Away | ||
2002 | X | 2005 | Under | 2009 | Even | 2075 | Over | ||
2003 | 2 | 2006 | Yes | 2057 | Home | 2076 | Under | ||
2037 | 1 | 2077 | 1 | 2310 | Over | 3057 | Yes | ||
2038 | X | 2078 | 2 | 2311 | Under | 3058 | No | ||
2039 | 2 | 2079 | X | 2312 | Odd | 3074 | Over | ||
2007 | No | 2313 | Even | 3075 | Under | ||||
The table above is illustrative — always fetch the live dictionary via GET /v1/markets at startup rather than hard-coding names. If an event surfaces a market id that’s missing from the endpoint response (very rare), render it with a fallback like "Market <id>" and let support know so we can add it upstream.
Sport IDs #
Use the numeric ID anywhere an endpoint accepts sport=.
| ID | Name | ID | Name | ID | Name |
|---|---|---|---|---|---|
1 | Soccer | 10 | Boxing / MMA | 26 | Waterpolo |
2 | Basketball | 12 | Rugby League | 29 | Futsal |
3 | Baseball | 13 | Aussie Rules | 39 | Lacrosse |
4 | Ice Hockey | 16 | American Football | 40 | Formula 1 |
5 | Tennis | 17 | Cycling | 117 | MMA |
6 | Handball | 20 | Table Tennis | 190 | Moto GP |
7 | Volleyball | 21 | Cricket | 306 | Bowling |
9 | Golf | 22 | Darts | 312 | Rugby Union |
Call GET /v1/sports to see which sports currently have events.
Code samples #
# Pull all live events
curl https://api.euro365.bet/v1/live \
-H "X-API-Key: $EURO365_KEY"
# Status / liveness
curl https://api.euro365.bet/v1/status \
-H "X-API-Key: $EURO365_KEY"
const KEY = process.env.EURO365_KEY;
async function getLive(sport) {
const url = `https://api.euro365.bet/v1/live${sport ? `?sport=${sport}` : ''}`;
const r = await fetch(url, { headers: { 'X-API-Key': KEY } });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
}
import os, requests
KEY = os.environ["EURO365_KEY"]
S = requests.Session()
S.headers["X-API-Key"] = KEY
def get_live(sport=None):
params = {"sport": sport} if sport else {}
r = S.get("https://api.euro365.bet/v1/live", params=params, timeout=10)
r.raise_for_status()
return r.json()
<?php
$key = getenv('EURO365_KEY');
$ch = curl_init('https://api.euro365.bet/v1/live');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: $key"],
CURLOPT_TIMEOUT => 10,
]);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
Integration patterns #
Live odds board (REST polling)
GET /v1/sportsonce a minute to drive your sport filter.GET /v1/live?sport=Nevery 2–5 s for the events list.GET /v1/odds?ids=…for the visible event IDs (batch in groups of up to 100) every 1–2 s.GET /v1/scoresevery 5–10 s for the scoreboard column.
Polling is fine up to a few hundred events. Beyond that, layer the WebSocket on top so you skip the /v1/live poll — the WS pushes event-list changes for you; you still call /v1/odds for the prices themselves.
Push updates (WebSocket + REST hybrid)
- Open
wss://api.euro365.bet/ws?api_key=…. - Consume the initial
snapshotframe — it carries the full live + pre-match event catalogue (fixtures, scores; no prices). No REST is needed to bootstrap your event index. - Call
GET /v1/odds?ids=<visible batch>for the prices on the events you actually render. - On each
live_updateframe (refreshed event list), re-callGET /v1/odds?ids=<same batch>to refresh prices. - Re-fetch the snapshot on reconnect.
Rendering a price
// from /v1/odds → market 1001 → line "s" → outcome 2001
const [price, flags] = outcome;
if (flags !== 0) renderLocked();
else renderPrice((price / 100).toFixed(2)); // 125 → "1.25"
Subscribing to one event
REST works fine: poll GET /v1/event?id=… + GET /v1/odds?ids=… every 1–2 s. For sub-second freshness, use the WebSocket and filter the live_update frames to the event id you care about.
Settlement Policy #
This is the binding rulebook applied by /v1/settlements. By using the settlement endpoint you agree these rules govern the win / lose / void result returned for each outcome. If you disagree with a specific result, follow the dispute procedure at the end of this section — do not silently override the engine on your side.
1. Final score & status
- The final score is the value of
score.home/score.awayin/v1/scoresat the momentstatusbecomes"ended". Score updates received after the status flip (e.g. corrections published hours later) do not retroactively re-settle outcomes; see Stat corrections below. - Settlement uses the score for the regulation period as reported by the upstream feed. Extra time and penalty shootouts are not included unless a market name explicitly says so ("including extra time", "penalty shootout").
- An event with
status: "ended"and a missing score is void: every outcome resolves tovoidwith reason"final score unavailable".
2. Push / void rules
- Whole-line totals (e.g. Over/Under 2.0): if final total equals the line, both sides are
void(push). - Whole-line European handicaps: if the adjusted score is a tie, both home and away handicap outcomes are
void; the "draw" outcome wins. - Asian handicap whole / half lines: settled normally (Asian draws not possible at these lines).
- Asian handicap quarter lines (e.g. −0.25, +0.75): V1 returns
voidfor these. V2 will support half-stake splits. - Draw No Bet: if the match is a draw, both Home and Away resolve to
void. - Suspended markets: if the market line was suspended (
line.s === 1) or the outcome had non-zero flags at the time of our last read before FT, that outcome isvoidwith reason"suspended at last read".
3. Abandoned, postponed & replayed matches
- A match is treated as abandoned if upstream reports
status: "ended"without completing the regulation period. All settlable outcomes for an abandoned match resolve tovoid. - A postponed match keeps
status: "scheduled"or"live"indefinitely; the engine returns"event has not ended"until upstream resolves it. - A replayed match is treated as a new fixture with a new event id. The original event remains abandoned and void.
4. Stat corrections & VAR reversals
- If upstream issues a corrected final score after we settle, we will re-run settlement against the corrected score. Any prior
/v1/settlementsresponse is superseded. Customers must re-poll the endpoint at least once 30 minutes after FT to capture late corrections. - VAR-induced goal or card reversals delivered during the match are captured automatically. Post-match disciplinary actions (e.g. retrospective bans, abandoned-match rulings published the next day) are not applied to settlement.
- Cancelled or expunged matches (e.g. competition-board rulings) are treated identically to abandoned matches: all outcomes void.
5. Coverage versioning
- V1 (current) — full-time score-only markets. Stat-driven and half-only markets return
pending. - V2 — adds half-time score capture: settles 1st/2nd-half markets, HT/FT, Highest Scoring Half.
- V3 — adds match-stat capture from the LMT feed: settles corners, cards, free kicks, shots, and Race-To-Nth-Corner.
- You can rely on outcomes that were previously
win/lose/voidremaining that way across versions, unless rule 4 (stat correction) applies.pendingoutcomes may transition towin/lose/voidas coverage grows.
6. Disputes
If you believe a specific result is wrong, do not modify the outcome on your side without first opening a dispute. Send the event id, market id, outcome id, your expected result, and a one-line reason to the operator (see Support). We will re-run the engine against fresh upstream data, publish a written ruling, and if our settlement was wrong we will update the engine and reissue the affected results within 72 hours.
7. Limitations of liability
The settlement engine is provided as a convenience and reflects our best interpretation of the final score and standard market rules. Customers remain responsible for the bets they accept and pay out; using /v1/settlements does not transfer settlement liability to Euro365 API. In the event of a confirmed engine error, our liability is limited to reissuing corrected results and does not extend to consequential losses (paid-out winnings to end-users, reputational damage, etc.). High-volume customers requiring a settlement SLA should contact support to discuss a custom agreement.
Support #
To request an API key, increase your rate limit, whitelist source IPs or domains, or report an issue, contact the operator. Include your account name and a short description of your use case (estimated request volume, REST vs WebSocket, sports of interest).