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

  1. Request an API key from the operator (see Support).
  2. Send the key as the X-API-Key header on every request.
  3. Pull periodic snapshots with REST, or open a single WebSocket for push updates.
  4. For drop-in compatibility with existing RDSTN-based backends, POST to /rdstn.
Tip Use REST for occasional reads and WebSocket for live-update fan-out. The WebSocket sends a full event-list snapshot on connect (fixtures, scores, status — no prices), so REST is not needed to bootstrap your event index. For the actual prices, always call 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=…).

Keep keys server-side Never embed your API key in browser JavaScript or mobile binaries. Proxy from your backend.

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.

StatusMeaningWhat to do
400Missing or malformed parameter (e.g. no ids).Check the parameter table for the endpoint.
401Invalid or disabled API key.Verify the key, contact support if it should be active.
404Resource not found (unknown event id).Re-fetch from /v1/live or /v1/prematch.
429Rate limit exceeded.Honor retry_after.
500Server 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 }

REST endpoints #

All REST endpoints require authentication unless noted. Responses are JSON unless noted.

GET/v1/sports
List sports that currently have live or pre-match events, with counts.
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 }
  ]
}
GET/v1/live
All live events, optionally filtered to a single sport.
Query parameters
sportintegerSport 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.

GET/v1/prematch
All upcoming pre-match events, optionally filtered to a single sport.
Query parameters
sportintegerSport ID. Omit for all sports. optional

Response shape mirrors /v1/live but without the ts field.

GET/v1/event Try it live →
Look up a single event by ID across live and pre-match.
Query parameters
idstringEvent ID. Accepts the full key (s2-43.133873042) or just the numeric tail (133873042). required
sportintegerNarrow 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.

GET/v1/odds Try it live →
Bulk lookup of market odds for one or more events.
Query parameters
idsstringComma-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]:

IndexFieldMeaning
0priceDecimal odds × 100. Divide by 100 to render: 125 → 1.25, 1300 → 13.00.
1flagsBitfield. 0 = open. Non-zero values flag suspensions / placeholder rows; treat any non-zero as “do not accept”.
2specialLine 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.
3tsUnix-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). special is null.
  • "s<line>" — handicap / total rungs: "s+0.5", "s-0.25", "s2.5", "s-1.25". Strip the leading s to read the line value; the same string is mirrored in the outcome special field. 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; special carries 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.

Suspended events A market with 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.
GET/v1/scores
Current scores for all known events (live and recently finished). Cheaper than /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 }
  }
}
FieldTypeMeaning
homeintegerHome team score (goals, points, sets, etc — sport-dependent).
awayintegerAway team score.
statusstringOne of: "live" (in-play), "ended" (final), "scheduled" (not started).
tsintegerUnix-ms timestamp of the last score update.
GET/v1/settlements
Per-outcome 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 void in 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
ParamTypeRequiredDescription
idsstringyesComma-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
ResultMeaningHow to pay out
winOutcome was correct against the final score.Pay out at the offered price.
loseOutcome was incorrect.Keep the stake.
voidPush, suspended at last read, or Asian-quarter half-line. reason explains why.Refund the stake (price = 1.00).
pendingEngine 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.

GET/v1/status
Aggregate counters and last refresh timestamp. Use this as a cheap liveness probe.
{
  "success": true,
  "live_events": 312,
  "prematch_events": 9874,
  "last_update": 1747300000000
}
GET/v1/markets
Full dictionary mapping market-group IDs and outcome IDs to human-readable names — currently 4,873 markets / 17,227 outcomes across 25 sports. Refreshed hourly from the upstream catalog; cache the response client-side. Upstream short-code names (e.g. 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
ParamTypeRequiredDescription
sportintnoRestrict 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.
nested0/1noWhen 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=.
groups0/1noWhen 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.

GET/v1/market-groups
The UI tab/category layout for each sport — which markets belong under the Main, Goals, Halves / Periods, Handicap, Teams, Combo, Corners, Cards, Score, Goal Scorers, Player, Interval, Specials tabs. Drives the tab strip on event pages so clients don’t have to hard-code groupings. Soccer ships 13 prematch tabs / 15 live tabs; coverage spans 83 sport-context buckets in total.
Query parameters
ParamTypeRequiredDescription
sportintnoReturn 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.

GET/health
Unauthenticated process health probe. Returns { "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).

The WS carries events, not prices. Both the 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

  1. Open WS → receive snapshot → render fixture list from snapshot.live / snapshot.prematch.
  2. Pick the event ids currently on screen.
  3. Call GET /v1/odds?ids=<batch> → render prices.
  4. On each live_update frame, re-call GET /v1/odds?ids=<same batch> to refresh prices. (Polling /v1/odds every 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.

Connection cap Max 5 concurrent WebSocket connections per API key. Pool from your backend; do not open one per browser session.

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.

Base prices come from /rdstn The relay delivers the live odds delta only; the connect snapshot replays just the last ≈ 10 s of odds. Seed your event list and base prices from /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));
GET/pt-relay/health
Relay liveness and fan-out stats. No authentication; intended for internal monitoring.
{
  "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.

POST/rdstn
Batched key resolver. Body is a JSON array of commands; response is a parallel array of results.

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

OpArgsReturns
ga / gf / g / go[key, …]One result object per key, in order.
slLive sport list: { sportId: "[id, name, count]", … }.
spPre-match sport list: same shape as sl.

Key patterns

PatternMeaning
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}_slLive sport list (counts).
bm{N}_spPre-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, … */ } }
]
When to use RDSTN You're porting an existing backend that already speaks the RDSTN protocol. For greenfield code, use REST or WebSocket.

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

FieldTypeMeaning
carrayCountry tuple: [country_id, name, source_sport_id, iso2]. Example: [66,"Georgia",43,"ge"]. Use the iso2 code for flag rendering.
tarrayTournament tuple: [tournament_id, name, priority]. priority is an upstream ordering hint (smaller = higher visibility); it is not a sport id.
gintegerGroup / display-bucket code from the upstream feed (e.g. league tier, knockout stage). Treat as opaque.
tsintegerScheduled kickoff time, unix seconds.
hstringHome team / participant name.
astringAway team / participant name.
cointegerUpstream category-order code (used internally to sort live boards). Treat as opaque.
mapobjectMarket 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).
sintegerScore code. 0 means 0–0 / not started. Live scores update via the live_update WebSocket frame or by polling /v1/scores.
msintegerMatch-status / live-minute hint from the upstream feed. Optional — may be absent on pre-match events.
livebooleantrue if the event is currently in-play.
sportintegerNormalised 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).
  • 133874554canonical 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.

Tip Compact field names (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/markets4,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

IDNameIDNameIDName
1001Match Winner1014HT Result10541H Asian HC
1004HT/FT1015HT O/U1080Total O/U
1005Asian HC1018Goals O/U1081Total O/E
1007Over/Under1019BTTS1279BTTS
1009Double Chance1020Double Chance1286Total Games
1011Odd/Even1021DNB1287Game Winner
1012Correct Score

Outcomes

IDNameIDNameIDNameIDName
200112004Over2008Odd2058Away
2002X2005Under2009Even2075Over
200322006Yes2057Home2076Under
20371207712310Over3057Yes
2038X207822311Under3058No
203922079X2312Odd3074Over
2007No2313Even3075Under

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=.

IDNameIDNameIDName
1Soccer10Boxing / MMA26Waterpolo
2Basketball12Rugby League29Futsal
3Baseball13Aussie Rules39Lacrosse
4Ice Hockey16American Football40Formula 1
5Tennis17Cycling117MMA
6Handball20Table Tennis190Moto GP
7Volleyball21Cricket306Bowling
9Golf22Darts312Rugby 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)

  1. GET /v1/sports once a minute to drive your sport filter.
  2. GET /v1/live?sport=N every 2–5 s for the events list.
  3. GET /v1/odds?ids=… for the visible event IDs (batch in groups of up to 100) every 1–2 s.
  4. GET /v1/scores every 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)

  1. Open wss://api.euro365.bet/ws?api_key=….
  2. Consume the initial snapshot frame — it carries the full live + pre-match event catalogue (fixtures, scores; no prices). No REST is needed to bootstrap your event index.
  3. Call GET /v1/odds?ids=<visible batch> for the prices on the events you actually render.
  4. On each live_update frame (refreshed event list), re-call GET /v1/odds?ids=<same batch> to refresh prices.
  5. 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.away in /v1/scores at the moment status becomes "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 to void with 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 void for 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 is void with 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 to void.
  • 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/settlements response 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 / void remaining that way across versions, unless rule 4 (stat correction) applies. pending outcomes may transition to win/lose/void as 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).