API guide
Overview
The API is open, read-only and needs no key. Every response is JSON encoded as UTF-8 and has an ok field.
| Base URL | https://api.embedse.com/v1 |
|---|---|
| Methods | GET, HEAD |
| CORS | Allowed from any origin |
| Times | Unix timestamps in seconds, UTC |
| Freshness | Data refreshes every 30 seconds; responses are cached for up to 15 seconds |
Match and stream IDs are short opaque strings such as k7Qx9PmA. Store them as strings and do not try to parse them.
Rate limits
Each IP address may send 60 requests per minute with a short burst allowance. Every response carries RateLimit-Policy: 60;w=60. Over the limit the API answers 429 with a Retry-After header. Poll the match list every 30–60 seconds; polling faster returns the same cached data.
Successful responses have an ETag header. Send it back in If-None-Match and the API answers 304 Not Modified with an empty body when nothing changed.
Call the API from your server and cache the result there. Your visitors should load your cached copy, not the API directly.
Errors
{ "ok": false, "error": "match_not_found" }
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_status | The status filter is not one of the allowed values |
| 400 | invalid_source | The source filter contains an unknown source ID |
| 404 | match_not_found, stream_not_found, not_found | Unknown ID or path |
| 405 | method_not_allowed | Only GET and HEAD are accepted |
| 429 | rate_limited | Too many requests from your IP |
| 503 | data_unavailable | Data is temporarily out of date; retry shortly |
List matches
GET /v1/matches
Matches come in this order: live matches with an online server, live matches with other servers, live matches without servers, upcoming matches by start time, then ended matches with the most recent first. Ended matches stay in the list for 6 hours and have no servers.
| Query | Values |
|---|---|
status | live, upcoming or ended |
sport | Sport ID. Common values: soccer, basketball, american-football, hockey, baseball, combat, racing, tennis, golf |
category | Category slug, for example nba or nhl |
embeddable | 1 to return only matches that have at least one server |
source | One source ID or a comma separated list, for example alpha,bravo. Returns only matches with a server from those sources; counts cover those servers only |
curl "https://api.embedse.com/v1/matches?status=live&embeddable=1"
curl "https://api.embedse.com/v1/matches?status=live&source=bravo"
{
"ok": true,
"updated_at": 1794590000,
"count": 1,
"matches": [
{
"id": "k7Qx9PmA",
"title": "Chicago Bulls vs Atlanta Hawks",
"category": "NBA",
"category_slug": "nba",
"sport": "basketball",
"league": "NBA",
"home": "Chicago Bulls",
"away": "Atlanta Hawks",
"home_logo": "https://embedse.com/logos/3f9a1c0b7e5d2a4c6b81.png",
"away_logo": "https://embedse.com/logos/9c2e7d4a1b0f3e6c5a92.png",
"start": 1794600000,
"end": 1794612000,
"status": "live",
"streams_count": 2,
"online_streams": 1,
"embed_url": "https://embedse.com/m/k7Qx9PmA"
}
]
}
Get a match
GET /v1/matches/{id}
Returns one match object with its streams array included. Add ?source=alpha to keep only servers from that source.
Match streams
GET /v1/matches/{id}/streams
Accepts the same source filter.
{
"ok": true,
"updated_at": 1794590000,
"match_id": "k7Qx9PmA",
"count": 1,
"streams": [
{
"id": "Zr4mT1bQ",
"server": 1,
"name": "Bravo",
"source": "bravo",
"language": "en",
"status": "online",
"embed_url": "https://embedse.com/m/k7Qx9PmA/1"
}
]
}
Stream status
GET /v1/streams/{id}/status
{
"ok": true,
"updated_at": 1794590000,
"stream": {
"id": "Zr4mT1bQ",
"server": 1,
"name": "Bravo",
"source": "bravo",
"language": "en",
"status": "online",
"embed_url": "https://embedse.com/m/k7Qx9PmA/1"
},
"match_id": "k7Qx9PmA",
"match_status": "live"
}
Sources
GET /v1/sources
Every server belongs to a source with a fixed ID: alpha, bravo, charlie, delta, echo, foxtrot, golf, hotel. IDs never change, so you can store them and use them in the source filter. Use the name of a server as its display label. This endpoint lists every source with its current counts.
{
"ok": true,
"updated_at": 1794590000,
"count": 7,
"sources": [
{
"id": "alpha",
"name": "Alpha",
"matches": 12,
"live_matches": 9,
"streams": 20,
"online_streams": 18
}
]
}
Categories
GET /v1/categories
Lists every category that has matches right now, with the most live matches first. Use slug in the category filter. sport is null for categories that mix several sports.
{
"ok": true,
"updated_at": 1794590000,
"count": 1,
"categories": [
{
"slug": "nba",
"name": "NBA",
"sport": "basketball",
"matches": 11,
"live_matches": 4,
"embeddable_matches": 6
}
]
}
Embedding
Every match has one embed link. Each server also has its own link, which is the match link plus the server number.
Wrap the iframe in a 16:9 box so it scales with the page width:
<div style="position:relative;width:100%;aspect-ratio:16/9;background:#000">
<iframe src="https://embedse.com/m/k7Qx9PmA"
style="position:absolute;inset:0;width:100%;height:100%;border:0"
allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
allowfullscreen referrerpolicy="origin"></iframe>
</div>
| Link | Behavior |
|---|---|
/m/{match_id} | Automatic. The player opens the first online server. If it stops working, the player switches to the next server on its own. Viewers can pick a server from the Sources menu in the top left corner of the player. The menu shows only when there is more than one server. |
/m/{match_id}/{server} | Single server. The player plays only that server and shows no menu. If the server goes down, the player retries the same server and never switches to another one. Use this link when you show your own server list above the player. |
The match embed_url is the automatic link. Each stream's embed_url is a single server link.
Server numbers can change when servers are added or removed. Read them again from the API instead of storing them for a long time. A single server link whose server no longer exists shows a "no servers available" message.
Filtering by source
Add ?source=bravo (or a comma separated list) to an automatic link to limit the player and its menu to servers from those sources. Links returned by a request with the source filter already include it.
https://embedse.com/m/k7Qx9PmA?source=alpha
https://embedse.com/m/k7Qx9PmA?source=alpha,bravo
Do not add the sandbox attribute. Sandboxed frames are detected and will not play.
Player events
The player sends messages to the parent page with postMessage. Every message has type: "embedse", event, match (the match ID) and status (the match status).
| Event | When | Extra fields |
|---|---|---|
ready | The player has loaded | servers: number of servers |
server | The player starts a server | server, name, manual (true when the viewer picked it) |
playing | Video starts playing on a server | server, name |
unavailable | Nothing can play right now | reason: not_started, no_servers or all_failed |
ended | The match has ended |
window.addEventListener('message', function (e) {
if (e.origin !== 'https://embedse.com' || !e.data || e.data.type !== 'embedse') return;
if (e.data.event === 'playing') console.log('Playing', e.data.name);
if (e.data.event === 'ended') console.log('Match ended');
});
Quick start
List live matches that have servers and put the first one on the page:
const res = await fetch('https://api.embedse.com/v1/matches?status=live&embeddable=1');
const data = await res.json();
if (data.ok && data.matches.length) {
const m = data.matches[0];
const frame = document.createElement('iframe');
frame.src = m.embed_url;
frame.allow = 'autoplay; fullscreen; encrypted-media; picture-in-picture';
frame.allowFullscreen = true;
frame.style.cssText = 'width:100%;aspect-ratio:16/9;border:0';
document.getElementById('player').appendChild(frame);
}
Ads
The API is free and has no ads. The JSON responses never contain ads.
The embed player runs one ad network script to keep the service free. When a viewer clicks the player, at most one ad opens every 10 seconds. The player shows no banners, overlays or pre-roll video ads, and it never blocks or pauses the stream.
Ad-free embeds are available. See Contact.
Contact
Use the contact form for:
- Ad-free embeds for your website
- Custom specs for your project
- Partnership, bug reports and other questions
Objects
Match
| Field | Type | Notes |
|---|---|---|
id | string | Opaque match ID |
title | string | |
category, category_slug | string | |
sport | string | null | See the sport filter for values |
league | string | null | Competition name, for example Premier League; null when unknown |
home, away | string | null | Null for events without two sides |
home_logo, away_logo | string | null | Team logo URL; null when there is no logo |
start, end | integer | Scheduled start and expected end. For ended matches end is when the match ended |
status | string | live, upcoming, ended |
streams_count, online_streams | integer | |
embed_url | string | null | Null when the match has no servers |
Stream
| Field | Type | Notes |
|---|---|---|
id | string | Opaque stream ID |
server | integer | Position in the match server list, starting at 1 |
name | string | Display label |
source | string | null | Source ID; null when the server has no source |
language | string | null | Commentary language code such as en; null when unknown |
status | string | online, idle (not started yet), offline |
embed_url | string |