Integration reference · v1
scoreClientSocket
The public edge of the live-score platform. Third-party integrators connect here — never directly to the internal provider — for scorecard/iframe lookups over REST and live push updates over a SignalR socket.
Authentication
Every request — REST or socket — needs a valid, unexpired agent key. Ask the platform owner for one; each key can optionally be locked to specific caller IPs.
How to send it
| Call type | Transport | Example |
|---|---|---|
| REST API | HTTP header X-App | X-App: <your-key> |
| REST API (alt.) | Query string | ?key=<your-key> |
| Socket connection | Query string on the hub URL — required, headers don't reach it | wss://host/clientScore?key=<your-key> |
The socket needs the query string because a browser's native WebSocket upgrade can't carry custom headers — only the initial SignalR "negotiate" call could use one, and the upgrade after it would then fail auth. ?key= works for both legs.
Failure responses
| Status | Message | Cause |
|---|---|---|
| 401 | Agent key required. | No key present, header or query string |
| 401 | Invalid agent key. | Key doesn't match any configured agent |
| 401 | Agent '<name>' key has expired on <date>. | Past the key's expiry date |
| 403 | IP '<ip>' is not whitelisted for agent '<name>'. | Key has an IP allowlist and yours isn't on it |
Rejection body shape
{ "success": false, "data": null, "message": "...", "status": 401 }
Testing in Swagger? Click Authorize and paste your key once — it's attached to every "Try it out" call automatically.
REST API
All routes are prefixed with /api. Every route below shares the auth rules above, a 5 req/s per-IP rate limit, and open CORS unless the deployment restricts it.
Scorecard & iframe
/api/bfrateScoreboradCached scorecard widget URL for an event.
Query params
| Param | Type | Required | Notes |
|---|---|---|---|
eventId | string | yes | — |
link | int | no · default 0 | scorecard variant/link id |
pid | int | no · default 0 | provider id |
color | string | no | appended to the URL as &color= |
font | string | no | appended to the URL as &font= |
Response
{
"EventID": 12345,
"scoreUrl": "https://.../scoreboard?id=12345&color=000000",
"streamingUrl": "https://..."
}
Operational
Monitoring endpoints — connection counts, daily stats, instance lifecycle. Not needed for a typical score integration.
Real-time socket
A SignalR hub at /clientScore pushes score updates the moment they change — no polling needed on your side.
Connect
const connection = new signalR.HubConnectionBuilder()
.withUrl(`wss://<host>/clientScore?key=${AGENT_KEY}`)
.withAutomaticReconnect()
.build();
connection.on('Score', (fullScore) => { /* §Score payloads */ });
connection.on('ShortScore', (shortScore) => { /* §Score payloads */ });
await connection.start();
connection.invoke('getscore', '30123456');
connection.invoke('getShortScore', '30123456');
Client → server methods
| Method | Args | Effect |
|---|---|---|
getscore | "123,456" | Subscribe to full-score updates. Sends the cached Score immediately if present, then every future update. |
disconnectscore | event id(s) | Unsubscribe from full-score updates. |
getShortScore | event id(s) | Same as getscore, for the ShortScore stream. |
disconnectShortScore | event id(s) | Unsubscribe from short-score updates. |
getupdateScore | event id(s) | Heartbeat — keeps the event active server-side. See below. |
Ping | none | Replies with Pong. Liveness check. |
Server → client events
| Event | Payload | When |
|---|---|---|
Score | full score object | On getscore (cache hit), then every live update |
ShortScore | short score object | On getShortScore (cache hit), then every live update |
Pong | none | Reply to your Ping |
Heartbeat & reconnection
Call getupdateScore on an interval well under 90 seconds for every event you're watching. The server only keeps polling events with recent client interest — an event goes idle 90s after its last getscore/getupdateScore/getShortScore call. score-webapp uses a 15s interval.
Use .withAutomaticReconnect(). Subscriptions are per-connection — on reconnect, re-issue getscore/getShortScore for every event you still care about. There's no explicit "connected" event; a resolved connection.start() is your signal.
Score payloads
scoredata (the Score event) and shortscoredata (ShortScore) are relayed byte-for-byte from the upstream provider.
Keys are abbreviated, not the "nice" names. The live socket push serializes raw C# member names — na, sc, hts, eid, etid, gsq, isrv — never the expanded name / score / halfTimeScore aliases you might expect from a Betfair-flavored API. The tables below are your field glossary.
eventTypeId (etid / eti) → sport
| Value | Sport |
|---|---|
| 1 | Soccer |
| 2 | Tennis |
| 3 | Golf |
| 4 | Cricket |
Score event — base fields
| Field | Type | Meaning |
|---|---|---|
etid | int | event type id (sport) |
eid | int | event id |
sc | object | score object — sc.hm home, sc.aw away (table below) |
st | string | status |
ms | string | match status |
te | int | Soccer match clock, seconds |
ert | int | Soccer elapsed regular time |
fte | object | { h, m, s } full time elapsed |
cset | int | Tennis current set number |
hs | bool | Tennis "has sets" flag |
ud | array | update log: {ut, uid, mt, ert, ty, uty, team, tname} |
sc.hm / sc.aw — per side
| Field | Type | Meaning | Sport |
|---|---|---|---|
na | string | name | all |
sc | string | score (goals / points) | all |
hts | string | half-time score | Soccer |
ftc | string | full-time score | Soccer |
pes / peseq | string / array | penalties score / sequence (omitted when empty) | Soccer |
games / sets | string | games in set / sets won | Tennis |
gsq | array<string> | point-by-point game sequence | Tennis |
isrv | bool? | currently serving | Tennis |
sbr | int? | service breaks | Tennis |
nyfc / nrfc / nfc | int | yellow / red / total cards (omitted at 0) | Soccer |
nfco / nfcofh / nfcosh | int | corners — total / 1st half / 2nd half (omitted at 0) | Soccer |
bp | int | booking points (omitted at 0) | Soccer |
in1 / in2 | object | {rn, wi, ov} runs/wickets/overs, innings 1 & 2 | Cricket † |
† Cricket doesn't get a Score event today. The model has cricket fields, but the live pipeline never populates or broadcasts Score for cricket — that sport's data arrives through a separate feed that only produces ShortScore. If you're integrating cricket, subscribe to ShortScore only.
ShortScore event — fields
No key aliasing here — these are the actual wire names.
| Field | Type | Meaning |
|---|---|---|
eti | int | event type id (sport) |
eid | string | event id |
en | string | event name, e.g. "Team A v Team B" |
te1n / te2n | string | team/player 1 & 2 names |
t1s / t2s | string | team/player 1 & 2 scores |
pt | int | period/time — meaning depends on sport (omitted at 0) |
t1set / t2set | string | sets won Tennis |
t1p / t2p | string | points/games Tennis |
currBatting | string | team currently batting Cricket |
currBattingTeamScore | string | batting team's current score Cricket |
Per sport
| Sport | t1s/t2s | pt | Sets/points | currBatting* | Source |
|---|---|---|---|---|---|
| Soccer | goals | match clock (s) | — | — | derived from Score |
| Tennis | games in set | current set # | set | — | derived from Score |
| Cricket | not populated this way | — | — | populated | independent live feed |
| Golf | — | — | — | — | no short score produced |
Soccer and tennis short scores are a summarized view of the same Score payload. Cricket short scores come from an independent feed whose messages already arrive pre-shaped this way — which is why currBatting* is only ever populated there.
Sample payloads
Soccer — Score
{
"etid": 1, "eid": 30123456, "st": "IN_PLAY", "ms": "1st Half", "te": 1523, "ert": 1523,
"fte": { "h": 0, "m": 25, "s": 23 },
"sc": {
"hm": { "na": "Team A", "sc": "1", "hts": "0", "ftc": "0", "nyfc": 2, "nfco": 3 },
"aw": { "na": "Team B", "sc": "0", "hts": "0", "ftc": "0" }
}
}
Soccer — ShortScore
{ "eti": 1, "eid": "30123456", "en": "Team A v Team B", "te1n": "Team A", "te2n": "Team B", "t1s": "1", "t2s": "0", "pt": 1523 }
Tennis — Score
{
"etid": 2, "eid": 30234567, "cset": 2, "hs": true,
"sc": {
"hm": { "na": "Player A", "sc": "30", "games": "3", "sets": "1", "isrv": true, "gsq": ["15-0","15-15","30-15"] },
"aw": { "na": "Player B", "sc": "15", "games": "2", "sets": "0", "isrv": false }
}
}
Tennis — ShortScore
{ "eti": 2, "eid": "30234567", "en": "Player A v Player B", "te1n": "Player A", "te2n": "Player B", "t1s": "3", "t2s": "2", "pt": 2, "t1set": "1", "t2set": "0", "t1p": "30", "t2p": "15" }
Cricket — ShortScore (values illustrative — exact shape is whatever the feed sends)
{ "eti": 4, "eid": "30345678", "en": "Team A v Team B", "te1n": "Team A", "te2n": "Team B", "t1s": "156/4 (18.2)", "t2s": "", "currBatting": "Team A", "currBattingTeamScore": "156/4" }
No Score event for cricket — see the callout above.
Quick-start checklist
- Get an agent key — and if you need IP restriction, share the IPs your backend calls from.
- REST: send
X-App: <key>(or?key=) on every/api/*call. - Socket: connect to
wss://<host>/clientScore?key=<key>. - Call
getscoreand/orgetShortScorefor each event you want. - Re-call
getupdateScoreat least every ~60–90s per event to keep it active. - Handle
Score/ShortScore— remember cricket only ever sendsShortScore. - On reconnect, re-subscribe — the server doesn't remember your subscriptions across a dropped connection.