# Live Publish

Restream one live or recorded source (HLS, MP4, DASH, RTMP, RTSP, SRT or a media library video) to YouTube, Facebook, Twitch and any RTMP destination at the same time.


## Overview

Live Publish takes **one source** and sends it to **up to 10 destinations at once**. You can go live now or schedule a stream up to 60 days ahead. You can run it from the dashboard (**[Live](https://social-publish-kappa.vercel.app/dashboard/live)**) or with `POST /v1/live`.

- **Sources:** a public URL (HLS `.m3u8`, MPEG-DASH `.mpd`, MP4/MOV/WebM/MKV/FLV/TS files, RTMP/RTMPS, RTSP/RTSPS, SRT) or a video from your [media library](/docs/media).
- **Connected destinations:** YouTube, Facebook Pages and Twitch. Relaycast creates the broadcast through each platform's API, sets the title, gets the ingest URL and ends the broadcast when you stop.
- **Custom RTMP destinations:** every other live platform (Instagram, X, TikTok, LinkedIn, Kick, Facebook profiles and groups, or any RTMP/RTMPS server). You create the live in the platform's own tool and paste its server URL and stream key.

Each destination is independent. If one platform drops or rejects the stream, the others keep going.

## Destinations

| Destination | How | What you need |
| --- | --- | --- |
| YouTube | Connected account (API) | A channel with live streaming enabled (YouTube Studio → Go live, first-time activation can take 24 h). You can set privacy and made-for-kids. |
| Facebook Page | Connected account (API) | The `publish_video` permission. Self-hosted: set `FACEBOOK_ENABLE_LIVE=true`, then reconnect the Page (needs Meta App Review for other users' Pages). |
| Twitch | Connected account (API) | Connect Twitch in **Integrations**. The title is set on the channel and the stream key is read automatically. |
| Instagram Live | Custom RTMP (`preset: "instagram"`) | Instagram on the web → Create → Live video → streaming software. Keys are **single-use**. |
| X (Twitter) Live | Custom RTMP (`preset: "twitter"`) | X Media Studio → Producer → an RTMP source. Needs live access on the account. |
| TikTok LIVE | Custom RTMP (`preset: "tiktok"`) | TikTok LIVE Center / LIVE Studio server URL and key. Needs LIVE access on the account. |
| LinkedIn Live | Custom RTMP (`preset: "linkedin"`) | A LinkedIn Live event with a custom stream. Needs LinkedIn Live access. |
| Kick | Custom RTMP (`preset: "kick"`) | Kick dashboard → Settings → Stream URL & Key. |
| Facebook profile / group | Custom RTMP (`preset: "facebook_profile"`) | Facebook → Live video → Streaming software. |
| YouTube / Twitch by key | Custom RTMP (`youtube_manual`, `twitch_manual`) | A stream key from YouTube Studio or the Twitch dashboard, without connecting the account. |
| Anything else | Custom RTMP (`preset: "other"`) | Any `rtmp://` or `rtmps://` server URL and stream key. |

> [!NOTE]
> Instagram, X, TikTok, LinkedIn and Kick have no public API for starting a live stream from a third-party app, so they use stream keys. Connected accounts for those platforms can't be added as live destinations. Doing so returns `live_not_supported`.

Stream keys are encrypted at rest. They never appear in API responses or logs. The response shows only the server address (`server`).

## Sources

| Source | Example | Notes |
| --- | --- | --- |
| HLS | `https://cdn.example.com/live/index.m3u8` | Live or VOD. For a master playlist, the highest quality is used. Live playlists start near the live edge. |
| MPEG-DASH | `https://cdn.example.com/stream/manifest.mpd` | Live (`type="dynamic"`) or VOD. |
| Video file | `https://cdn.example.com/show.mp4` | MP4, MOV, WebM, MKV, FLV, TS… Played at real-time speed. Can loop. The server should support byte ranges, otherwise MP4/MOV must be "fast start". |
| RTMP / RTMPS | `rtmp://ingest.example.com/live/abc` | Pulls from an RTMP server that is already live. |
| RTSP / RTSPS | `rtsp://camera.example.com:554/stream1` | IP cameras, read over TCP. |
| SRT | `srt://srt.example.com:9000?streamid=abc` | Caller mode. |
| Media library | `{"type": "media", "ref": "media://<id>"}` | Any video you uploaded. Can loop. |

Source URLs must point to **public hosts**. Private, loopback and link-local addresses are rejected, and so are URLs with a username or password in them. The dashboard's **Check source** button looks at the URL before you go live. It reports the format, live or VOD, the qualities and codecs found, and whether the server supports byte ranges.

The source must be reachable when the stream starts. Live sources (HLS/DASH live, RTMP, RTSP, SRT) are reconnected automatically up to 5 times if they drop.

## Video and audio

| `video` | Behaviour |
| --- | --- |
| `auto` (default) | H.264 up to 1080p is **passed through** untouched (lowest latency, no quality loss, almost no CPU). Anything else (HEVC, VP9, AV1, above 1080p) is re-encoded to 720p H.264. |
| `passthrough` | Always passes H.264 through, at any resolution. Codecs RTMP can't carry are still re-encoded to 720p. |
| `1080p` / `720p` | Always re-encodes to H.264 at that height (never upscaled), 30-fps-friendly bitrates (6 Mbps / 3.5 Mbps), keyframe every 2 s. |

Audio is copied if it is AAC and the video is passed through. Otherwise it is encoded to AAC 160 kbps / 48 kHz. Sources **without audio** get a silent track, because YouTube, Facebook and Twitch require one.

The stream's `stats` report what was found and what was done: `videoCodec`, `audioCodec`, `width`, `height`, `fps`, `encoding` (`copy` or `720p`/`1080p`), `bitrateKbps`, `speed` and `restarts`.

## Lifecycle

| Stream `status` | Meaning |
| --- | --- |
| `scheduled` | Waiting for `scheduledFor`. Stopping it cancels it. |
| `starting` | Probing the source and creating broadcasts on each platform. |
| `live` | Media is flowing to at least one destination. |
| `ending` | A stop was requested; broadcasts are being closed. |
| `ended` | Finished normally: the file ended, you stopped it, or it reached its time limit. |
| `failed` | Nothing could be streamed (for example an unreachable source, or every destination failed). `error` says why. |
| `canceled` | A scheduled stream was stopped before it started. |

Each destination has its own `status` (`pending`, `connecting`, `live`, `ended`, `failed`), `error` and `watchUrl` (YouTube, Facebook and Twitch). A stream with one failed destination keeps going on the others.

A stream ends when:

- the source ends (files without `loop`, VOD playlists, a live source that stops and doesn't come back),
- you call [Stop](/docs/api/stop-live) (it takes effect within about 5 seconds),
- it reaches `maxDurationMinutes` (at most 720), or
- the project runs out of live minutes.

On YouTube the broadcast is moved to *complete*, and on Facebook the live video is ended. Twitch goes offline when the stream stops.

You get an in-app notification when a stream goes live, ends, or fails.

## Limits

| Limit | Default | Self-hosted setting |
| --- | --- | --- |
| Live minutes per project per month | 120 | `LIVE_MINUTES_PER_MONTH` |
| Concurrent streams per project | 2 | `LIVE_MAX_CONCURRENT_PER_PROJECT` |
| Destinations per stream | 10 | — |
| Longest stream | 12 hours | — |
| Schedule ahead | 60 days | — |

Minutes are counted per stream, not per destination: one hour sent to 5 platforms uses 60 minutes. Check usage with [`GET /v1/live/usage`](/docs/api/live-usage).

## Example

```bash tab="cURL"
curl -X POST https://social-publish-api.onrender.com/v1/live \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "title": "Product launch",
    "description": "Live from the studio",
    "source": { "type": "url", "url": "https://cdn.example.com/launch/index.m3u8" },
    "destinations": [
      { "accountId": "8a7b6c5d-4e3f-4a1b-9c8d-7e6f5a4b3c2d", "options": { "privacy": "public" } },
      { "accountId": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e" },
      { "type": "custom", "preset": "instagram", "url": "rtmps://edgetee-upload-xx.fbcdn.net:443/rtmp/", "streamKey": "1789…?s_bl=1&s_ow=10" }
    ],
    "startNow": true
  }'
```

See the [API reference](/docs/api/create-live) for every field.

## Self-hosting notes

- The worker runs **ffmpeg** (bundled through `ffmpeg-static`; set `FFMPEG_PATH` to use your own build).
- Each running stream is one ffmpeg process. Passthrough uses very little CPU. Re-encoding to 720p needs about 1 vCPU per stream, and 1080p needs about 2. Small free instances (such as Render's free 0.1 CPU) can only do passthrough reliably.
- `LIVE_CONCURRENCY` (default 2) is how many streams one worker runs at a time. Add workers to run more.
- Twitch needs `TWITCH_CLIENT_ID` and `TWITCH_CLIENT_SECRET`. See [Twitch](/docs/platforms/twitch).
