Encoder guidesPricingDashboard

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 URLhttps://app.relaya.tv
FormatJSON request and response bodies (Content-Type: application/json), UTF-8
LanguageError 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"

Errors & rate limits

Errors return a non-2xx status and a body like {"error":"Stream not found"}.

StatusMeaning
400Invalid input
401Missing or invalid API key
402Plan limit reached (streams, destinations, storage…) or trial ended
403Permission missing, read-only key, or endpoint not available to keys
404Not found (or not visible to this key)
429Rate limit — see Retry-After
502Relay 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.

GET/api/channels view

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 } ]
}]
POST/api/channels channel_create
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.

PATCH/api/channels/{id} channel_control

Rename: {"name":"New name"}

DELETE/api/channels/{id} channel_delete

Deletes the stream and its destinations.

Live status

GET/api/channels/{id}/status view

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)

POST/api/channels/{id}/push channel_control
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.

PATCH/api/channels/{id}/push/{targetId}

Change label / url.

DELETE/api/channels/{id}/push/{targetId}

Recording & clips

POST/api/channels/{id}/record channel_control

{"on":true} starts recording, {"on":false} stops.

GET/api/channels/{id}/recordings rec_read
[ { "name": "2026-10-06_19-00-02.mp4", "start": 1759766402000, "duration": 5412.3, "size": 3221225472 } ]
GET/api/channels/{id}/recfile?name={file} rec_read

Downloads a recording as MP4.

GET/api/channels/{id}/clip?last=30 clip

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.

GET/api/channels/{id}/dvr view
PATCH/api/channels/{id}/dvr channel_control
{ "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

POST/api/channels/{id}/embed channel_control

{"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.

GET/api/videos view
{ "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.

POST/api/videos/from-recording clip
{"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.

PATCH/api/videos/{id} channel_control

{"title":"…","description":"…","public":true}

POST/api/videos/{id}/prep-playout channel_control

Pre-convert to the playout format (1080p25 · 5 Mbps) so playout sources start instantly.

DELETE/api/videos/{id} rec_delete

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.

GET/api/ingest view

Lists sources with live state (running, preparing, retry, error, off).

POST/api/ingest/test channel_control

{"url":"rtsp://user:pass@cam.example.com:554/stream1"} → codec and resolution of the source.

POST/api/ingest channel_control
# 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}
FieldValues
modecopy (pull only, no transcoding) · 720p · 1080p
bitratekbps — 720p: 1000–8000, 1080p: 1000–15000
rcvbr (capped) or cbr (constant, for broadcast delivery)
legpri or bak (backup requires Pro/Team)
PATCH/api/ingest/{id}

Start/stop with {"enabled":true|false}, or change any field.

DELETE/api/ingest/{id}
Only one source can feed the same input of a stream at a time. Local network addresses (192.168.x.x etc.) are rejected — the camera must be reachable from the internet.

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.

GET/api/channels/{id}/radio view
{ "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" }
PATCH/api/channels/{id}/radio channel_control

{"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.

GET/api/tracks view
PATCH/api/tracks/{id} channel_control

{"title":"…","artist":"…","jingle":true|false} · DELETE /api/tracks/{id} (rec_delete)

GET/api/playlists view
POST/api/playlists channel_control
{ "name": "Evening", "shuffle": true, "items": [12, 15, 9] }

PATCH /api/playlists/{id} takes the same fields (items replaces the order) · DELETE /api/playlists/{id}

GET/api/channels/{id}/autodj view

Returns settings plus running, live_dj, now (title, artist, position, duration), active_playlist_id and recent plays.

PATCH/api/channels/{id}/autodj channel_control
{ "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).

POST/api/channels/{id}/autodj/skip channel_control
GET/api/channels/{id}/autodj/log?days=30&format=csv view

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.

GET/api/channels/{id}/radio/visual view
PATCH/api/channels/{id}/radio/visual channel_control
{ "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 }
POST/api/channels/{id}/radio/visual/image?type=bg|logo channel_control

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.

GET/api/channels/{id}/radio/visual/preview view

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.

GET/api/player-settings view
PATCH/api/player-settings channel_control
{ "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.

PUT/api/access/{live|vod}/{id} channel_control

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.

EventWhen
stream.starteda stream goes live
stream.endeda stream has been offline for 30 s (includes duration_s)
video.readyan uploaded / clipped / recorded video finished processing
vod.createdLive-to-VOD created a video from a finished stream
usage.bandwidthviewer 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=.

GET/api/webhooks view
POST/api/webhooks channel_control
{ "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}.

GET/api/events view
POST/api/events channel_control
{ "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

GET/api/analytics?days=7 view
{ "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

GET/api/me

Organization, plan, quota and your permissions.

GET/api/usage

Current month usage against plan limits.

Questions or need a higher rate limit? hello@relaya.tv · © 2026 Relaya — a product of Active Production, Istanbul