REST API quick start
Everything the app does, a program can do too. This page walks one live from start to finish over the API. Every request and response below was made against a running creekX server; only the keys are replaced.
Replace live.example.com with your server's address and
YOUR-API-KEY with a key from Settings → Keys for automation (Keys and webhooks). The whole API is described at /openapi.json on your server.
1. Authenticate
Send the key with every request as X-CreekX-Key. Requests are JSON.
export CREEKX=https://live.example.com/v1
export KEY=YOUR-API-KEY
curl -H "X-CreekX-Key: $KEY" $CREEKX/streams
A mistake answers with a problem description that says what to fix. For example, an unknown webhook event:
{
"title": "Bad request",
"status": 400,
"detail": "The request body is invalid.",
"remedy": "Fix every listed issue. The request shapes are in the OpenAPI document at /openapi.json.",
"issues": [{
"path": "events[0]",
"message": "Invalid enum value. Expected 'stream.live' | 'stream.ended' | 'recording.saved' | ..."
}]
}
2. Make a stream
curl -X POST $CREEKX/streams \
-H "X-CreekX-Key: $KEY" -H "content-type: application/json" \
-d '{"name":"Main stage"}'
The answer (shortened). The key is never returned here, only its last four characters:
{
"id": "eebc8cd3-14d5-4ea1-b9b0-aa8c483d67a7",
"name": "Main stage",
"obs": {
"server": "rtmp://live.example.com:1935/live",
"streamKeyMasked": "xxxx-xxxx-xxxx-cOhk"
},
"srt": true,
"rtsp": true,
"browser": { "whipUrl": "https://live.example.com/live/eebc8cd3-.../browser/whip" },
"record": true,
"destinationIds": [],
"qualities": false,
"chat": false
}
3. Get its addresses
Revealing the key is a separate call, and it is written to the audit log. The answer gives the stream in the form every tool takes:
curl -X POST $CREEKX/streams/$STREAM_ID/key/reveal -H "X-CreekX-Key: $KEY"
{
"key": "YOUR-STREAM-KEY",
"obs": { "server": "rtmp://live.example.com:1935/live",
"streamKey": "eebc8cd3-...?user=perpetua&pass=YOUR-STREAM-KEY" },
"srt": { "url": "srt://live.example.com:8890?streamid=publish:live/eebc8cd3-...:perpetua:YOUR-STREAM-KEY",
"passphrase": null },
"rtsp": { "url": "rtsp://perpetua:[email protected]:8554/live/eebc8cd3-..." },
"browser": { "whipUrl": "https://live.example.com/live/eebc8cd3-.../browser/whip",
"bearerToken": "YOUR-STREAM-KEY" }
}
Paste obs.server and obs.streamKey into OBS, or use any of the
others from an encoder. Lost it? POST /streams/{id}/key/rotate makes a new
one and the old one stops at once.
4. Go live from FFmpeg
The same three ways in we used to test this page. Keyframes every second and no B-frames give viewers the lowest delay.
# RTMP (as OBS sends)
ffmpeg -re -i show.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -b:v 2500k \
-c:a aac -b:a 128k -ar 48000 -f flv "rtmp://live.example.com:1935/live/STREAM_ID?user=perpetua&pass=YOUR-STREAM-KEY"
# SRT (better over the internet and mobile networks)
ffmpeg -re -i show.mp4 -c:v libx264 -preset veryfast -tune zerolatency -g 30 -b:v 2500k \
-c:a aac -b:a 128k -f mpegts "srt://live.example.com:8890?streamid=publish:live/STREAM_ID:perpetua:YOUR-STREAM-KEY"
# RTSP (as an encoder or camera pushes)
ffmpeg -re -i show.mp4 -c:v libx264 -tune zerolatency -g 30 -c:a aac \
-rtsp_transport tcp -f rtsp "rtsp://perpetua:[email protected]:8554/live/STREAM_ID"
Use the exact addresses from step 3: they already hold your stream's id and key.
5. Check it is live
curl -H "X-CreekX-Key: $KEY" $CREEKX/streams/$STREAM_ID/status
{
"state": "live",
"since": "2026-10-15T17:14:21.539Z",
"tracks": ["H264", "MPEG-4 Audio"],
"bytesReceived": 94579978,
"viewers": 4,
"destinations": [
{ "label": "YouTube", "health": "sending", "problem": null },
{ "label": "Facebook", "health": "sending", "problem": null },
{ "label": "Twitch", "health": "sending", "problem": null }
],
"backup": null
}
state is live while video arrives. Each platform reports its own
health, with the reason when it is not sending.
6. Restream to platforms
Add each platform once. The stream key is encrypted when it arrives and never returned by any read:
curl -X POST $CREEKX/destinations \
-H "X-CreekX-Key: $KEY" -H "content-type: application/json" \
-d '{"kind":"youtube","label":"YouTube","ingestUrl":"rtmp://a.rtmp.youtube.com/live2","streamKey":"YOUR-YOUTUBE-KEY"}'
kind is one of youtube, facebook,
twitch, kick, rtmp, srt or
rtsp. Then choose where a stream goes (up to 10), and turn on its watch link
and chat in the same call:
curl -X PATCH $CREEKX/streams/$STREAM_ID \
-H "X-CreekX-Key: $KEY" -H "content-type: application/json" \
-d '{"destinationIds":["DESTINATION_ID"],"watch":true,"chat":true}'
The same call takes record, qualities (several sizes for HLS
viewers), backupId (another stream to switch to if this one stops),
publishFrom and watchFrom (address lists) and
pull (an rtsp://, rtmp://, srt:// or
.m3u8 address to fetch instead).
7. A private, expiring watch link
curl -X POST $CREEKX/streams/$STREAM_ID/tokens \
-H "X-CreekX-Key: $KEY" -H "content-type: application/json" \
-d '{"use":"play","expiresInSeconds":7200,"oneTime":true}'
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi...",
"use": "play",
"oneTime": true,
"expiresAt": "2026-10-15T19:19:05.187Z",
"watch": { "watchPath": "/watch/eebc8cd3-.../eyJhbGciOi..." }
}
The token is a signed JWT (HS256) naming the stream, its use, when it expires and whether
it is for one viewer. "use":"publish" makes a guest key for someone else's
encoder. POST /streams/{id}/tokens/revoke stops every token made so far.
8. Hear about events: webhooks
curl -X POST $CREEKX/webhooks \
-H "X-CreekX-Key: $KEY" -H "content-type: application/json" \
-d '{"url":"https://example.com/hooks/creekx","events":["stream.live","stream.ended","recording.saved"]}'
{
"webhook": {
"id": "74855578-dee4-4f09-aadb-375800e05ab4",
"url": "https://example.com/hooks/creekx",
"events": ["stream.live", "stream.ended", "recording.saved"],
"lastDelivery": null
},
"secret": "whsec_..."
}
Events: stream.live, stream.ended, recording.saved,
video.ready, channel.on_air, channel.off_air,
viewer.started, viewer.ended. The secret is shown once. Each
delivery carries CreekX-Event, CreekX-Delivery and
CreekX-Signature: t=…,v1=…, where v1 is the hex HMAC-SHA256 of
<t>.<body> keyed with the whole secret. Check it like this
(Node.js):
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received, before any JSON parsing.
export function fromCreekX(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!(age < 300)) return false; // older than five minutes: refuse replays
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'hex');
return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, 'hex'));
}
POST /webhooks/{id}/test sends a test event, and
GET /webhooks/{id}/deliveries lists recent deliveries and their answers.
What else the API covers
| For | Endpoints |
|---|---|
| Videos |
POST /uploads (resumable), GET /assets,
/assets/{id}/captions, /captions/search
|
| 24/7 channels |
GET /channels, POST /channels/{id}/live (on air),
/channels/{id}/schedule, GET /epg
|
| IP cameras |
POST /cameras/find, PUT /streams/{id}/camera,
/camera/move, /camera/presets,
/camera/timeline
|
| Viewers and safety |
GET /viewers, /streams/{id}/sessions,
/viewer-blocks, /streams/{id}/srt-passphrase
|
| The server |
GET /healthz, /admin/system,
/support/check
|