B2B integration
Universal API — Sports
Server-side API for soccer, cricket, tennis, horse racing, and greyhound racing match lists and live odds from the kingexch365 exchange feed.
https://universeapi.storeOverview
Sports data is separate from the casino live tables product. Your account must have the Sports product enabled (see Admin → client products).
- Soccer —
sportId=1 - Tennis —
sportId=2 - Cricket —
sportId=4 - Horse Racing —
sportId=7 - Greyhound Racing —
sportId=4339
Product event ID format: {sportId}.{exEventId} (e.g. 1.100202662715511777).
Product access
Each B2B API key is entitled to one or both products: Casino and Sports. If Sports is not enabled, sports endpoints return:
{
"error": "Product not enabled: sports",
"products": { "casino": true, "sports": false }
}Authentication
Send your API key on every request:
X-API-Key: your-partner-api-keyOr:
Authorization: Bearer your-partner-api-keyIP allowlist
Admin → client Allowed IPs applies to all B2B sports REST and WebSocket calls from your servers:
- Leave empty — any source IP may use the API key (default).
- Non-empty — only those IPs (your backend egress) are accepted. Others get
403/ WebSocket closeIP not allowlisted.
The gateway reads the first hop of X-Forwarded-For when present (set by the reverse proxy in front of the API).
API key & endpoint setup
Every B2B integration needs one real API key and one base URL. Follow these steps end to end; the same key powers events, odds, results and TV embeds.
Request a B2B API key
uapi_live_xxxxxxxxxxxxxxxx plus an allowlisted origin list.Store the key server-side
UAPI_BASE_URL=https://universeapi.store/api
UAPI_KEY=uapi_live_xxxxxxxxxxxxxxxxVerify the key
curl -s "$UAPI_BASE_URL/sports" \
-H "X-API-Key: $UAPI_KEY"Proxy the API from your backend
/api/public/uapi/* forwards to the provider and injects the credential server-side.export async function uapi(path: string) {
const res = await fetch(`${process.env.UAPI_BASE_URL}/${path}`, {
headers: { "X-API-Key": process.env.UAPI_KEY!, accept: "application/json" },
});
if (!res.ok) throw new Error(`Upstream ${res.status}`);
return res.json();
}Wire the front end
Your key setup
Stored only in this browser to generate your snippets. Production keys must live in a server environment variable.
# .env (backend only) UAPI_BASE_URL=https://universeapi.store/api UAPI_KEY=uapi_live_xxxxxxxxxxxxxxxx # verify curl -s "https://universeapi.store/api/sports" -H "X-API-Key: $UAPI_KEY"
Rotate keys from the partner portal. A rotated key invalidates old TV embed tokens within 60 seconds.
Sport IDs
| sportId | Sport |
|---|---|
1 | Soccer |
2 | Tennis |
4 | Cricket |
7 | Horse Racing |
4339 | Greyhound Racing |
Event fields
Each object in events[] (from GET /sports/events or GET /sports/:sportId/events) includes kingexch-style capability flags alongside match-odds snapshot data.
| Field | Type | Description |
|---|---|---|
tournamentId | string | null | Exchange tournament ID (when published by upstream). |
tournamentName | string | null | Human-readable tournament or competition name. |
isBookmakers | boolean | Event has bookmaker markets available. |
isFancy | boolean | Event has fancy / line markets available. |
isSportsbook | boolean | Event has sportsbook markets available. |
isStreaming, tv | boolean | Hint that upstream advertises live TV for this event. tv is an alias of isStreaming. Flags can lag — a true value does not guarantee the CDN is publishing yet, and false does not mean mint will always fail. Prefer POST /sports/:sportId/:exEventId/tv/embed and handle 503. |
isScore | boolean | Hint that a live score payload may exist (soccer / tennis / cricket). On the event list when odds cache exists; always on GET /odds; merged into summary on GET /state. Still call GET /score or POST /score/embed and handle 503 when unpublished. |
betDelay | number | null | Bet placement delay in seconds (from upstream match odds). |
preBet, crossMatching, totalMatched | boolean / number | null | Additional upstream match-odds metadata. |
runnersData | object | null | Map of selection ID to runner name, e.g. {"235":"Team A"}. |
marketType, popular, quickLink | string | boolean | Upstream market classification flags. |
isCasinoGame, isVirtual | boolean | Upstream event type flags. |
_id | string | null | Upstream document id. |
runners[].handicap | number | null | Runner handicap from upstream. |
Endpoints
All paths are relative to https://universeapi.store/api.
/sportsX-API-Key + Sports productSport catalog (IDs and names).
/sports/eventsX-API-Key + Sports productAll events across every supported sport (soccer, tennis, cricket, horse, greyhound). Pass ?inPlay=true to return only live matches (recommended for lobby UIs).
/sports/:sportId/eventsX-API-Key + Sports productEvent list for one sport with match-odds snapshot per event.
/sports/:sportId/:exEventId/oddsX-API-Key + Sports productLive odds — match odds, fancy, bookmakers, sportsbook, lottery, and binary markets for one event. Top-level betDelay, preBet, crossMatching, and totalMatched mirror upstream match odds.
/sports/:sportId/:exEventId/stateX-API-Key + Sports productCombined event summary + odds payload.
/sports/:sportId/:exEventId/scoreX-API-Key + Sports product + domain whitelistLive scoreboard JSON for soccer (1), tennis (2), and cricket (4). Your API key must have at least one domain in allowedDomains (same whitelist used for embeds). Horse / greyhound return 404.
/sports/:sportId/:exEventId/tv/embedX-API-Key + X-TV-Client + domain whitelistMint a short-lived embed token for the live TV iframe. Returns streamingId (diamondtech gmid when primary, else LTVE channel id), source, iframePath (preferred), and optional iframeUrl. Returns 503 when no channel is assigned for the event.
/sports/:sportId/:exEventId/score/embedX-API-Key + X-TV-Client + domain whitelistMint a short-lived embed token for the scoreboard iframe. Returns iframePath and current score snapshot. Returns 503 when score is not published yet.
/tv/sports/playerembedToken + tv=true + Referer/OriginBrowser iframe HTML for live TV. Query: sportId, exEventId, embedToken, tv=true. Load only inside an iframe on an allowlisted partner page.
/tv/sports/scoreboardembedToken + tv=true + Referer/OriginBrowser iframe HTML for the live scoreboard (soccer / tennis / cricket). Same query params as the TV player. Soft-polls scores about every 5s in-page (no full reload).
WebSocket odds
Push live odds from your backend instead of polling HTTP for every tick. Prefer WebSocket for in-play viewing; keep GET /sports/:sportId/:exEventId/odds as a reconnect fallback.
wss://universeapi.store/ws/sports?sportId={sportId}&exEventId={exEventId}&apiKey=YOUR_KEY- Auth:
apiKeyquery param (same key asX-API-Key) + Sports product + IP allowlist (same rules as REST). - On connect you receive
{ "type": "subscribed", "sportId", "exEventId", "client" }. - Updates are
{ "type": "odds", "sportId", "exEventId", "data": { … } }—datahas the same shape asGET /odds. - Rejected connections close with code
1008(e.g.Unauthorized,IP not allowlisted,Product not enabled: sports).
import WebSocket from "ws";
const sportId = "4";
const exEventId = "40020266291998437";
const apiKey = process.env.UNIVERSAL_API_KEY;
const ws = new WebSocket(
`wss://universeapi.store/ws/sports?sportId=${sportId}&exEventId=${exEventId}&apiKey=${apiKey}`
);
ws.on("message", (raw) => {
const msg = JSON.parse(String(raw));
if (msg.type === "odds") {
// msg.data.matchOdds / fancy / …
}
});TV & scoreboard embeds
Browser iframes use the same pattern as casino TV: your backend mints an embed token with your API key; the user's browser loads our iframe URL with that token. Domain access is enforced via your client's allowedDomains list.
LIVE TV source (Universal sports): diamondtech is primary for cricket, soccer, tennis, horse racing, and greyhound racing. We resolve the diamondtech event id (gmid) for the match and expose it as streamingId. Kingexch LTVE is secondary when no diamondtech channel is available. Always embed via iframePath on https://universeapi.store — do not open diamondtech URLs yourself.
What partners embed: only https://universeapi.store/api/tv/sports/player?... (from mint iframePath). That page is our HTML wrapper; when diamond is primary it nests diamondtech /play/sportstv/{etid}/{gmid} with the viewer IP. Scoreboard uses /api/tv/sports/scoreboard?... the same way.
Capability by sport
| sportId | Sport | TV embed | Score JSON / scoreboard iframe |
|---|---|---|---|
1 | Soccer | Yes (per event) | Yes |
2 | Tennis | Yes (per event) | Yes |
4 | Cricket | Yes (per event) | Yes |
7 | Horse Racing | Yes (per event) | No — 404 |
4339 | Greyhound Racing | Yes (per event) | No — 404 |
TV and score availability is per event. Flags isStreaming / tv on the event list (and isScore on odds/state) are hints — they can lag behind upstream. Prefer trying the mint endpoints for in-play events and treat 503 as "not available yet". Resolve exEventId from GET /sports/:sportId/events before minting embeds.
Live first-party demo (provider site only): universeapi.store/sports.
Requirements
- Sports product enabled on your API key
- At least one domain on your allowlist (Admin → client Domains) — this is your panel origin (e.g.
mahabet.club), required for score JSON, TV mint, and score mint. Do not putuniverseapi.storeon the partner allowlist for B2B embeds X-TV-Client: trueon embed mint requestsembedDomainin the POST body when you have multiple whitelisted domains (must be on your allowlist). Subdomains of an allowlisted apex are accepted (e.g.app.partner.comifpartner.comis listed;www.is normalized away).- Iframe URL must include
tv=true - Iframe must be loaded from a page whose Referer/Origin matches your allowlist — opening the iframe URL in a new tab fails with 403
- Use
referrerpolicy="strict-origin-when-cross-origin"on your iframe. Do not setreferrerpolicy="no-referrer"— diamondtech sportstv returnsembed_origin_requiredwithout Origin/Referer - Do not mix tokens: a TV token only works on
/api/tv/sports/player; a score token only on/api/tv/sports/scoreboard - Remint before
expiresIn(typically ~3600s) - If your site uses CSP, allow
frame-src/child-srcforhttps://universeapi.store(required when usingiframePath)
Resolve event ID (any sport)
curl -sS -H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/1/events?inPlay=true"
# use events[].exEventId from the responseTV iframe flow (all sports: 1, 2, 4, 7, 4339)
curl -sS -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "X-TV-Client: true" \
-H "Content-Type: application/json" \
-d '{"embedDomain":"your-partner-site.com"}' \
"https://universeapi.store/api/sports/{sportId}/{exEventId}/tv/embed"{
"sportId": "4",
"exEventId": "4002026823127209",
"productId": "4.4002026823127209",
"embedToken": "YkpY2Roz...",
"expiresIn": 3600,
"streamingId": "807289833",
"source": "allpanel-sportstv",
"iframePath": "/api/tv/sports/player?sportId=4&exEventId=...&embedToken=...&tv=true"
}streamingId is the diamondtech event id (gmid) when source is allpanel-crickettv / allpanel-sportstv. For LTVE secondary feeds it is the LTVE channel id. For diamond/AllPanel, mint omits iframeUrl — you must use iframePath so we remint playback with the viewer IP. LTVE-only responses may include an optional iframeUrl; still prefer iframePath in production.
<iframe
src="https://universeapi.store/api/tv/sports/player?sportId={sportId}&exEventId={exEventId}&embedToken=TOKEN&tv=true"
allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
style="width:100%;aspect-ratio:16/9;border:0"
></iframe>Prefer iframePath on https://universeapi.store — that path enforces your domain allowlist and token kind, then nests diamondtech (or LTVE) inside our player. Do not paste diamondtech /play/... URLs into your panel.
Channel vs broadcast: a successful mint means a stream channel was assigned (streamingId). The player may still show a waiting screen until the feed is publishing. That is expected and is not an API error. Mint returns 503 only when no channel is assigned for the event.
Per-sport TV examples
POST https://universeapi.store/api/sports/1/{exEventId}/tv/embedPOST https://universeapi.store/api/sports/2/{exEventId}/tv/embedPOST https://universeapi.store/api/sports/4/{exEventId}/tv/embedPOST https://universeapi.store/api/sports/7/{exEventId}/tv/embedPOST https://universeapi.store/api/sports/4339/{exEventId}/tv/embedScore JSON shape (soccer, tennis, cricket)
{
"client": "your-client-name",
"sportId": "4",
"exEventId": "4002026712143720420",
"productId": "4.4002026712143720420",
"score": {
"sportId": "4",
"exEventId": "4002026712143720420",
"sportName": "Cricket",
"commentary": "",
"slider": [ /* upstream score panels (teamInfo, scoreItems, statusCommentry, …) */ ],
"updatedAt": "2026-07-13T07:00:00.000Z"
}
}Parse score.slider for sport-specific panels. Cricket / soccer / tennis layouts differ; the scoreboard iframe renders a kingexch-style board for you if you do not want to parse slider yourself.
Scoreboard iframe flow (soccer, tennis, cricket only)
curl -sS -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "X-TV-Client: true" \
-H "Content-Type: application/json" \
-d '{"embedDomain":"your-partner-site.com"}' \
"https://universeapi.store/api/sports/{sportId}/{exEventId}/score/embed"{
"sportId": "4",
"exEventId": "4002026712143720420",
"productId": "4.4002026712143720420",
"embedToken": "YkpY2Roz...",
"expiresIn": 3600,
"score": { "sportId": "4", "exEventId": "...", "slider": [], "updatedAt": "..." },
"iframePath": "/api/tv/sports/scoreboard?sportId=4&exEventId=...&embedToken=...&tv=true"
}<iframe
src="https://universeapi.store/api/tv/sports/scoreboard?sportId={sportId}&exEventId={exEventId}&embedToken=TOKEN&tv=true"
referrerpolicy="strict-origin-when-cross-origin"
style="width:100%;min-height:220px;border:0;background:#000"
></iframe>Score mint response includes embedToken, expiresIn, iframePath, and a score snapshot. The scoreboard soft-polls about every 5s in-page (no full reload) so Referer stays valid — you do not need to remint the token just to refresh scores.
Per-sport scoreboard examples
POST https://universeapi.store/api/sports/1/{exEventId}/score/embed
GET https://universeapi.store/api/sports/1/{exEventId}/scorePOST https://universeapi.store/api/sports/2/{exEventId}/score/embed
GET https://universeapi.store/api/sports/2/{exEventId}/scorePOST https://universeapi.store/api/sports/4/{exEventId}/score/embed
GET https://universeapi.store/api/sports/4/{exEventId}/scoreHorse (7) and greyhound (4339) do not have scoreboard endpoints — GET /score and POST /score/embed return 404.
For server-side polling (no iframe), use GET /sports/:sportId/:exEventId/score every 2–5s during in-play events (soccer, tennis, cricket only).
Live demo
On the provider site you can preview working TV and scoreboard iframes without a B2B API key: /sports (in-play list) and event detail /sports/:sportId/:exEventId.
That page uses first-party public routes (GET /public/sports/events, GET /public/tv/sports/player, GET /public/tv/sports/scoreboard) and is not for partner embedding. Partners must use the B2B mint + /api/tv/sports/* flow above.
The widget below issues real requests against this site’s backend proxy, which forwards to the provider with server-side credentials.
Sports API — live responses
idle · 0 msGET /api/public/uapi/sports
…
Polling guide
Poll the endpoints below from your backend — do not call the API from end-user browsers.
How often you should poll
Prefer WebSocket odds for live event screens. If you poll HTTP instead, apply these rates per endpoint and only for events your users are actively viewing. Do not poll the full event list at odds frequency.
| Endpoint | When | Poll interval |
|---|---|---|
GET /sports/events or GET /sports/:sportId/events | Discovery / lobby | 30–60s |
GET /sports/:sportId/:exEventId/odds | Pre-match event on screen | 5–10s |
GET /sports/:sportId/:exEventId/odds | In-play event on screen | 500ms–1s |
GET /sports/:sportId/:exEventId/state | Same as odds for that event | Same as odds row above |
GET /sports/:sportId/:exEventId/score | In-play event with scoreboard | 2–5s |
Best practices
- Poll odds only for events on screen — not every event in the list.
- Use
updatedAtin the odds response; skip UI updates when it has not changed. - Polling faster than the recommended intervals only increases load on your API key.
- Do not poll
/sports/eventsevery 1s or 500ms.
Errors
- 401 — missing/invalid API key; missing
X-TV-Clienton mint; missingtv=trueon iframe URL; invalid/expired/wrong-kind embed token - 403 — subscription inactive, sports product not enabled, caller IP not on Allowed IPs (when configured), domain not on allowlist (also blocks
GET /scorewhen allowlist is empty), missing browser Referer/Origin on iframe, or token bound to a different embed domain - 404 — unknown sportId, event excluded, or scoreboard not supported (horse/greyhound)
- 400 —
embedDomainnot on this client's allowlist - 502 — upstream mint failure while creating an embed token
- 503 — ingest unavailable; no TV channel assigned; or score not published yet. A waiting TV player (mint succeeded) is not a 503 — the CDN feed may still be offline.
- Nested diamond player JSON
embed_origin_required— your panel iframe is missing Origin/Referer (oftenreferrerpolicy="no-referrer"). Usestrict-origin-when-cross-originand load the iframe from your allowlisted panel page
Examples
curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/4/events?inPlay=true"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/7/events?inPlay=true"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/4339/43390020266291998437/odds"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/1/100202662715511777/odds"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/2/200202662832959134/state"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/1/{exEventId}/score"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/2/{exEventId}/score"curl -sS \
-H "X-API-Key: YOUR_KEY" \
"https://universeapi.store/api/sports/4/{exEventId}/score"curl -sS -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "X-TV-Client: true" \
-H "Content-Type: application/json" \
-d '{"embedDomain":"your-partner-site.com"}' \
"https://universeapi.store/api/sports/4/{exEventId}/tv/embed"curl -sS -X POST \
-H "X-API-Key: YOUR_KEY" \
-H "X-TV-Client: true" \
-H "Content-Type: application/json" \
-d '{"embedDomain":"your-partner-site.com"}' \
"https://universeapi.store/api/sports/4/{exEventId}/score/embed"B2B integration
Keep the API key server-side, mint embed tokens from your backend, and serve only the iframePath URLs to browsers. Remint before expiresIn, respect the polling intervals above, and treat 503 responses as "not available yet" rather than hard failures.
Casino tables are documented separately — Casino API docs.