Partner API base path is /api/v1 on https://api.dcast.pro. This site documents the live surface; use the Quickstart QA curls to validate keys and routes.

Restreams API (multistream)

Create restreams to push one live stream to multiple destinations (YouTube, Twitch, Facebook, or custom RTMP). You get a single ingest URL and key; we relay to all configured destinations. Recording controls: Recording.

Create a restream

curl -s -X POST https://api.dcast.pro/api/v1/restreams \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My Multistream",
    "sourceStreamId": "optional-live-stream-id",
    "destinations": [
      { "platform": "YOUTUBE", "streamKey": "your-youtube-key" },
      { "platform": "CUSTOM", "streamKey": "key", "rtmpUrl": "rtmp://custom.example.com/live" }
    ]
  }'

title is required. For a standalone multistream ingest, send {"title":"…","destinations":[…]} and do not send streamId or sourceStreamId. Those fields are only for linking to a live stream you already created via POST /streams; a wrong or foreign id returns 400.

Response includes restream id and source stream key. Use /encoder-urls to retrieve RTMP/SRT/WHIP ingest URLs.

Canonical pagination

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  "https://api.dcast.pro/api/v1/restreams?page=1&limit=20"

# Response
{
  "success": true,
  "data": {
    "restreams": [ /* ... */ ],
    "pagination": { "page": 1, "limit": 20, "total": 7, "pages": 1 }
  }
}

Encoder URLs and start/stop

GET  https://api.dcast.pro/api/v1/restreams/{id}/encoder-urls
POST https://api.dcast.pro/api/v1/restreams/{id}/start
POST https://api.dcast.pro/api/v1/restreams/{id}/stop
Authorization: Bearer pk_YOUR_KEY_HERE

Response from /encoder-urls exposes rtmp, rtmps, srt (literal publishable streamid), srtDisplay (identical alias of srt, kept for backward compatibility), and whip. All hostnames are r.dcast.pro (the canonical restream DNS pool) — never a worker hostname.

SRT note: feed srt (or the identical srtDisplay) to ffmpeg / OBS / vMix verbatim. The streamid is emitted in its literal form (#!::r=restream/…,m=publish) because libsrt does not URL-decode it — the literal bytes go on the wire. Do not percent-encode the # / ! / : / , yourself: an encoded streamid (%23%21%3A%3A…) reaches the server as a literal stream name and silently fails to publish.

# Quick test (paste srt verbatim):
ffmpeg -re -f lavfi -i testsrc2=size=1280x720:rate=30 \
       -f lavfi -i sine=frequency=440 \
       -c:v libx264 -preset veryfast -b:v 1500k -g 60 \
       -c:a aac -b:a 96k \
       -f mpegts "srt://r.dcast.pro:10080?streamid=#!::r=restream/sk_rstrm_<…>,m=publish&mode=caller&latency=500000"

The latency token of an SRT URL is read by ffmpeg and libsrt as microseconds, not milliseconds. Paste the srt field from /encoder-urls verbatim and the conversion is never yours to make; the band in force is published under srtConfig.latency in https://api.dcast.pro/api/v1/docs/openapi.json.

Live preview player

GET /restreams/:id/preview-url returns the canonical HTTP-FLV preview URL for the restream's incoming source. The hostname is always api.dcast.pro — partners never see a worker hostname. When the player hits that URL we 302-redirect to the current preview worker (worker rebalance is transparent; flv.js / mpegts.js follow redirects natively). The same URL is what the cabinet plays in its «Preview» pane and what the cabinet WebSocket event stream:preview_ready emits.

curl -s -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  https://api.dcast.pro/api/v1/restreams/{restreamId}/preview-url

# 200 — restream is publishing and the worker-side preview is up
{
  "success": true,
  "data": {
    "httpFlvUrl": "https://api.dcast.pro/api/v1/restreams/{restreamId}/preview.flv?token=eyJ...",
    "expiresAt": "2026-05-27T17:43:56.103Z",
    "isLive": true
  }
}

# 202 — restream has not received a publish yet. Caller may poll every 5–15s.
{
  "success": true,
  "data": {
    "httpFlvUrl": null,
    "expiresAt": null,
    "isLive": false,
    "reason": "restream_not_publishing"
  }
}

# 202 — source publish received but the worker-side preview transcoder
# has not yet published the _preview mount back to SRS (transient,
# normally <2s after the first source publish). Retry after 1–2s.
{
  "success": true,
  "data": {
    "httpFlvUrl": null,
    "expiresAt": null,
    "isLive": false,
    "reason": "preview_warming_up"
  }
}

# 404 — wrong owner or restream does not exist.
{ "success": false, "error": { "code": "NOT_FOUND", "message": "Restream not found" } }

Token lifetime

The ?token=... in the URL is a short-lived signed JWT (~5 minutes, master-token TTL). Use the returned expiresAt to refresh before it expires; re-fetch on a ≤ 4-minute cadence for long-running preview sessions.

flv.js example (browser)

import mpegts from 'mpegts.js'; // or 'flv.js' — interchangeable

async function attachRestreamPreview(videoEl, restreamId, apiKey) {
  const r = await fetch(
    'https://api.dcast.pro/api/v1/restreams/' + restreamId + '/preview-url',
    { headers: { Authorization: 'Bearer ' + apiKey } }
  );
  const { data } = await r.json();
  if (!data.isLive) {
    setTimeout(() => attachRestreamPreview(videoEl, restreamId, apiKey), 10_000);
    return;
  }

  const player = mpegts.createPlayer({ type: 'flv', url: data.httpFlvUrl, isLive: true });
  player.attachMediaElement(videoEl);
  player.load();
  player.play();

  const refreshInMs = Math.max(
    60_000,
    new Date(data.expiresAt).getTime() - Date.now() - 60_000
  );
  setTimeout(() => {
    player.destroy();
    attachRestreamPreview(videoEl, restreamId, apiKey);
  }, refreshInMs);
}

Works for FULL_ACCESS, STANDARD, and READ_ONLY pk_* keys.

Destinations — masked keys on read

On every read endpoint (GET /restreams, GET /restreams/:id), each destinations[*].streamKey is returned masked as "***". Destination keys are write-only: the POST /restreams response also returns "***" (or "" when no key is set), and the PUT /restreams/:id response lists destinations without a key. The key is the one you got from the destination platform; keep your own copy.

Patch a destination (partial update)

curl -s -X PATCH https://api.dcast.pro/api/v1/restreams/{id}/destinations/{destId} \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'

# Any subset of the four fields is accepted; at least one is required.
# - enabled    (boolean)
# - displayName (string | null)
# - rtmpUrl    (string, non-empty)
# - platform   (string, normalised to uppercase: YOUTUBE/TWITCH/FACEBOOK/CUSTOM)
#
# streamKey is NOT patchable here. To change a destination's key, send the
# new key from the platform in PUT /restreams/:id (see below).

PATCH is partial: send only the fields you want to change. Empty body returns 400 BAD_REQUEST ("include at least one of: enabled, displayName, rtmpUrl, platform"). Foreign destId returns 404 NOT_FOUND.

Change a destination's key

Send the destination with its id and the new key from YouTube / Twitch / Facebook / your RTMP server in PUT /restreams/:id. A field you do not send keeps its stored value; a streamKey that is absent, null or the masked "***" that reads return is not a key and leaves the stored one unchanged (send "" only to clear it). On an ACTIVE restream the running forwarder keeps the old key until you stop and start the restream.

curl -s -X PUT https://api.dcast.pro/api/v1/restreams/{id} \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"destinations":[{"id":"{destId}","platform":"YOUTUBE","streamKey":"NEW_KEY_FROM_YOUTUBE","rtmpUrl":"rtmp://a.rtmp.youtube.com/live2"}]}'

rotate-key (store a new platform key)

curl -s -X POST https://api.dcast.pro/api/v1/restreams/{id}/destinations/{destId}/rotate-key \
  -H "Authorization: Bearer pk_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"streamKey":"NEW_KEY_FROM_YOUTUBE"}'

# Response
{
  "success": true,
  "data": {
    "id": "...",
    "restreamId": "...",
    "platform": "YOUTUBE",
    "rtmpUrl": "rtmp://a.rtmp.youtube.com/live2",
    "status": "INACTIVE",
    "streamKey": "NEW_KEY_FROM_YOUTUBE",
    "runtimeApplied": true,
    "note": "New key takes effect on the next push from your encoder."
  }
}

Requires { "streamKey": "<new platform key>" }: the key is issued by YouTube / Twitch / Facebook, never by DCAST, and is stored as sent. A request without it (absent, empty, or the masked "***", which is not a key) is refused 400 BAD_REQUEST and the stored key stays. On an ACTIVE restream runtimeApplied is false: the running forwarder keeps the old key until stop + start. The response echoes the key you sent; subsequent reads mask it back to "***".

Endpoints

MethodEndpointDescription
GET/restreamsList restreams (canonical pagination)
POST/restreamsCreate restream (destination keys masked in the response)
GET/restreams/:idRestream details (destination keys masked)
GET/restreams/:id/encoder-urlsIngest URLs (RTMP / SRT / WHIP)
GET/restreams/:id/preview-urlLive HTTP-FLV preview URL for flv.js playback (the same URL the cabinet renders)
POST/restreams/:id/startStart restream
POST/restreams/:id/stopStop restream
PUT/restreams/:idUpdate restream (a destination field you do not send keeps its stored value; response has no keys)
PATCH/restreams/:id/destinations/:destIdPartial-update a destination — any subset of enabled, displayName, rtmpUrl, platform (at least one required)
POST/restreams/:id/destinations/:destId/rotate-keyReplace the stored streamKey with the new platform key sent in the body (streamKey, required)
DELETE/restreams/:idDelete restream
POST/restreams/:id/record/startStart recording
POST/restreams/:id/record/stopStop recording
GET/restreams/:id/record/statusRecording status

Platforms: YOUTUBE, TWITCH, FACEBOOK, CUSTOM (requires rtmpUrl).

Destination validation (before /start)

POST /restreams/:id/start runs a pre-flight gate over destinations[] before touching worker state. The pre-flight enforces the following per destination, after trimming whitespace:

  • enabled must not be false (default is true; toggle via PATCH /restreams/:id/destinations/:destId).
  • Either a connected platformAccountId (OAuth-backed destination) or a non-empty streamKey and a rtmpUrl containing "://".
  • For platform="CUSTOM" there is no default RTMP host — you must supply rtmpUrl on create / update. YOUTUBE / TWITCH / FACEBOOK fall back to the standard ingest URL when rtmpUrl is omitted.
  • streamKey is whitespace-trimmed before the length check, so " " and the masked placeholder "***" (echoed back from read responses) will not pass — never round-trip the masked value into PUT /restreams/:id. A destination entry sent with its id in PUT is written whole, so include the real key each time (an omitted key is stored empty), or leave that destination out of destinations[] to keep it unchanged.

If at least one destination passes the gate the restream starts; the others are still attempted by the worker but failures stay scoped to that destination's status. If no destination passes, /start returns 422 PRECONDITION_FAILED (see below).

Error envelope codes

Lifecycle endpoints (/start, /stop) return error.code that mirrors the HTTP status semantics, so partner clients can branch on the code alone without parsing HTTP. Previously every lifecycle failure collapsed to BAD_REQUEST while HTTP varied — that has been corrected.

HTTPerror.codeMeaning & example trigger
400BAD_REQUESTMalformed input the caller can fix (e.g. /start with sourceStreamId set but no source signal received yet).
404NOT_FOUNDRestream not found, or not owned by the caller.
409CONFLICTConcurrent /start in flight (Redis NX lock loss), or restream is already ACTIVE.
422PRECONDITION_FAILEDPre-flight rejected: no valid destinations after applying the rules above.
502UPSTREAM_ERROROAuth-backed destination provisioning failed at the third-party platform.
503SERVICE_UNAVAILABLENo fleet worker available for the restream right now — safe to retry.
5xxINTERNAL_ERRORUnhandled server error; retry with backoff and report if persistent.

Security & scope

Restream endpoints are creator-scoped. Foreign restream ids return 404 NOT_FOUND. pk_* keys cannot list other creators' restreams, cannot bind destinations to streams they do not own, and cannot read another account's un-masked destination keys.

Restreams API (multistream) — dcast.pro API docs