Relaya API
Everything you can do in the Relaya dashboard is available over a simple JSON REST API: open SRT/RTMP contribution streams with primary and backup inputs, push to YouTube or your broadcaster, record and cut clips, publish videos with embeddable players, run IP-camera pulls and 24/7 playout, and read viewer analytics.
| Base URL | https://app.relaya.tv |
|---|---|
| Format | JSON request and response bodies (Content-Type: application/json), UTF-8 |
| Language | Error messages in English when you send X-Lang: en (otherwise Turkish) |
Authentication
Create an API key in the dashboard under Team → API keys. Keys look like rly_… and are shown only once. Send the key as a bearer token:
curl https://app.relaya.tv/api/channels \
-H "Authorization: Bearer rly_YOUR_KEY" \
-H "X-Lang: en"
- A key acts with the permissions of the team member who created it, inside that team's organization.
- Read-only keys can only make
GETrequests. Read + write keys can do everything except billing, team management, audit logs and key management — those stay in the dashboard. - Revoke a key at any time in the dashboard; it stops working immediately.
Errors & rate limits
Errors return a non-2xx status and a body like {"error":"Stream not found"}.
| Status | Meaning |
|---|---|
| 400 | Invalid input |
| 401 | Missing or invalid API key |
| 402 | Plan limit reached (streams, destinations, storage…) or trial ended |
| 403 | Permission missing, read-only key, or endpoint not available to keys |
| 404 | Not found (or not visible to this key) |
| 429 | Rate limit — see Retry-After |
| 502 | Relay temporarily unavailable — retry |
Each key may make 120 requests per minute. Responses include X-RateLimit-Limit and X-RateLimit-Remaining.
Streams
A stream (channel) is a contribution endpoint on the relay with a primary SRT input, an optional backup input (Pro/Team) and an RTMP input. Relaya fails over to the backup automatically.
List streams with their ingest addresses and destinations.
[{
"id": 12, "name": "Final — Court A", "relay_id": "acme-3f9c1a",
"srt_pri": "srt://rly1.activeproduction.com:8890?streamid=publish:acme-3f9c1a-pri&passphrase=…",
"srt_bak": "srt://…-bak&passphrase=…", "rtmp": "rtmp://…",
"whip": "https://rly1.activeproduction.com:8889/acme-3f9c1a-pri/whip",
"record": 1, "public_live": 0, "push_targets": [ { "id": 4, "label": "YouTube", "url": "rtmp://…", "enabled": 1 } ]
}]
curl -X POST https://app.relaya.tv/api/channels \
-H "Authorization: Bearer rly_YOUR_KEY" -H "Content-Type: application/json" \
-d '{"name":"Final — Court A","record":true}'
Returns the new stream including srt_pri, srt_bak and rtmp — paste these into your encoder.
Rename: {"name":"New name"}
Deletes the stream and its destinations.
Live status
Real-time signal health from the relay. Poll every 2–5 seconds.
{ "id": "acme-3f9c1a", "active_source": "pri", "pri_kbps": 5210, "bak_kbps": 0,
"switches": 0, "uptime_pct": 100, "record": true, "events": [ { "t": 1759761234000, "e": "signal up" } ] }
A stream is live when pri_kbps > 0 or bak_kbps > 0; active_source tells which input is on air.
Destinations (multistream)
curl -X POST https://app.relaya.tv/api/channels/12/push \
-H "Authorization: Bearer rly_YOUR_KEY" -H "Content-Type: application/json" \
-d '{"label":"YouTube","url":"rtmp://a.rtmp.youtube.com/live2/STREAM-KEY"}'
url must start with rtmp://, rtmps:// or srt://. The number of destinations per stream depends on your plan.
Change label / url.
Recording & clips
{"on":true} starts recording, {"on":false} stops.
[ { "name": "2026-10-06_19-00-02.mp4", "start": 1759766402000, "duration": 5412.3, "size": 3221225472 } ]
Downloads a recording as MP4.
Returns an MP4 of the last N seconds of the live recording — or use ?start=<unix ms>&dur=<seconds> for any range.
curl -o goal.mp4 "https://app.relaya.tv/api/channels/12/clip?last=30" -H "Authorization: Bearer rly_YOUR_KEY"
DVR & Live-to-VOD
With DVR on, viewers of the public player can rewind the live stream within the chosen window and jump back with "Go live". With Live-to-VOD on, the whole session is added to your Videos library automatically when the stream ends (sessions under 1 minute are skipped; storage quota applies). The program output is recorded without re-encoding. Longest DVR window: Trial/Starter 30 min, Pro 2 h, Team 4 h.
{ "dvr": true, "window_min": 60, "auto_vod": true, "vod_public": false }
The response also shows recording, session_s, the last automatic video and any error (e.g. quota full).
Live player & embed
{"public":true} publishes the stream to the Relaya player; {"public":false} hides it again.
{ "public": true, "token": "mWTxHvUanyCo",
"watch_url": "https://app.relaya.tv/live/mWTxHvUanyCo",
"embed_url": "https://app.relaya.tv/embed/live/mWTxHvUanyCo" }
Embed it anywhere:
<iframe src="https://app.relaya.tv/embed/live/mWTxHvUanyCo" width="960" height="540"
style="width:100%;height:auto;aspect-ratio:16/9;border:0"
allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
The player starts automatically when the stream goes live (muted autoplay), works on iOS, Android and desktop, and reports views to Analytics.
Video library
Videos are stored as MP4 (for download) plus adaptive HLS (1080p / 720p / 480p) for smooth playback.
{ "videos": [ { "id": 31, "title": "Highlights", "status": "ready", "duration": 99.7, "size": 86299282,
"public": 1, "hls": 1, "views": 412, "watch_url": "https://app.relaya.tv/v/nVISYCve7Uwr",
"embed_url": "https://app.relaya.tv/embed/v/nVISYCve7Uwr", "file_url": "/vf/nVISYCve7Uwr.mp4",
"hls_url": "/vh/nVISYCve7Uwr/master.m3u8", "thumb_url": "/vt/nVISYCve7Uwr.jpg" } ],
"used_bytes": 1234567, "quota_bytes": 26843545600 }
status is processing → ready (or error). Private videos return short-lived signed URLs.
{"channel_id":12, "name":"2026-10-06_19-00-02.mp4", "title":"Second half", "start":2700, "dur":2700}
Omit start/dur to import the whole recording.
{"title":"…","description":"…","public":true}
Pre-convert to the playout format (1080p25 · 5 Mbps) so playout sources start instantly.
Chunked upload
Upload files up to 20 GB in 16 MB chunks. Chunks may be sent in parallel (we recommend 4) and in any order.
# 1) start
curl -X POST https://app.relaya.tv/api/videos/upload/init -H "Authorization: Bearer rly_YOUR_KEY" \
-H "Content-Type: application/json" -d '{"title":"Match","size":734003200}'
# → {"id":"8397026d2a2a6c91d02e00f8","chunk":16777216,"parallel":4}
# 2) send chunk i (bytes i*chunk … i*chunk+chunk-1)
curl -X PUT "https://app.relaya.tv/api/videos/upload/8397026d2a2a6c91d02e00f8?i=0" \
-H "Authorization: Bearer rly_YOUR_KEY" -H "Content-Type: application/octet-stream" --data-binary @part0
# 3) finish → video starts processing
curl -X POST https://app.relaya.tv/api/videos/upload/8397026d2a2a6c91d02e00f8/complete -H "Authorization: Bearer rly_YOUR_KEY"
Unfinished uploads are discarded after 24 hours. DELETE /api/videos/upload/{id} cancels one.
Sources: IP camera & playout
A source feeds a stream's primary or backup input without an encoder: pull an IP camera (RTSP, RTMP, SRT caller or HLS) or play library videos as a live loop.
Lists sources with live state (running, preparing, retry, error, off).
{"url":"rtsp://user:pass@cam.example.com:554/stream1"} → codec and resolution of the source.
# IP camera on the backup input, transcoded to 720p CBR
{"kind":"pull","name":"Pitch cam","channel_id":12,"leg":"bak","url":"rtsp://user:pass@cam.example.com:554/stream1",
"mode":"720p","bitrate":3000,"rc":"cbr","enabled":true}
# 24/7 playout of library videos
{"kind":"playout","name":"Promo loop","channel_id":12,"items":[31,32,33],"loop":true,
"mode":"1080p","bitrate":5000,"rc":"vbr","enabled":true}
| Field | Values |
|---|---|
mode | copy (pull only, no transcoding) · 720p · 1080p |
bitrate | kbps — 720p: 1000–8000, 1080p: 1000–15000 |
rc | vbr (capped) or cbr (constant, for broadcast delivery) |
leg | pri or bak (backup requires Pro/Team) |
Start/stop with {"enabled":true|false}, or change any field.
Radio / audio-only
Any stream can run as a radio station. Connect your radio software (BUTT, Mixxx, RadioBOSS, SAM Broadcaster, Liquidsoap…) with the Icecast protocol, or send audio-only SRT/RTMP from a hardware encoder. Listeners get a direct MP3/AAC stream for radio apps and car systems, a listening page and an embeddable mini player.
{ "server": "rly1.activeproduction.com", "port": 8000, "mount": "/acme-3f9c1a", "user": "source", "password": "…",
"leg": "pri", "video": false, "connected": true, "now_playing": "Artist - Title", "listeners": 42, "public": true,
"listen_mp3": "https://app.relaya.tv/radio/mWTxHvUanyCo.mp3", "listen_aac": "https://app.relaya.tv/radio/mWTxHvUanyCo.aac",
"page_url": "https://app.relaya.tv/radio/mWTxHvUanyCo", "embed_url": "https://app.relaya.tv/embed/radio/mWTxHvUanyCo" }
{"leg":"pri"|"bak", "video":true|false, "regen":true} — video adds a static picture so video-only destinations (YouTube, Facebook) accept the stream; regen issues a new source password.
Listener links work once the stream is public (POST /api/channels/{id}/embed {"public":true}). "Now playing" is updated by your software through the standard Icecast metadata call:
curl -u source:PASSWORD "http://rly1.activeproduction.com:8000/admin/metadata?mode=updinfo&mount=/acme-3f9c1a&song=Artist%20-%20Title"
AutoDJ & playlists
Upload music once and let the station play 24/7: playlists, jingles every N songs, crossfade, day/time schedule and automatic loudness normalization (−16 LUFS). A live DJ connecting over Icecast takes over with a short fade; AutoDJ resumes when they leave.
Upload music with the same chunked upload as videos, adding "kind":"audio" to POST /api/videos/upload/init (see Chunked upload). Files named jingle/spot become jingles; "Artist - Title" names (or ID3 tags) fill in the artist.
{"title":"…","artist":"…","jingle":true|false} · DELETE /api/tracks/{id} (rec_delete)
{ "name": "Evening", "shuffle": true, "items": [12, 15, 9] }
PATCH /api/playlists/{id} takes the same fields (items replaces the order) · DELETE /api/playlists/{id}
Returns settings plus running, live_dj, now (title, artist, position, duration), active_playlist_id and recent plays.
{ "enabled": true, "playlist_id": 3, "jingle_playlist_id": 4, "jingle_every": 3, "crossfade": 4,
"schedule": [ { "days": [1,2,3,4,5], "start": "18:00", "end": "22:00", "playlist_id": 7 } ], "tz": "Europe/Istanbul",
"live_mode": "duck", "duck_db": -18, "resume": "continue" }
live_mode: "stop" (music stops while a live DJ is on) or "duck" (music keeps playing underneath at duck_db, −40…−3 dB). resume: "continue" (the interrupted song resumes) or "next" (the next song starts from the top).
Play history for royalty reporting (JSON, or CSV with format=csv).
Visual radio
Add a picture to the station so video-only destinations (YouTube, Facebook, TV/IPTV) accept it: background image or logo, station name, clock, now playing / up next, album cover (from the file's embedded art), equalizer bars that move with the audio and a LIVE badge while a DJ is on (also when a DJ broadcasts without AutoDJ). Output is 1280×720, 25 fps, H.264 ~1.2 Mbps.
{ "video": true, "title": "Radio Active", "accent": "#F5A524", "lang": "tr"|"en",
"show_now": true, "show_next": true, "show_cover": true, "show_wave": true, "show_clock": true }
Send the image file as the request body (JPG, PNG or WEBP, up to 12 MB). Backgrounds are cropped to 16:9; a transparent PNG works best for logos. DELETE on the same URL removes it.
Returns the current on-air picture as a JPEG.
Player branding & security
Organisation-wide rules for the watch page and the embedded player. Branding (your logo, no Relaya marks, your name and link in the watch-page header) is on Pro and Team; security rules are on every plan.
{ "accent": "#F2A93B", "logo_pos": "tr", "logo_link": "https://acme.tv", "hide_brand": true, "site_name": "Acme TV", "site_url": "https://acme.tv",
"allowed_domains": ["acme.tv", "*.acme.tv"], "geo_mode": "allow"|"block"|"off", "geo_countries": ["TR", "CY"], "watch_page": true }
allowed_domains limits where the player can be embedded (enforced by browsers via CSP frame-ancestors). geo_mode applies to pages and stream URLs (country from the viewer's IP). watch_page: false turns off the app.relaya.tv watch page so content plays only embedded. Logo: POST /api/player-settings/logo with the image as the body · DELETE removes it.
Password-protect a stream or video: {"password":"…"} (min. 4 characters) · {"password":null} removes it. Viewers enter it once; it stays valid on that device for 12 hours. Changing the password signs everyone out. GET /api/access lists protected content.
Webhooks
Relaya POSTs a JSON event to your HTTPS endpoint when something happens. Respond with any 2xx within 8 seconds; otherwise we retry after 1 min, 5 min, 30 min and 2 h. Up to 10 webhooks per organisation.
| Event | When |
|---|---|
stream.started | a stream goes live |
stream.ended | a stream has been offline for 30 s (includes duration_s) |
video.ready | an uploaded / clipped / recorded video finished processing |
vod.created | Live-to-VOD created a video from a finished stream |
usage.bandwidth | viewer bandwidth reached 80% / 100% of the monthly plan quota (level, used_gb, quota_gb, month) |
POST https://your-app.com/relaya-webhook
X-Relaya-Event: stream.started
X-Relaya-Delivery: whd_9f2c…
X-Relaya-Signature: sha256=5d41… (HMAC-SHA256 of the raw body with your signing secret)
{ "id": "whd_9f2c…", "event": "stream.started", "created_at": "2026-10-09T17:45:02.118Z",
"data": { "channel": 12, "name": "Field A", "relay_id": "acme-3f9c1a", "watch_url": "https://app.relaya.tv/live/…" } }
Verify the signature (Node.js): crypto.createHmac("sha256", secret).update(rawBody).digest("hex") must equal the part after sha256=.
{ "url": "https://your-app.com/relaya-webhook", "events": ["stream.started", "stream.ended"], "description": "CMS" }
The response includes the signing secret (whsec_…). PATCH /api/webhooks/{id} (url, events, enabled, rotate_secret) · DELETE · POST /api/webhooks/{id}/test · GET /api/webhooks/{id}/deliveries · POST …/deliveries/{delivery}/retry.
Scheduled event pages
A shareable page with title, poster, countdown and "Add to calendar" (.ics). When the stream goes live the page switches to the player by itself (password and country rules still apply); afterwards it plays the recording if Live-to-VOD is on. Embed: /embed/e/{slug}.
{ "channel_id": 12, "title": "Season Final — Live", "description": "…", "starts_at": 1791575100000, "duration_min": 120 }
starts_at is a Unix timestamp in milliseconds. Returns url (https://app.relaya.tv/e/{slug}) and an embed snippet. PATCH / DELETE /api/events/{id} · poster: POST /api/events/{id}/poster with the image as the body. Public status: GET /api/public/event/{slug} → state: scheduled · starting · live · ended.
Analytics
{ "days": 7, "views": 1840, "unique": 1213, "watch_s": 412200, "watch_hours": 114.5, "live_now": 37, "live_peak": 512,
"daily": [ { "d": "2026-10-06", "views": 640, "w": 151200 } ],
"top": [ { "kind": "l", "ref_id": 12, "name": "Final — Court A", "views": 900, "uniq": 610, "w": 220000 } ],
"countries": [ { "k": "TR", "n": 1200 } ], "devices": [ { "k": "mobile", "n": 1100 } ], "referrers": [ { "k": "example.com", "n": 300 } ] }
days: 1–90. Counts views in Relaya players (watch pages and embeds).
Account & usage
Organization, plan, quota and your permissions.
Current month usage against plan limits.
Questions or need a higher rate limit? hello@relaya.tv · © 2026 Relaya — a product of Active Production, Istanbul