This is the full developer documentation for Cardinal # Cardinal documentation > How to go live with Cardinal, put the player on your site, and build on the API. Cardinal takes **one live stream** from your encoder, encodes it **three times in three separate data centers**, and delivers it to every viewer in 1080p or 4K. Captions, recordings and a player come with every broadcast. ## Start here [Section titled “Start here”](#start-here) * [Your first broadcast](/docs/first-broadcast) — from nothing to live in about ten minutes. * [Choosing an encoder](/docs/encoders) — what to send from, and the settings that matter. * [Pricing and billing](/docs/billing) — what a broadcast costs and how the bill works. ## Put it in front of viewers [Section titled “Put it in front of viewers”](#put-it-in-front-of-viewers) * [Embedding the player](/docs/player/embed) on your own website. * [Your own brand](/docs/player/white-label) on the player and the watch page. * [Giving and tips](/docs/player/giving) — a button beside the stream that opens your own giving page. * [Private streams](/docs/player/private) behind an access code. * [Captions](/docs/captions). ## Build on it [Section titled “Build on it”](#build-on-it) Everything in Cardinal is an [API](/docs/api) first. Create a stream per customer, read usage per customer, and pull recordings with plain HTTPS calls. These docs are also available to AI assistants as [llms.txt](/llms.txt). # API introduction > The Cardinal API's base URL, authentication, errors, paging, idempotency, and the formats AI tools read. Everything in Cardinal is available over a REST API: JSON over HTTPS. The [API reference](/docs/api/reference/streams) describes every endpoint, with examples in cURL, Python, JavaScript, TypeScript, PHP, Ruby, Go, Java and C#. ```plaintext Base URL: https://api.cardinalstreaming.com ``` ## Authentication [Section titled “Authentication”](#authentication) Every request carries an API key as a bearer token: ```plaintext Authorization: Bearer strm_... ``` A key identifies **one organization**, which is what a stream is billed to. Create and revoke keys in the console. Keep keys on your servers — never in a browser or an app. The examples read the key from the `CARDINAL_API_KEY` environment variable. A missing, unknown or revoked key is `401 unauthenticated`, and so is a key whose organization is suspended; the answer is the same in every case, on purpose. Billing routes are the exception: they accept a key suspended for non-payment, so the bill can always be paid. Another organization’s stream, customer or recording is `404 not_found`, never `403`. ## Your customers [Section titled “Your customers”](#your-customers) If you stream for your own customers — you are a **partner** — put **your** identifier for each one in `customer_ref` when you book a stream. Booking registers the customer; the [customers](/docs/api/reference/customers) endpoints name, list and archive them, [usage](/docs/api/reference/usage) breaks down by customer so you can bill them, and [recordings](/docs/api/reference/recordings) lists each customer’s recordings with download links. ## Errors [Section titled “Errors”](#errors) Every error has one shape: ```json { "error": "invalid_tier", "detail": "tier must be standard_1080p or pro_4k" } ``` Branch on `error`, which is stable; show or log `detail`, which is written for a person and may change. Each endpoint’s reference lists the codes it can return. See [Errors and limits](/docs/api/errors) for the codes every endpoint shares. ## Paging [Section titled “Paging”](#paging) Lists come a page at a time, newest first. Ask for up to 200 with `limit` (default 50) and pass the page’s cursor back to get the next one: `next_cursor` as `cursor` for recordings and customers, `next_before` as `before` for streams. The last page has no cursor. ## Retrying safely [Section titled “Retrying safely”](#retrying-safely) Send an `Idempotency-Key` header when booking a stream. A retry with the same key and body returns the stream the first request booked instead of booking a second; the same key with a different body is `422 idempotency_key_reused`. A key is up to 255 printable ASCII characters with no spaces; a UUID is ideal. Starting, ending and deleting are safe to repeat without one. ## For AI assistants and tools [Section titled “For AI assistants and tools”](#for-ai-assistants-and-tools) The API is described by an [OpenAPI 3 document](/openapi.json) ([YAML](/openapi.yaml)) — the same description the API’s own tests check every response against, so it is exact. Use it to generate a client or to give an AI coding assistant the whole API at once. Every page of these docs is also available as Markdown at its own address plus `.md` — for example [/docs/api/reference/recordings.md](/docs/api/reference/recordings.md) — and the whole documentation is in [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt). # Errors and limits > The error format, the errors you will meet most, and the limits to design around. Every error is JSON: ```json { "error": "invalid_tier", "detail": "tier must be standard_1080p or pro_4k" } ``` | Status | `error` | Meaning | | ------ | -------------------------------------------- | ----------------------------------------------------------------- | | 400 | `invalid_body`, `missing_field`, `invalid_*` | The request is malformed; `detail` names the field | | 401 | `unauthenticated` | Missing, unknown or revoked key, or a suspended organization | | 402 | `cap_exceeded` | Your organization’s monthly cap is spent (only if one was set) | | 402 | `payment_failed` | Paying an invoice: the card was declined | | 404 | `not_found` | No such stream **for this key** | | 409 | `stream_ended` | The stream is over and cannot be started again | | 409 | `not_published_yet` | Live, but no video has arrived yet | | 410 | `recording_expired` | The recording is past its 30 days | | 422 | `idempotency_key_reused` | That `Idempotency-Key` was used for a different request | | 429 | `too_many_attempts` | Too many wrong access codes; see `Retry-After` | | 429 | (no body) | More than 500 requests in a minute from one IP address; slow down | | 5xx | `internal`, `unavailable` | Ours; safe to retry | `start` and `end` are idempotent; every `5xx` is safe to retry. ## Limits [Section titled “Limits”](#limits) * **A stream runs for at most 12 hours.** Book a new one for anything longer. * **No webhooks.** Poll `GET /v1/streams/{playback_id}` for state changes. * **Five restream destinations per stream**, RTMPS only. * **No limit on streams on air at once**; each is billed on its own. Send an `Idempotency-Key` when booking so a retry is never a second stream. * **List pages** return at most 200 streams, with a cursor for the rest. * **500 requests a minute from one IP address.** Past that the API answers `429`, with no body, until the rate falls back under the limit. A server that makes every call from one address should spread a large backfill out and back off on a `429` rather than retry at once. # Pricing and billing > What a broadcast costs, how minutes are counted, and how the monthly bill is collected. ## Rates [Section titled “Rates”](#rates) | | 1080p | 4K | | ------------------------- | ----------------------- | ----------------------- | | Streaming | $5 per stream-hour | $20 per stream-hour | | Live captions | included | included | | Restreaming | $1 per destination-hour | $3 per destination-hour | | Recordings (kept 30 days) | included | included | | Viewers | no charge, any number | no charge, any number | There is no monthly fee. ## How minutes are counted [Section titled “How minutes are counted”](#how-minutes-are-counted) * A stream is billed **per minute**, from when it starts to when it ends, **rounded up**, with a **15-minute minimum** for each stream that starts. * A stream that fails, or a booking that never starts, costs nothing. * Restreaming is billed per minute while each destination is live, rounded up once per destination per stream, with no minimum. * The rate is fixed when a stream starts. Rates change only from the first of a month. ## The monthly bill [Section titled “The monthly bill”](#the-monthly-bill) On the **1st of each month** you are billed for the month just ended, on the card on file: 1. Any **account credit** pays first. Credit never leaves the card a charge under $0.50; that much stays on your balance. 2. The rest is charged to your card. A month owing less than $0.50 is carried into the next month’s bill instead. If a charge is declined, we email your billing address right away and try again **1, 3, 5 and 7 days later** — seven days in all. Your streams keep working during that week. A backup card, if you add one, is tried when the main card is declined. A card that has expired or is no longer valid isn’t charged again; add a new one and pay from the **Billing** page. If the bill is still unpaid after the last try, **streaming pauses**: no stream can be booked or started until the bill is paid. A broadcast that is already live is never cut off, and everything else — your recordings, keys, reports and billing — keeps working. Pay the bill from the **Billing** page (with the card on file or a new one) and streaming resumes at once. Every stream and what it cost is visible as you go, in the console and through the [usage API](/docs/api/reference/usage). # Captions > Live captions on every broadcast, in the language being spoken, at no extra cost. ## Captions on every broadcast [Section titled “Captions on every broadcast”](#captions-on-every-broadcast) Every broadcast offers **live captions** in the language being spoken, at no extra cost. Each viewer turns them on or off in the player. You can tell Cardinal which language is spoken, or let it detect it. Captions are timed to the video: they appear with the words, not before them. ## Privacy [Section titled “Privacy”](#privacy) Captions are made from a broadcast’s audio while it is live. Broadcasts are never used to train models. # Choosing an encoder > What to stream from, the encrypted connection every encoder uses, and the settings every encoder needs. Any encoder that sends **RTMPS** works with Cardinal. RTMPS is RTMP inside an encrypted (TLS) connection, the same way YouTube, Facebook and Twitch take a stream. Plain RTMP is not accepted: it would send your stream key across the internet in clear text. Encoders that support SRT can send encrypted SRT instead, with a passphrase of the stream’s own. See [Encrypted ingest](/docs/encoders/rtmps). ## Settings every encoder needs [Section titled “Settings every encoder needs”](#settings-every-encoder-needs) | | 1080p | 4K | | ----------------- | ---------------------------- | ------------------------ | | Resolution | 1920 × 1080 | 3840 × 2160 | | Frame rate | 30 | 30 | | Video | H.264, about 6,000 Kbps | H.264, about 16,000 Kbps | | Upload you need | 10 Mbps or more | 25 Mbps or more | | Keyframe interval | 2 seconds | 2 seconds | | Audio | AAC, 48 kHz stereo, 160 Kbps | the same | **The 2-second keyframe interval matters.** Video is cut into segments at keyframes; a longer interval makes them uneven and lengthens the gap after a reconnect. **Set your encoder to reconnect by itself**, as quickly as it allows. In OBS: Settings → Advanced → Automatically Reconnect, Retry Delay 1. ## Encoders that work well [Section titled “Encoders that work well”](#encoders-that-work-well) | Encoder | RTMPS | Notes | | ------------------------------------ | ------------------------ | ----------------------------------------------- | | OBS Studio (Windows, Mac, Linux) | version 22 and later | Free. [Step-by-step guide](/docs/encoders/obs). | | vMix | version 21 and later | | | Wirecast | yes | | | Blackmagic ATEM Mini Pro and Extreme | firmware 8.6.1 and later | Update the firmware first. | | Magewell Ultra Encode | yes | | Most hardware encoders made in the last few years support RTMPS. Look for **RTMPS** or a **secure** option in the encoder’s streaming settings. An encoder that offers only plain RTMP can’t connect; streaming from a computer with OBS is the simplest way around it. # OBS Studio > Set up OBS Studio to stream to Cardinal over an encrypted RTMPS connection. OBS Studio is free, runs on Windows, Mac and Linux, and streams to Cardinal over RTMPS, an encrypted connection. Use version 22 or later. ## 1. Install [Section titled “1. Install”](#1-install) Download OBS Studio from obsproject.com and run the auto-configuration wizard if it offers one — then change the settings below. ## 2. Point OBS at your stream [Section titled “2. Point OBS at your stream”](#2-point-obs-at-your-stream) Open **Settings → Stream** and choose **Service: Custom…** Paste the stream’s **server** — it starts `rtmps://` — into **Server**, and its **stream key** into **Stream Key**. They are two different values; the stream key on its own in the Server field does not work. ## 3. Set the quality [Section titled “3. Set the quality”](#3-set-the-quality) In **Settings → Output**, choose **Output Mode: Advanced**, then on the **Streaming** tab: | | 1080p | 4K | | ----------------- | ------------------------------------------------------------- | ---------- | | Video Encoder | your GPU’s H.264 encoder (NVENC, QuickSync, Apple VT) or x264 | the same | | Rate Control | CBR | CBR | | Bitrate | 6000 Kbps | 16000 Kbps | | Keyframe Interval | 2 s | 2 s | In **Settings → Video**, set the output resolution to 1920×1080 (or 3840×2160 for 4K) and **30 FPS**. In **Settings → Audio**, 48 kHz. In **Settings → Advanced**, turn on **Automatically Reconnect** with a Retry Delay of 1, and **Stream Delay** at 2 seconds with **Preserve cutoff point when reconnecting**: OBS then re-sends what a dropped connection lost. ## 4. Go live [Section titled “4. Go live”](#4-go-live) Add your camera and audio as sources, press **Start Streaming**, then start the stream in the console. The status turns **live** within a few seconds. ## If something goes wrong [Section titled “If something goes wrong”](#if-something-goes-wrong) * **OBS can’t connect:** check the server starts `rtmps://`, that you copied the whole stream key, and that it’s the current one — a new stream key replaces the old one at once. * **Dropped frames:** your upload can’t keep up. Lower the bitrate. * **Live, but viewers see nothing:** make sure the stream itself was started, not only OBS. # Encrypted ingest (RTMPS and SRT) > Every stream reaches Cardinal encrypted, over RTMPS or over SRT with a passphrase of its own, so the video and its credentials never cross the internet in clear text. Your encoder sends each broadcast to Cardinal over **RTMPS**: RTMP inside a TLS connection, the same encryption a web browser uses. The video and the stream key are encrypted from your encoder to our ingest servers. Plain RTMP is not accepted, because it would send the stream key in clear text, where anyone on the same network could copy it and broadcast as you. ## What to enter [Section titled “What to enter”](#what-to-enter) The address goes in **two fields**, exactly as the stream shows them: | Field | Example | | ---------- | ----------------------------------------------- | | Server | `rtmps://ingest.cardinalstreaming.com:443/live` | | Stream key | `1846bdac…?key=strmi_…` | The stream key is the stream’s id with its ingest key attached. The ingest key on its own is refused. Treat the stream key like a password; if it leaks, make a new one from the stream’s page and the old one stops working at once. ## If your encoder can’t connect [Section titled “If your encoder can’t connect”](#if-your-encoder-cant-connect) * **Use the `rtmps://` server**, including `:443`. An encoder pointed at an `rtmp://` address can’t connect. * **Update the encoder’s firmware or software.** RTMPS needs TLS 1.2 or later, which encoders added years ago (OBS in version 22, vMix in version 21, the ATEM Mini Pro and Extreme in firmware 8.6.1). * **Check the network allows port 443.** It is the port the web uses, so a network that allows web browsing almost always allows it. ## SRT, encrypted [Section titled “SRT, encrypted”](#srt-encrypted) Encoders that support SRT can use it instead of RTMPS. SRT copes better with a shaky internet connection and recovers faster if one of our servers fails. With Cardinal it is **encrypted only**: each stream has its own passphrase, and a connection without it – or with the wrong one – is refused. The stream shows, beside its stream key: | Field | Example | | ---------- | -------------------------------------------------------------------------------------------------- | | SRT URL | `srt://ingest.cardinalstreaming.com:8890?streamid=publish:live/1846bdac…&passphrase=…&pbkeylen=32` | | SRT server | `srt://ingest.cardinalstreaming.com:8890` | | Stream ID | `publish:live/1846bdac…` | | Passphrase | 32 letters and numbers | * **One address (OBS):** paste the SRT URL into **Server** and leave **Stream Key** empty. It carries the Stream ID, the passphrase and AES-256 encryption. * **Separate fields (hardware encoders):** host `ingest.cardinalstreaming.com`, port `8890`, mode **Caller**; the Stream ID; and the passphrase, with encryption set to **AES-256** (AES-128 works too). The Stream ID names the stream and carries no key: it is sent before encryption starts, so nothing secret goes in it. The passphrase is the credential. Like the stream key it is shown once; making a new stream key gives a new passphrase too, and an encoder still using the old one is disconnected. If an SRT encoder can’t connect: * **Check encryption is on** and the passphrase is pasted whole. * **Check the Stream ID** is exactly as shown, with nothing added after it. * **Check the network allows outgoing UDP to port 8890.** SRT uses UDP; if the network blocks it, use RTMPS on port 443 instead. # Your first broadcast > Create a stream, point your encoder at it, go live and share it. ## 1. Create a stream [Section titled “1. Create a stream”](#1-create-a-stream) In the console, choose **New stream**, give it a title and pick a quality: **1080p** for almost everything, **4K** when your camera and connection can carry it. Through the API it is one call — see [Book a stream](/docs/api/reference/streams#createStream). A stream can start now or at a time you book. **Billing starts at the booked time whether or not your encoder is connected**, so book the time you will actually be live. If no video arrives for 15 minutes, the stream is ended for you and billed those 15 minutes. ## 2. Point your encoder at it [Section titled “2. Point your encoder at it”](#2-point-your-encoder-at-it) The stream shows a **server** and a **stream key**, for an encrypted (RTMPS) connection. Copy both into your encoder — [OBS Studio](/docs/encoders/obs) step by step, or [any other encoder](/docs/encoders). The stream key is a password for your broadcast: don’t share it. Your encoder can connect up to **an hour before** a booked start, to check the picture. Nothing is billed or shown to viewers until the start. ## 3. Go live [Section titled “3. Go live”](#3-go-live) Start streaming in your encoder, then start the stream in the console (or let a booked stream start itself). It goes **live** — encoded three times and playing for viewers — once its video arrives: * **A booked stream** gets ready in the five minutes before its start time, so it is live within seconds of the start. * **A stream started on the spot** takes up to about a minute after its video arrives, while it gets ready. ## 4. Share it [Section titled “4. Share it”](#4-share-it) Embed the [player](/docs/player/embed) on your website, or share the stream’s watch page. Viewers can turn on captions and chat while they watch. ## 5. End it [Section titled “5. End it”](#5-end-it) End the stream in the console. Billing stops at that moment, rounded up to the minute. The recording is ready to download a few minutes later and kept for 30 days. # Embedding the player > Every stream has a watch page and an embeddable player on cardinal.stream — share the link, or put the player on your own website with one line of HTML. Every stream has two addresses on **cardinal.stream**, ready the moment it is booked — no player code of your own needed: | | Address | | ---------- | --------------------------------------------- | | Watch page | `https://cardinal.stream/watch/{playback_id}` | | Embed | `https://cardinal.stream/embed/{playback_id}` | The console shows both, with copy buttons, on each stream’s page under **Share**. ## Put it on your website [Section titled “Put it on your website”](#put-it-on-your-website) ```html ``` To make it fill the width of your page and keep its shape, give the frame a style of your own — for example `width: 100%; aspect-ratio: 16 / 9; height: auto; border: 0`. ## What viewers get [Section titled “What viewers get”](#what-viewers-get) * **Before the broadcast**, the page shows when it starts, with a countdown, and switches to the video by itself when it goes live — nobody needs to reload. * **During**, the broadcast plays about a minute behind the camera. That minute lets viewers watch straight through a brief drop in your encoder’s connection without the video stopping, and gives captions time to keep up. Viewers can pause, go back to live, pick a quality, go full screen or picture-in-picture. * **Captions** in the language being spoken are offered on every broadcast; each viewer turns them on with **CC**. They are never on by default. See [Captions](/docs/captions). * **Chat and reactions** appear beside the video while the broadcast is live, when you have them on. Viewers choose a name to chat with. * **Afterwards**, the recording plays on the same page for 30 days. If a browser refuses to start the video with sound, the player starts it muted and shows **Tap to unmute**. ## With your own player [Section titled “With your own player”](#with-your-own-player) The watch page and embed are built on the same API you can use yourself: see [Playing the stream](/docs/api) in the API reference. # Giving and tips > A button beside the stream that opens your own giving or tips page — or no button at all. The player can show a **giving button** that opens **your own** giving or tips page — whichever giving service you already use. Cardinal never handles the gift; the button simply takes the viewer to your page, in a new tab, so the stream keeps playing. ## Turn it on [Section titled “Turn it on”](#turn-it-on) In the console, under **Account → Player**, enter your giving page’s address (it must start with `https://`) and, if you like, the button’s text — for example **Give**, **Tithe** or **Support us** (up to 30 characters). With the API, set `giving_url` and `giving_label` with `PUT /v1/player`. Leave the address blank and there is **no giving button** — the player works exactly the same without one. ## Partners [Section titled “Partners”](#partners) Each of your customers can send viewers to its own giving page, use yours, or show no giving button at all (for a customer that collects gifts some other way): choose **Player** beside the customer in the console, or set `giving_url` / `giving_on` with `PUT /v1/customers/{customer_ref}/player`. # Private streams > Keep a broadcast to the people who have its access code. A **private** stream plays only for viewers who enter its **access code**. Choose **Private** when you book or start a stream; the code is shown to you once, then — give it to the people you want watching. On the watch page and embed, a private stream shows its title and an access code box, and nothing else: not whether it is live, not when it started, not whether a recording exists. Once a viewer enters the right code, the page plays the stream as usual and remembers the code for that browser tab. After several wrong codes from the same place, the page asks the viewer to wait before trying again, so a code cannot be guessed. If a code has been shared too widely, make a new one from the stream’s page in the console; the old one stops working at once. # Your own brand > The watch page and embed carry your name, logo and colors — and, for partners, each customer's own. The watch page and embed show **your** name and logo, in your color. Set them in the console under **Account → Player** (owners only), or with the API’s `PUT /v1/player`. | Setting | What viewers see | | ----------------------- | ------------------------------------------------------------------------ | | Name | Above the video. Unset: your organization’s name. | | Logo | Beside the name. Any `https://` image; it is shown about 36 pixels tall. | | Accent color | Buttons and highlights. Unset: Cardinal red. | | Page | Dark (the default) or light. | | “Streaming by Cardinal” | A small line under the player. You can hide it. | ## Partners: each customer’s own brand [Section titled “Partners: each customer’s own brand”](#partners-each-customers-own-brand) If you resell Cardinal, each of your customers can have its own player: its own name, logo, color and giving button. In the console, open **Customers** and choose **Player** beside a customer; with the API, `PUT /v1/customers/{customer_ref}/player`. Anything you leave on **Same as the default** uses your own settings, so you can set your brand once and change only what differs — usually the name, the logo and the giving page. # Recordings > Every broadcast is recorded automatically as one MP4, kept for 30 days, and downloadable through the API with links you can hand to other systems. Every broadcast is recorded. A few minutes after it ends — roughly a minute per hour of broadcast, longer for 4K — the recording is ready as **one MP4 file** at the top quality, with its audio. Recordings are included in the stream-hour price. ## Getting a recording [Section titled “Getting a recording”](#getting-a-recording) Ask the API for a download link: * **One broadcast:** `GET /v1/streams/{playback_id}/recording` answers with the recording’s state and, once it is `ready`, a `download_url`. See [Recordings in the API reference](/docs/api/reference/recordings). * **Many at once:** `GET /v1/recordings` lists every recording on your account, the most recently ended first, each ready one with its own `download_url`. Add `customer_ref=` to list one customer’s, or call `GET /v1/customers/{customer_ref}/recordings`. Results come in pages; pass the `next_cursor` you were given to get the next one. A download link is an ordinary HTTPS address. Anything that can fetch a URL can use it: a browser, `curl`, a video platform’s “import from URL”, a storage transfer job. Downloads are fast and carry no per-gigabyte charge. ## How long a link lasts [Section titled “How long a link lasts”](#how-long-a-link-lasts) A link works for **one hour** by default. Ask for a longer one with `link_ttl`, in seconds, from 60 up to 604,800 (seven days): ```bash curl -H "Authorization: Bearer $CARDINAL_API_KEY" \ "https://api.cardinalstreaming.com/v1/recordings?customer_ref=grace-church&link_ttl=86400" ``` A link never outlives the recording: if the file is due to be deleted sooner, the link expires then. Anyone holding a link can download the file until it expires, so give a long-lived link only to systems you trust, and ask again for a fresh link whenever you need one. ## Partners and their customers [Section titled “Partners and their customers”](#partners-and-their-customers) If you resell Cardinal, tag each stream with your own `customer_ref` when you book it, and you can list or download any of your customers’ recordings, for example to help one of them with support. A recording is only ever visible to the account whose stream made it: no account can list, download or delete another account’s recordings, and asking for one answers `404`, the same as a recording that does not exist. ## Keeping and deleting [Section titled “Keeping and deleting”](#keeping-and-deleting) Recordings are kept for **30 days** after they are ready and then deleted, along with the live segments they were made from. Deletion is permanent: we do not archive recordings, so download any you want to keep. Delete one sooner with `DELETE /v1/streams/{playback_id}/recording` once the stream has ended. The deletion runs in the background: the answer says `deleting`, then `deleted` once every file is gone, and calling it again is safe. It cannot be undone, and download links already handed out stop working once the files are gone. When an account is closed, its API keys stop working, so download recordings before closing it. # Restreaming > Send a broadcast on to YouTube, Twitch, Facebook and other RTMPS services at the same time, from one encoder. While a stream plays on your own site, Cardinal can send it on to up to **five other services** at once — YouTube, Twitch, Facebook, or anything that takes RTMPS. Your encoder sends one stream to Cardinal; we copy it to each service. ## How it works [Section titled “How it works”](#how-it-works) * **The video goes out exactly as your encoder sends it.** We copy it to each destination without re-encoding, so it reaches them at the quality you send, with nothing added. That also means your encoder settings must suit every service you send to — see [Encoder settings](#encoder-settings-that-suit-every-service). * **Destinations follow the broadcast.** Add them before you go live or while you are live. Sending starts when the stream goes live and stops when it ends. If a service drops the connection, we reconnect on our own. * **Destinations are RTMPS only.** RTMPS encrypts the stream, and your stream key for that service, on the way there. A plain `rtmp://` address is refused (`400 invalid_url`): it would send the key unencrypted. Every major service lists an RTMPS address in its live settings. ## Adding a destination [Section titled “Adding a destination”](#adding-a-destination) Each destination needs the service’s **server URL** and your **stream key** from that service: ```bash curl -X POST "https://api.cardinalstreaming.com/v1/streams/$PLAYBACK_ID/destinations" \ -H "Authorization: Bearer $CARDINAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "YouTube", "url": "rtmps://a.rtmps.youtube.com/live2", "stream_key": "…"}' ``` The stream key is a password for your channel on that service. We store it encrypted, never show it again — the API returns only its last four characters — and delete it an hour after the broadcast ends. Pause a destination with `PATCH …/destinations/{id}` and `{"enabled": false}`, and remove it with `DELETE`. See [Restreaming in the API reference](/docs/api/reference/restreaming) for every field. ## Watching a destination [Section titled “Watching a destination”](#watching-a-destination) Each destination reports a `status`: | Status | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------- | | `idle` | Waiting for the broadcast to go live. | | `connecting` | Connecting to the service. | | `live` | The service is receiving the stream. | | `failing` | The service refused or dropped the stream; `last_error` says why, and we keep retrying while the broadcast is live. | A paused destination (`"enabled": false`) sends nothing until you turn it back on. A refused stream key reads *“the service refused the stream; check the stream key and that the service is ready to go live.”* Most services also refuse a stream until you have set up the live event on their side. ## YouTube [Section titled “YouTube”](#youtube) 1. Your channel must be allowed to live stream: verify it with a phone number, then turn on live streaming in YouTube Studio. **The first time can take up to a day**, so do it well before the broadcast. 2. In YouTube Studio, choose **Create → Go live → Stream**. 3. Copy the **Stream URL** — choose the secure (RTMPS) one — and the **Stream key**, and add them as a destination. YouTube asks for H.264 video with a keyframe at least every 4 seconds (2 is recommended) and AAC audio. ## Twitch [Section titled “Twitch”](#twitch) 1. Turn on **two-factor authentication** for the Twitch account. Twitch requires it before you can stream. 2. In the Twitch **Creator Dashboard**, open **Settings → Stream** and copy your **Primary Stream key**. 3. Add a destination with the server URL `rtmps://ingest.global-contribute.live-video.net/app` and that key. Twitch’s broadcasting guidelines call for a 2-second keyframe interval and top out at 1080p at 60 frames a second, 6,000 Kbps. Send Twitch a `standard_1080p` broadcast within those limits. ## Facebook [Section titled “Facebook”](#facebook) 1. Facebook allows live streaming from a Page or professional profile once the account is at least 60 days old and the Page or profile has at least 100 followers. 2. In **Live Producer**, choose **Streaming software**. Turn on **Use a persistent stream key** if you want the same key to work every week; otherwise a key works for one live video. 3. Copy the **Server URL** and **Stream key** and add them as a destination. Facebook accepts **RTMPS only**. Facebook asks for H.264 video with a keyframe every 2 to 4 seconds, AAC audio, and limits a broadcast to eight hours. ## Encoder settings that suit every service [Section titled “Encoder settings that suit every service”](#encoder-settings-that-suit-every-service) Because each service receives your encoder’s stream as it is, set your encoder to what every destination accepts. These settings suit YouTube, Twitch and Facebook together, as well as Cardinal’s own player: | Setting | Value | | ----------------- | ------------------------------------- | | Video | H.264, constant bitrate (CBR) | | Resolution | 1920×1080 at 30 or 60 frames a second | | Video bitrate | 6,000 Kbps or less | | Keyframe interval | 2 seconds | | Audio | AAC, 48 kHz, stereo, 128–160 Kbps | See [Encoder settings](/docs/encoders) for where each one is in OBS and common hardware encoders. ## Cost [Section titled “Cost”](#cost) Each destination is billed **per destination-hour while it is live**: $1 an hour for a 1080p stream, $3 for 4K, rounded up to the minute once per destination per broadcast, with no minimum. Time spent connecting, failing or paused is never billed, so a destination that never connects costs nothing.