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.

Open protocol. This API is designed for open-source deployment. Every endpoint is documented below; no credentials are required to create a stream.

API versioning

Every endpoint exists under two prefixes:

PrefixPurposeStability
/api/v1/*Canonical, version-pinnedStable forever
/api/*Alias for the latest versionAlways 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.

Both prefixes accept the same headers, request bodies, and return the same response shapes. This means you can start with /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:

LayerHeader / fieldWho can control
Admin keyX-Admin-KeyAPI operator (all sessions)
Site keyX-Site-KeyEmbedding site (its own users' sessions)
IP-lockipLock fieldCreator IP (if locked) or anyone (if open)
Operator credential. The server operator holds a private admin key (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.

ScenarioWho can control
User creates session directly (no site key)Same IP only (IP-lock)
Site's backend creates session with site keySite key + same IP
Site key set to null after creationSame 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 key format. Site keys are configured by the operator as 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

POST/api/streams alias: /api/v1/streams

Create a new stream from a magnet URI, infohash, or HTTPS URL to a .torrent file.

FieldTypeDescription
sourcestringMagnet URI, 40-char infohash, or https:// URL
fileIndexnumberWhich file to stream (default: largest video/audio)
transcodebooleanForce H.264/AAC transcode with 1s keyframes
ipLockbooleanLock session to creator IP (default: server policy)
watermarkobjectSee Watermarking
themeobjectSee Theming
refstringOptional referring site identifier (max 200 chars)

Headers:

HeaderPurpose
X-Site-KeyAssociate session with embedding site
X-IP-Lock0 / 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

POST/api/torrents alias: /api/v1/torrents

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

GET/api/streams alias: /api/v1/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

GET/api/streams/<id> alias: /api/v1/streams/<id>

Full details for a stream. Public; no ownership check.

Delete stream

DELETE/api/streams/<id> alias: /api/v1/streams/<id>

Stop stream and purge cache. Requires admin key, matching site key, matching IP (if ipLock), or ipLock=false.

Heartbeat

POST/api/streams/<id>/heartbeat alias: /api/v1/streams/<id>/heartbeat

Required every 10 seconds. If no heartbeat for 15 s, the session is killed and segments are deleted.

{"pong": true, "streams": 3}

Health

GET/api/health alias: /api/v1/health
{"ok":true,"streams":3,"maxStreams":20,"uptime":1234.5,"memory":{...},"version":"1.0.0"}

Version

GET/api/version alias: /api/v1/version
{"name":"torrent-hls","version":"1.0.0","api":"v1","features":["hls","subtitles","watermark","theme","embed","heartbeat","ip-lock","site-key","rate-limit"]}

Examples

GET/api/examples alias: /api/v1/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

FieldTypeDefaultDescription
textstring—Watermark text (max 100 chars)
urlstringnullOptional link (clickable, opens new tab)
positionenumbottom-righttop-left / top-right / bottom-left / bottom-right / center
opacitynumber0.60.05 to 1
colorhex#ffffffText color
bghexnullOptional background color
sizenumber13Font size in px (8–72)

Theming

{
  "theme": {
    "primary": "#14b8a6",
    "background": "#080b10"
  }
}

Rate limits

ActionBaseWindow
Create stream10015 min
Heartbeat6001 min
List streams6001 min
Delete1201 min

Limits scale down dynamically only when server capacity exceeds 70%. 429 responses include Retry-After.

Error codes

CodeMeaning
400Bad request / malformed input / unplayable torrent
403Not authorized to control this session
404Session not found or expired
429Rate limit exceeded
500Internal 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

VariableDefaultPurpose
PORT8787HTTP port
HOST127.0.0.1Bind address
BRAND_*—Custom branding (name, tagline, url, color, footer)
MAX_SESSIONS20Concurrent session cap
HEARTBEAT_TIMEOUT15Seconds of silence before session kill
IP_LOCK1Default IP-locking policy
ADMIN_KEY—Operator credential (keep secret)
SITE_KEYS—key1=name1,key2=name2
EXAMPLE_SOURCES—JSON array of external example sources
TRUST_PROXY0Trust 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.