# Create a live stream

Start or schedule a Live Publish stream from a URL (HLS, MP4, DASH, RTMP, RTSP, SRT) or a media library video to several platforms.


```http
POST https://social-publish-api.onrender.com/v1/live
```

Creates a live stream and queues it. With `startNow: true` it starts within a few seconds; with `scheduledFor` it starts at that time. The response is the stream with `status: "scheduled"`. Follow it with [Get a live stream](/docs/api/get-live). See [Live Publish](/docs/live) for how sources and destinations work.

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `x-access-key` | Yes* | Your API key. *Or send `Authorization: Bearer <key>`. |
| `content-type` | Yes | `application/json` |

## Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | Yes | 1–100 characters. Used as the broadcast title on YouTube, Facebook and Twitch. |
| `description` | string | No | Up to 5,000 characters (YouTube and Facebook). |
| `source` | object | Yes | `{ "type": "url", "url": "…" }` or `{ "type": "media", "ref": "media://<id>" }`. |
| `destinations` | object[] | Yes | 1–10 destinations, see below. |
| `startNow` | boolean | One of | Start immediately. |
| `scheduledFor` | string | One of | ISO 8601 with offset, e.g. `2026-11-02T18:00:00+05:30`. Up to 60 days ahead. |
| `loop` | boolean | No | Restart a **file** source when it ends, until stopped or the time limit. Default `false`. Not allowed for HLS/DASH or live sources. |
| `maxDurationMinutes` | integer | No | 1–720. The stream ends automatically after this. |
| `video` | string | No | `auto` (default), `passthrough`, `1080p` or `720p`. See [Video and audio](/docs/live#video-and-audio). |

### Source URL

`http(s)://` (HLS `.m3u8`, DASH `.mpd`, MP4/MOV/WebM/MKV/FLV/TS files), `rtmp(s)://`, `rtsp(s)://` or `srt://`. The host must be public, and the URL must not contain a username or password.

### Destinations

A **connected account** (YouTube, Facebook Page or Twitch):

| Field | Type | Description |
| --- | --- | --- |
| `accountId` | string (UUID) | From [List accounts](/docs/api/list-accounts). Each account can appear once. |
| `options.privacy` | string | YouTube only: `public` (default), `unlisted` or `private`. |
| `options.madeForKids` | boolean | YouTube only. Default `false`. |

A **custom RTMP** destination (any other platform):

| Field | Type | Description |
| --- | --- | --- |
| `type` | `"custom"` | Required. |
| `preset` | string | `instagram`, `twitter`, `tiktok`, `linkedin`, `kick`, `facebook_profile`, `youtube_manual`, `twitch_manual` or `other` (default). Sets the icon and label. |
| `name` | string | Optional label, up to 60 characters. |
| `url` | string | The server URL, `rtmp://` or `rtmps://`. |
| `streamKey` | string | The stream key. It's appended to the URL with a `/`. Leave it empty if the key is already in the URL. |

## Request

```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": "Friday product demo",
    "source": { "type": "url", "url": "https://cdn.example.com/demo.mp4" },
    "video": "auto",
    "destinations": [
      { "accountId": "8a7b6c5d-4e3f-4a1b-9c8d-7e6f5a4b3c2d", "options": { "privacy": "unlisted" } },
      { "accountId": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e" },
      { "type": "custom", "preset": "kick", "url": "rtmps://fa723fc1b171.global-contribute.live-video.net:443/app/", "streamKey": "sk_us-west-2_…" }
    ],
    "scheduledFor": "2026-11-06T18:00:00+05:30"
  }'
```

```ts tab="Node.js"
const res = await fetch("https://social-publish-api.onrender.com/v1/live", {
  method: "POST",
  headers: { "x-access-key": process.env.RELAYCAST_API_KEY!, "content-type": "application/json" },
  body: JSON.stringify({
    title: "Friday product demo",
    source: { type: "url", url: "https://cdn.example.com/live/index.m3u8" },
    destinations: [{ accountId: "8a7b6c5d-4e3f-4a1b-9c8d-7e6f5a4b3c2d" }],
    startNow: true,
  }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data: stream } = await res.json();
console.log(stream.id, stream.status);
```

```python tab="Python"
import os, requests

res = requests.post(
    "https://social-publish-api.onrender.com/v1/live",
    headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
    json={
        "title": "Friday product demo",
        "source": {"type": "media", "ref": "media://5d1f0a8e-2b3c-4d5e-8f9a-0b1c2d3e4f5a"},
        "loop": True,
        "maxDurationMinutes": 60,
        "destinations": [{"accountId": "8a7b6c5d-4e3f-4a1b-9c8d-7e6f5a4b3c2d"}],
        "startNow": True,
    },
)
res.raise_for_status()
print(res.json()["data"]["id"])
```

## Response

```json title="Response 201"
{
  "data": {
    "id": "c0ffee00-1234-4abc-9def-0123456789ab",
    "title": "Friday product demo",
    "description": null,
    "status": "scheduled",
    "source": { "type": "url", "url": "https://cdn.example.com/demo.mp4", "mediaId": null, "filename": null, "kind": "file" },
    "loop": false,
    "video": "auto",
    "maxDurationMinutes": null,
    "scheduledFor": "2026-11-06T12:30:00.000Z",
    "startedAt": null,
    "endedAt": null,
    "durationSeconds": 0,
    "error": null,
    "stats": { "sourceKind": "file" },
    "destinations": [
      {
        "id": "0a1b2c3d-…",
        "kind": "youtube",
        "platform": "youtube",
        "label": "YouTube · Acme",
        "accountId": "8a7b6c5d-4e3f-4a1b-9c8d-7e6f5a4b3c2d",
        "handle": "Acme",
        "server": null,
        "status": "pending",
        "error": null,
        "watchUrl": null,
        "options": { "privacy": "unlisted" },
        "startedAt": null,
        "endedAt": null
      },
      {
        "id": "4e5f6a7b-…",
        "kind": "custom",
        "platform": "kick",
        "label": "Kick",
        "accountId": null,
        "handle": null,
        "server": "rtmps://fa723fc1b171.global-contribute.live-video.net:443/app",
        "status": "pending",
        "error": null,
        "watchUrl": null,
        "options": {},
        "startedAt": null,
        "endedAt": null
      }
    ],
    "createdAt": "2026-11-05T09:12:44.120Z"
  }
}
```

The stream key is never returned. For field meanings, see [Get a live stream](/docs/api/get-live).

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `validation_error` | Invalid body (`error.details` lists the issues), `loop` on a non-file source, a duplicate account, or `scheduledFor` in the past or more than 60 days ahead. |
| 400 | `unsafe_url` | The source or a destination URL points to a private address or contains credentials. |
| 400 | `unknown_media` | The `media://` reference isn't in this project. |
| 400 | `unknown_account` | An `accountId` isn't in this project (`details.missing`). |
| 400 | `account_inactive` | An account needs reconnecting. |
| 400 | `live_not_supported` | The account's platform has no live API. Use a custom RTMP destination instead. |
| 401 | `unauthorized` | Missing or invalid API key. |
| 402 | `live_minutes_exhausted` | The project has no live minutes left this month. |
| 429 | `rate_limited` | Rate limit exceeded for this key. |
