torrent-hls API v1
Stream anything from BitTorrent. Torrent → HTTP Live Streaming with real-time fast-start segments, optional subtitles, per-session watermarking, and an embeddable player.
API versioning
Every endpoint exists under two prefixes:
| Prefix | Purpose | Stability |
|---|---|---|
/api/v1/* | Canonical, version-pinned | Stable forever |
/api/* | Alias for the latest version | Always maps to the current version |
These resolve to identical handlers. Use /api/ for quick integration; pin /api/v1/ for production clients that need a frozen contract.
/api/ and migrate to /api/v1/ later without changing anything else.Session control
Session control (delete, status, heartbeat) uses three independent layers, checked in order:
| Layer | Header / field | Who can control |
|---|---|---|
| Admin key | X-Admin-Key | API operator (all sessions) |
| Site key | X-Site-Key | Embedding site (its own users' sessions) |
| IP-lock | ipLock field | Creator IP (if locked) or anyone (if open) |
X-Admin-Key) used for maintenance and emergency override. It is never distributed and is not documented here. To obtain elevated access on a self-hosted deployment, see "Self-hosting".Site keys
Embedding sites can register a site key with the API operator. This key is never sent to the browser — it lives on the site's backend. When the site's backend creates a session on behalf of a user, it includes its site key. The session is then associated with that site.
Later, the site can manage any session it created using the same key, regardless of which user's IP created it.
| Scenario | Who can control |
|---|---|
| User creates session directly (no site key) | Same IP only (IP-lock) |
| Site's backend creates session with site key | Site key + same IP |
Site key set to null after creation | Same IP only |
// Site backend creates a session for a user
POST /api/streams
X-Site-Key: sk_live_abc123...
Content-Type: application/json
{ "source": "magnet:?xt=...", "ipLock": false }
// Response includes policy.siteKey = "example-site"
// Site can later DELETE /api/streams/<id> with the same X-Site-Key
SITE_KEYS=key1=siteName,key2=siteName in the env file. Keys are never logged and never returned in responses — only the human-readable site name appears.Create stream
Create a new stream from a magnet URI, infohash, or HTTPS URL to a .torrent file.
| Field | Type | Description |
|---|---|---|
| source | string | Magnet URI, 40-char infohash, or https:// URL |
| fileIndex | number | Which file to stream (default: largest video/audio) |
| transcode | boolean | Force H.264/AAC transcode with 1s keyframes |
| ipLock | boolean | Lock session to creator IP (default: server policy) |
| watermark | object | See Watermarking |
| theme | object | See Theming |
| ref | string | Optional referring site identifier (max 200 chars) |
Headers:
| Header | Purpose |
|---|---|
X-Site-Key | Associate session with embedding site |
X-IP-Lock | 0 / false to open, 1 / true to lock |
curl -X POST https://your-domain/api/streams \
-H 'content-type: application/json' \
-H 'X-Site-Key: sk_live_...' \
-d '{"source":"magnet:?xt=urn:btih:..."}'
200 response:
{
"id": "dd8255ec...",
"status": "ready",
"file": { "name": "Big Buck Bunny.mp4", "size": 276134947, "sizeHuman": "263.3 MB", "kind": "video" },
"playlist": "/hls/dd8255ec.../index.m3u8",
"embed": "/embed/dd8255ec...",
"subtitles": [],
"policy": { "ipLock": true, "transcode": false, "siteKey": null },
"control": {
"heartbeat": "/api/v1/streams/dd8255ec.../heartbeat",
"delete": "/api/v1/streams/dd8255ec...",
"interval": 10000,
"timeout": 15000,
"yours": true
}
}
Upload .torrent
Upload a raw .torrent file (max 10 MB). Body is binary.
curl -X POST https://your-domain/api/torrents \
-H 'content-type: application/x-bittorrent' \
--data-binary @movie.torrent
List streams
List active streams. Add ?mine=1 to filter to your IP only. Site keys can add ?site=1 to filter to their sessions.
Get stream
Full details for a stream. Public; no ownership check.
Delete stream
Stop stream and purge cache. Requires admin key, matching site key, matching IP (if ipLock), or ipLock=false.
Heartbeat
Required every 10 seconds. If no heartbeat for 15 s, the session is killed and segments are deleted.
{"pong": true, "streams": 3}
Health
{"ok":true,"streams":3,"maxStreams":20,"uptime":1234.5,"memory":{...},"version":"1.0.0"}
Version
{"name":"torrent-hls","version":"1.0.0","api":"v1","features":["hls","subtitles","watermark","theme","embed","heartbeat","ip-lock","site-key","rate-limit"]}
Examples
Returns a dynamically-built list of example magnets from built-in sources plus any externally-configured sources (archive.org, webtorrent-fixtures). External sources fail-soft: if a source is down, the response still includes built-in examples.
Embedding
Every session exposes an embed URL that serves a minimal player page, safe to load in an <iframe>:
<iframe src="https://your-domain/embed/dd8255ec..."
width="960" height="540" frameborder="0"
allowfullscreen></iframe>
The embed page inherits the session's watermark and theme settings, and manages its own heartbeat for the duration the iframe is open.
Watermarking
| Field | Type | Default | Description |
|---|---|---|---|
| text | string | — | Watermark text (max 100 chars) |
| url | string | null | Optional link (clickable, opens new tab) |
| position | enum | bottom-right | top-left / top-right / bottom-left / bottom-right / center |
| opacity | number | 0.6 | 0.05 to 1 |
| color | hex | #ffffff | Text color |
| bg | hex | null | Optional background color |
| size | number | 13 | Font size in px (8–72) |
Theming
{
"theme": {
"primary": "#14b8a6",
"background": "#080b10"
}
}
Rate limits
| Action | Base | Window |
|---|---|---|
| Create stream | 100 | 15 min |
| Heartbeat | 600 | 1 min |
| List streams | 600 | 1 min |
| Delete | 120 | 1 min |
Limits scale down dynamically only when server capacity exceeds 70%. 429 responses include Retry-After.
Error codes
| Code | Meaning |
|---|---|
| 400 | Bad request / malformed input / unplayable torrent |
| 403 | Not authorized to control this session |
| 404 | Session not found or expired |
| 429 | Rate limit exceeded |
| 500 | Internal error |
Error responses use the shape:
{ "error": "Human-readable message", "code": "machine_readable_code" }
Client integration
// 1. Create stream
const r = await fetch('/api/streams', {
method: 'POST',
headers: {
'content-type': 'application/json',
'X-Site-Key': SITE_KEY // only if you're an embedding site
},
body: JSON.stringify({ source: 'magnet:?xt=urn:btih:...' })
});
const s = await r.json();
// 2. Start heartbeat (required every 10s)
const hb = setInterval(() => {
fetch('/api/streams/' + s.id + '/heartbeat', { method: 'POST' })
.catch(() => clearInterval(hb));
}, s.control.interval);
// 3. Play
const hls = new Hls();
hls.loadSource(s.playlist);
hls.attachMedia(video);
// 4. Cleanup on unmount
clearInterval(hb);
fetch('/api/streams/' + s.id, { method: 'DELETE' });
Self-hosting
| Variable | Default | Purpose |
|---|---|---|
| PORT | 8787 | HTTP port |
| HOST | 127.0.0.1 | Bind address |
| BRAND_* | — | Custom branding (name, tagline, url, color, footer) |
| MAX_SESSIONS | 20 | Concurrent session cap |
| HEARTBEAT_TIMEOUT | 15 | Seconds of silence before session kill |
| IP_LOCK | 1 | Default IP-locking policy |
| ADMIN_KEY | — | Operator credential (keep secret) |
| SITE_KEYS | — | key1=name1,key2=name2 |
| EXAMPLE_SOURCES | — | JSON array of external example sources |
| TRUST_PROXY | 0 | Trust X-Forwarded-For |
See /opt/torrent-hls/.env.example for the full list with comments. Copy it to /etc/default/torrent-hls and edit, then systemctl restart torrent-hls.