# API tokens and connected apps Source: https://streamloop.app/docs/account-access Manage personal API tokens and review the OAuth apps and agents that can act on your Streamloop account. Streamloop gives you two ways to authorize access to your account from outside the dashboard: * **API tokens** — long-lived `sl_…` secrets you create yourself and send as the `x-api-key` header. Use them for your own scripts, servers, and CI pipelines. * **Connected apps** — third-party apps and AI agents that you signed into through Streamloop's OAuth flow (for example, an [MCP agent](/docs/api-reference/mcp/overview) like Claude or ChatGPT). Use this list to review who you've authorized and revoke access when you no longer need it. Both live under your profile in the dashboard. ## Open account access settings 1. Open the [dashboard](https://streamloop.app/loops). 2. Click your avatar in the top right and choose **Settings**. 3. In the settings dialog, select **API Tokens** or **Connected Apps** from the side menu. ## API tokens The **API Tokens** panel lists every personal API token on your account, when it was created, when it expires, and when it was last used. From here you can mint a new token or revoke an existing one. ### Create a token 1. Open **Settings → API Tokens**. 2. Click **New token**. 3. Give the token a descriptive name (for example, `CI pipeline` or `monitoring-prod`) so future-you can tell what it's for. 4. Pick an expiration: **30 days**, **90 days**, **1 year**, or **No expiration**. Shorter is safer — rotate when you can. 5. Click **Create token**. The token is shown **once**, immediately after creation: ```bash theme={null} sl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Copy it with the copy button and store it somewhere safe — a secret manager, a CI variable, a `.env` file that's not committed. Once you dismiss the reveal banner, the full token cannot be retrieved again; if you lose it, you'll need to create a new one. > [!WARNING] > > An API token is a **password-equivalent secret** that carries your full account access in this first version. Store it server-side, never commit it to source control, and never ship it in client-side or browser code. If a token leaks, revoke it immediately. ### Use a token Send the token in the `x-api-key` header on any REST or GraphQL request: ```bash theme={null} curl https://api.streamloop.app/v1/me \ -H "x-api-key: sl_your_token_here" ``` See [Authentication](/docs/api-reference/authentication) for the full request contract and OAuth alternative. ### Revoke a token 1. Find the token in the list. 2. Click the trash icon and confirm. Revocation takes effect immediately — the next request that uses the token will be rejected. Revoke a token any time you suspect it's leaked, when the script or server that used it is retired, or as part of routine rotation. ## Connected apps The **Connected Apps** panel shows every third-party client you've authorized through OAuth. Each entry is an app — an [MCP agent](/docs/api-reference/mcp/overview), a custom integration, or any OAuth client — that completed Streamloop's authorization-code flow and received an access token tied to your account. For each connected app you'll see: * The app's name (or its hostname if no name is registered). * The scopes you granted, such as `streamloop:read` or `streamloop:write`. See the full list on the [Authentication](/docs/api-reference/authentication#scopes) page. * When you first authorized the app. ### Revoke an app To stop an app from acting on your behalf: 1. Open **Settings → Connected Apps**. 2. Find the app in the list. 3. Click the trash icon and confirm. Revoking consent invalidates the app's existing access and refresh tokens — its next API call will fail, and it won't be able to mint new tokens without you signing in and approving again. If you reauthorize the same app later, it starts from a fresh consent. > [!NOTE] > > Revoking a connected app does **not** revoke any API tokens you created yourself in **API Tokens**. The two lists are independent: API tokens are yours, connected apps belong to a third party you delegated to. ## Which should I use? | Use this | When | | - | - | | **API token** | A script, server, or CI job you own and control needs to call the Streamloop API. | | **Connected app (OAuth)** | A third-party tool — an AI agent, a published integration — needs to act on your account, and you want to grant only specific scopes and be able to revoke it cleanly. | If you're building an integration that other Streamloop users will sign into, register it as an OAuth client instead of asking them for API tokens. See [Authentication](/docs/api-reference/authentication) for the OAuth 2.1 flow and dynamic client registration. # Get your profile Source: https://streamloop.app/docs/api-reference/account/get-your-profile https://api.streamloop.app/v1/openapi.json get /me # List your workspaces Source: https://streamloop.app/docs/api-reference/account/list-your-workspaces https://api.streamloop.app/v1/openapi.json get /workspaces Every workspace you are a member of, with your role in each. Pass one's id as X-Workspace-Id: an API key carries no workspace, so every other route needs it. # Authentication Source: https://streamloop.app/docs/api-reference/authentication Authenticate Streamloop API requests with an API key or OAuth 2.1, scoped to what each integration needs. Every Streamloop API request is tied to your account. There are two ways to authenticate: * **API keys** — a single secret you send on each request. Best for your own scripts, servers, and backends. * **OAuth 2.1** — an authorization-code flow with PKCE. Best for third-party apps and AI agents that act on a user's behalf (this is what the [MCP server](/docs/api-reference/mcp/overview) uses). Both work on the REST, Scenes and GraphQL APIs. The MCP server uses OAuth. ## API keys Send your key in the `x-api-key` header: ```bash theme={null} curl https://api.streamloop.app/v1/me \ -H "x-api-key: sl_your_key_here" ``` An API key carries no workspace. On every other route, also send the workspace in `X-Workspace-Id` (from `GET /v1/workspaces`), or the request answers `400 WORKSPACE_REQUIRED`. See [Workspaces](/docs/api-reference/conventions#workspaces). Streamloop API keys are prefixed with `sl_`. Create, list, and revoke them from your account settings in the [dashboard](https://streamloop.app/loops) — see [API tokens and connected apps](/docs/account-access) for a step-by-step walkthrough. > [!WARNING] > > An API key is a password-equivalent secret. Store it server-side, never commit it to source control, and never expose it in client-side or browser code. Revoke a key immediately if it leaks. > [!NOTE] > > In this first version, an API key carries your full account access. Per-key scopes are on the way — until then, treat every key as fully privileged and prefer OAuth (below) when you need to limit what an integration can do. ## OAuth 2.1 For apps and agents acting on behalf of a Streamloop user, use the OAuth 2.1 authorization-code flow with **PKCE (S256)**. Access tokens are ES256 JWTs you then send as a bearer token: ```bash theme={null} curl https://api.streamloop.app/v1/me \ -H "Authorization: Bearer " ``` The authorization server is at `https://auth.streamloop.app` and supports **dynamic client registration** (RFC 7591), so agents can register without manual setup. Endpoints are discoverable from standard metadata documents: | Document | URL | | - | - | | Authorization Server metadata (RFC 8414) | `https://streamloop.app/.well-known/oauth-authorization-server` | | Protected Resource metadata (RFC 9728) | `https://streamloop.app/.well-known/oauth-protected-resource` | | OpenID configuration | `https://auth.streamloop.app/.well-known/openid-configuration` | | JWKS (token verification keys) | `https://auth.streamloop.app/api/auth/jwks` | Key endpoints: | Endpoint | URL | | - | - | | Authorization | `https://auth.streamloop.app/api/auth/oauth2/authorize` | | Token | `https://auth.streamloop.app/api/auth/oauth2/token` | | Dynamic client registration | `https://auth.streamloop.app/api/auth/oauth2/register` | ## Scopes OAuth access tokens are limited to the scopes you request. Ask for the least you need. | Scope | Grants | | - | - | | `streamloop:read` | Read streams, destinations, playlists, uploads, workspaces, scenes, and billing status | | `streamloop:write` | Create and control streams, destinations, playlists, uploads, and scenes | | `streamloop:destructive` | Delete streams, uploads, and scenes; discard a scene's draft; revoke a scene's ingest keys and secrets | | `streamloop:billing` | Create top-up links and crypto deposit addresses (REST API only) | Each scope includes the ones above it: `destructive` includes `write`, and `write` includes `read`. `read` also covers the credit balance and usage. The MCP server asks only for `read`, `write` and `destructive`. > [!TIP] > > Read-only automation (dashboards, monitoring) only needs `streamloop:read`. Add `streamloop:write` to create and control streams, and request `streamloop:destructive` or `streamloop:billing` only when an integration genuinely deletes resources or moves money. ## Trying endpoints in the docs The **Try it** playground on each REST endpoint page authenticates with a **bearer token** (your OAuth access token). To try an endpoint with an API key instead, send the request from your own terminal with the `x-api-key` header shown above. # Create a card top-up link Source: https://streamloop.app/docs/api-reference/billing/create-a-card-top-up-link https://api.streamloop.app/v1/openapi.json post /billing/top-up/card-link Purchase credits by dollar amount. amountCents is the dollar amount in cents (e.g. 500 = $5). Default (no savedCardId): returns a checkout URL from the selected payment provider for hosted checkout. When savedCardId is set: the referenced vaulted card is charged off-session on the same provider (no redirect) and checkoutUrl is empty on success. checkoutUrl is only populated when the provider requires an interactive step (e.g. a 3DS/SCA re-authentication) to complete the charge. savedCardId and saveCard=true are mutually exclusive. Requires workspace role owner or admin. # Get billing overview Source: https://streamloop.app/docs/api-reference/billing/get-billing-overview https://api.streamloop.app/v1/openapi.json get /billing/overview # Get usage analytics Source: https://streamloop.app/docs/api-reference/billing/get-usage-analytics https://api.streamloop.app/v1/openapi.json get /billing/usage # List invoices Source: https://streamloop.app/docs/api-reference/billing/list-invoices https://api.streamloop.app/v1/openapi.json get /billing/invoices # Conventions Source: https://streamloop.app/docs/api-reference/conventions Versioning, workspaces, cursor pagination, idempotent retries, rate limits and the RFC 7807 error model shared by the Streamloop REST API and Scenes API. These rules hold across the REST API and the [Scenes API](/docs/api-reference/scenes). Once you know them, you can read the rest of the reference quickly. ## Base URL and versioning All requests go to a versioned base path: ``` https://api.streamloop.app/v1 ``` The version (`v1`) is in the path. We add fields and endpoints without changing the version. A breaking change would ship under a new version path, so your integration keeps working. ### Deprecation and sunset Nothing in `v1` is retiring today. When something does, this is how you hear about it, at least 90 days before it stops: * **REST:** every answer of a retiring route carries `Deprecation: @` (when it was marked, [RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)), `Sunset: ` (when it stops, [RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)) and `Link: ; rel="deprecation"`, and the route is marked `deprecated` in [`/v1/openapi.json`](https://api.streamloop.app/v1/openapi.json) with what replaces it in its description. * **GraphQL:** a retiring field or argument is marked `@deprecated(reason: "…")` in the schema, with what replaces it; introspection shows it. * **This page** lists what is retiring and when. A retired route answers `410` with code `GONE` after its sunset date. A whole version (`/v1`) would get the same notice, a year rather than 90 days. ## Requests and responses Requests and responses are JSON (`Content-Type: application/json`). Send a JSON body on `POST`, `PATCH` and `PUT`, and query parameters on `GET`. The Scenes API also takes code as the body (`text/jsx`, `text/tsx`, `text/typescript`) and answers pictures as `image/png`. A single-resource response returns the resource itself: ```json theme={null} { "id": "stream_01J…", "name": "Lofi radio", "state": "active", "quality": "q_1080p", "framerate": "f_30" } ``` ## Workspaces Everything you create belongs to a [workspace](/docs/workspaces-and-team). Name the workspace in the `X-Workspace-Id` header: ```bash theme={null} curl https://api.streamloop.app/v1/streams \ -H "X-API-Key: $SL_KEY" \ -H "X-Workspace-Id: wksp_…" ``` * **With an API key, the header is required.** A key carries no workspace, so a request without it answers `400 WORKSPACE_REQUIRED`. * **With an OAuth token** it defaults to the workspace the token was issued for. * If you aren't a current member of the workspace, the answer is `403 WORKSPACE_FORBIDDEN`. It never falls back to another workspace. `GET /v1/workspaces` lists your workspaces, with your role in each. It's the one route that doesn't need the header. ## Pagination List endpoints are **cursor-paginated**. Pass `limit` to set the page size and `cursor` to get the next page: ```bash theme={null} curl "https://api.streamloop.app/v1/streams?limit=20" \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" ``` The response has the items in `data` and paging details in `page`: ```json theme={null} { "data": [ { "id": "stream_01J…", "name": "Lofi radio", "state": "active" } ], "page": { "hasMore": true, "nextCursor": "eyJpZCI6…" } } ``` Send `nextCursor` back as `cursor` for the next page. When `hasMore` is `false`, `nextCursor` is `null` and you've reached the end. ## Idempotent retries Send an `Idempotency-Key` header (any unique string, for example a UUID) on `POST` and `PATCH` requests. If you repeat a request with the same key within 24 hours, you get the original response again, with `Idempotency-Replayed: true`, and nothing runs twice. Server errors (`5xx`) aren't stored, so a retry after one runs again. A key belongs to one request. If you reuse a key with a different body, the API refuses it with `422 IDEMPOTENCY_KEY_REUSED` and doesn't run the request. Use a new key for each new request, and the same key only to retry. ```bash theme={null} curl -X POST https://api.streamloop.app/v1/streams \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Idempotency-Key: 6f1c2e9a-…" \ -H "Content-Type: application/json" \ -d '{ "name": "Lofi radio" }' ``` ## Rate limits Each caller can make **600 requests a minute**. Every response carries the current state: | Header | Meaning | | - | - | | `RateLimit-Limit` | Requests allowed per window | | `RateLimit-Remaining` | Requests left in this window | | `RateLimit-Reset` | Seconds until the window resets | | `RateLimit` | The same three as one field: `limit=600, remaining=599, reset=1` (the current [IETF draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)) | | `RateLimit-Policy` | The quota and its window: `600;w=60` | Over the limit, the answer is `429` with code `RATE_LIMITED` and a `Retry-After` header in seconds. The GraphQL endpoint draws on the same allowance for a call with an API key or a bearer token, with the same headers; its `429` is a GraphQL `errors` entry with `extensions.code` `RATE_LIMITED`. ## Long-running requests A request whose work goes on after the answer returns `202 Accepted`: the body has the resource (its `id`), and the `Location` header is where to poll, `GET /v1/uploads/{id}` for an import from a link. When the answer is a list, `Location` is the first item's and the body has every id. Poll until the resource says it is done (`uploaded` true, or `externalSource.state.status` of `COMPLETED` or `FAILED`). ## Concurrency Some edits take the version you read, so you never overwrite a newer change: * **Playlists:** pass `basedOnVersion` to `POST /playlists/{id}/operations`. If the playlist moved on, the batch is refused with `409 CONFLICT`. * **Scenes:** send the resource's `ETag` as `If-Match`. If it changed, the answer is `412 PRECONDITION_FAILED`. See [Scenes API](/docs/api-reference/scenes#concurrency). ## Errors Successful requests return `2xx`. Errors return the matching HTTP status and an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem document (the successor of RFC 7807, same shape) (`Content-Type: application/problem+json`): ```json theme={null} { "type": "about:blank", "title": "Conflict", "status": 409, "detail": "Destination change refused", "code": "STREAM_LIVE" } ``` | Field | Description | | - | - | | `type` | A URI reference for the problem type (`about:blank`) | | `title` | A short summary of the HTTP status | | `status` | The HTTP status code | | `detail` | What happened in this case | | `code` | A stable, machine-readable error code. Branch on this. | Some problems carry more fields, such as a `reason`, or `errors` and `currentRevision` in the Scenes API. ### Codes and statuses | Status | Codes | | - | - | | `400` | `BAD_REQUEST` (a body or query that can't be parsed), `INVALID_BATCH`, `WORKSPACE_REQUIRED` | | `401` | `UNAUTHENTICATED` | | `402` | `INSUFFICIENT_CREDITS` | | `403` | `FORBIDDEN`, `INSUFFICIENT_SCOPE`, `WORKSPACE_FORBIDDEN`, `MULTISTREAM_NOT_ENABLED` | | `404` | `NOT_FOUND` (a resource, or a path no route has: the routes are in `/v1/openapi.json`) | | `405` | `METHOD_NOT_ALLOWED` (the `Allow` header lists the route's methods) | | `409` | `STREAM_LIVE`, `STREAM_ILLEGAL_TRANSITION`, `IMMUTABLE_WHILE_LIVE`, `NOT_LIVE`, `DESTINATION_LIMIT`, `YOUTUBE_DESTINATION_LIMIT`, `DESTINATION_DUPLICATE`, `LAST_DESTINATION`, `MIRROR_DESTINATION_CHANGE_BLOCKED`, `IN_USE`, `UPLOAD_INCOMPLETE`, `UPLOAD_ALREADY_COMPLETED`, `STALE_DRAFT`, `CONFLICT` | | `422` | `INVALID_INPUT` (a request that parses but doesn't fit: a missing or unknown field, a value of the wrong type), `STREAM_GUARD_FAILED`, `DESTINATION_INCOMPATIBLE`, `SCENE_MISSING`, `SCENE_NOT_PUBLISHED`, `SCENE_ASSET_MISSING`, `SCENE_PLAYLIST_MISSING`, `SCENE_PLAYLISTS_UNSUPPORTED`, `SCENE_QUALITY_UNSUPPORTED`, `UPLOAD_REMOVED`, `UNSUPPORTED_TYPE`, `FILE_TOO_LARGE`, `IDEMPOTENCY_KEY_REUSED` | | `410` | `GONE` (a route past its sunset date; see [Deprecation and sunset](#deprecation-and-sunset)) | | `429` | `RATE_LIMITED` | | `500` | `INTERNAL` | | `502` | `UPSTREAM_ERROR` (an unexpected failure) | | `503` | `UNAVAILABLE` (try again) | Both APIs use the same split: `400 BAD_REQUEST` only when the request can't be read at all (a body that isn't JSON), `422 INVALID_INPUT` for one that can be read but doesn't fit, with `detail` naming the field. An unknown query parameter is `422 INVALID_INPUT` too. A code not in this table follows its suffix: `…_NOT_FOUND` is `404`, `…_FORBIDDEN` is `403`, `…_UNAVAILABLE` is `503`, and anything else is `422`. The Scenes API has its own codes as well. See [Scenes API errors](/docs/api-reference/scenes#errors). > [!TIP] > > Treat `429` and `5xx` as retryable: back off, retry with jitter, and send the same `Idempotency-Key`. Treat other `4xx` responses as permanent. Fix the request instead of retrying it. ## Batches `POST /v1/batch` runs up to 20 REST operations in order, in one request. Results keep the input order. A failed operation doesn't stop the ones after it, and the batch isn't a transaction. ## Service status `GET /v1/status` is an unauthenticated health check. `GET /v1/openapi.json` returns the REST API's OpenAPI document, and `GET /v1/scenes/openapi.json` the Scenes API's. # Get a crypto deposit address Source: https://streamloop.app/docs/api-reference/crypto/get-a-crypto-deposit-address https://api.streamloop.app/v1/openapi.json get /crypto/deposit-address Get (or lazily create) a deposit address for the authenticated user. id must be a valid 0xProcessing currency identifier from cryptoCurrencies. # List crypto currencies Source: https://streamloop.app/docs/api-reference/crypto/list-crypto-currencies https://api.streamloop.app/v1/openapi.json get /crypto/currencies List all supported cryptocurrencies with live rates. # Create an RTMP destination Source: https://streamloop.app/docs/api-reference/destinations/create-an-rtmp-destination https://api.streamloop.app/v1/openapi.json post /destinations # List destinations Source: https://streamloop.app/docs/api-reference/destinations/list-destinations https://api.streamloop.app/v1/openapi.json get /destinations # GraphQL API Source: https://streamloop.app/docs/api-reference/graphql Query Streamloop's GraphQL schema directly for full control over filtering and field selection — the layer the REST API and MCP server are built on. The REST API and MCP server are curated layers over Streamloop's **GraphQL API**. If you want to select exactly the fields you need, filter and sort server-side, or traverse relationships in one round trip, you can query GraphQL directly. > [!NOTE] > > GraphQL is the advanced, lower-level surface. For most integrations the [REST API](/docs/api-reference/quickstart) is simpler and gives you a stable, versioned contract. Reach for GraphQL when you specifically need its flexibility. ## Endpoint | | | | - | - | | URL | `https://api.streamloop.app/graphql` | | Method | `POST` (`application/json`) | | Auth | `x-api-key` or OAuth bearer — same as REST (see [Authentication](/docs/api-reference/authentication)) | Introspection is enabled, so you can explore the full schema from any GraphQL client (GraphiQL, Apollo Sandbox, Insomnia) pointed at the endpoint. ## Making a request ```bash theme={null} curl -X POST https://api.streamloop.app/graphql \ -H "x-api-key: sl_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "query": "query { me { id email firstName } }" }' ``` ## Schema shape The schema is generated from the data model, so it follows consistent **Relay** conventions: * **Connections** for lists — `first` / `after` arguments and `{ totalCount, pageInfo { hasNextPage, endCursor }, edges { node { … } } }` results. (The REST API flattens these into `{ data, page }`.) * **`where` inputs** for filtering and **`orderBy`** for sorting on list fields. * A **`Cursor`** scalar for pagination positions. ```graphql theme={null} query ListStreams($first: Int = 20, $after: Cursor, $where: StreamWhereInput) { streams(first: $first, after: $after, where: $where) { totalCount pageInfo { hasNextPage endCursor } edges { node { id name state createdAt } } } } ``` ## Errors A refusal is an entry of the top-level `errors` array. Its `extensions` has the shape of the schema's `ApiError` type: `code` is a value of the `ErrorCode` enum, and some codes add a `reason` or a `cause`. Branch on `code`, never on the message. ```json theme={null} { "errors": [ { "message": "The stream is live; stop it before changing its destinations.", "path": ["addStreamDestination"], "extensions": { "code": "STREAM_LIVE" } } ], "data": null } ``` The codes are the same ones the REST API answers, and the [conventions table](/docs/api-reference/conventions#codes-and-statuses) gives each its meaning and HTTP status. Introspect `ErrorCode` for the full list with a one-line description each. ## Deprecation A retiring field or argument is marked `@deprecated(reason: "…")` with what replaces it, and stays for at least 90 days. Introspection shows the mark (`includeDeprecated: true`), and the [conventions page](/docs/api-reference/conventions#deprecation-and-sunset) lists what is retiring and when. Additions never break a query that worked. ## Choosing GraphQL vs REST vs MCP > **REST** > > Stable, versioned, simplest to call. The default choice for scripts and servers. > **MCP** > > For AI agents — the same operations exposed as tools over OAuth. > **GraphQL** > > Maximum control over fields, filtering, and relationships. Advanced clients. > [!WARNING] > > The GraphQL schema tracks the data model and can evolve faster than the REST contract. If you need a fixed, long-lived interface, build on the versioned [REST API](/docs/api-reference/introduction) instead. # Streamloop API Source: https://streamloop.app/docs/api-reference/introduction Create and control 24/7 loop streams programmatically — over REST, an MCP server for AI agents, or GraphQL. The Streamloop API lets you do what the dashboard does — upload media, build playlists, connect destinations, and start, schedule, and monitor 24/7 loop streams — from your own code or an AI agent. The Scenes API builds and runs live, designed scenes. Everything runs on the same backend as the app, so a stream you create over the API behaves exactly like one created in the dashboard. ## Ways to integrate > **[REST API](/docs/api-reference/quickstart)** > > A clean, versioned HTTP API at `api.streamloop.app/v1`. Best for scripts, servers, and backends. Start here. > **[Scenes API](/docs/api-reference/scenes)** > > Build, check, publish and run scenes over HTTP at `api.streamloop.app/v1/scenes`. > **[MCP server](/docs/api-reference/mcp/overview)** > > A Model Context Protocol server for AI agents (Claude, ChatGPT, and others) to call Streamloop as tools. > **[GraphQL](/docs/api-reference/graphql)** > > Direct access to the underlying GraphQL schema. For advanced clients that want to shape their own queries. The REST API, the Scenes API and the MCP server cover the same capabilities and share the same accounts, workspaces and scopes — pick whichever fits how you're building. ## Base URLs | Surface | Base URL | | - | - | | REST API | `https://api.streamloop.app/v1` | | OpenAPI document | `https://api.streamloop.app/v1/openapi.json` | | Scenes API | `https://api.streamloop.app/v1/scenes` | | Scenes OpenAPI document | `https://api.streamloop.app/v1/scenes/openapi.json` | | MCP server (Streamable HTTP) | `https://mcp.streamloop.app/mcp` | | GraphQL | `https://api.streamloop.app/graphql` | | Authorization server | `https://auth.streamloop.app` | ## What you can do > **Streams** > > Create, update, start, stop, schedule, and monitor 24/7 loop streams; check readiness and live stats. > **Media & playlists** > > Upload video and audio (direct or from a link), then build, reorder, copy, and publish playlists. > **Destinations** > > Add custom RTMP targets, and send one stream to up to 5 destinations ([multistream](/docs/platform/multistream)). > **Scenes** > > Write a scene's frames as JSX, get a picture of each, publish, and put it on a stream. > **Workspaces** > > List your workspaces and act in one with the `X-Workspace-Id` header. > **Billing** > > Read your credit balance, usage analytics, and invoices, and create top-up links (card or crypto). ## Next steps **1. Authenticate** Get an API key or set up OAuth — see [Authentication](/docs/api-reference/authentication). **2. Learn the conventions** Workspaces, pagination, retries, rate limits and errors are consistent across every endpoint — see [Conventions](/docs/api-reference/conventions). **3. Make your first call** Follow the [Quickstart](/docs/api-reference/quickstart) to go from an API key to a live stream with `curl`. > [!NOTE] > > The REST endpoint pages in this section are generated directly from the live > [OpenAPI document](https://api.streamloop.app/v1/openapi.json), so they always match what the API actually accepts and returns. # MCP server: connection and auth reference Source: https://streamloop.app/docs/api-reference/mcp/overview Streamloop MCP server reference: endpoint, Streamable HTTP transport, OAuth 2.1 sign-in and scopes, workspace selection, confirmation rules and error codes. Streamloop runs a hosted **Model Context Protocol (MCP)** server. An AI agent connected to it can do what you do in the dashboard: upload media, fill playlists, connect destinations, start and schedule 24/7 streams, and build and run scenes. It signs in with OAuth as you, so it sees only the workspaces you belong to. For a step-by-step setup in Claude, ChatGPT, Cursor or Claude Code, see [Use Streamloop with AI](/docs/use-with-ai). For what the server lets an assistant do with a stream, in plain words, see the [live streaming MCP server page](https://streamloop.app/features/mcp). ## Endpoint | | | | - | - | | URL | `https://mcp.streamloop.app/mcp` | | Transport | Streamable HTTP | | Auth | OAuth 2.1 (authorization code with PKCE, S256) | | Tools | 67, listed in [Tools](/docs/api-reference/mcp/tools) | ## Registry listings Find the hosted Streamloop server in these registries: * [Official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.streamloop%2Fmcp/versions/latest): `io.github.streamloop/mcp`, published under the Streamloop GitHub organization. * [Smithery](https://smithery.ai/servers/streamloop/mcp): `streamloop/mcp`. * [Glama](https://glama.ai/mcp/connectors/io.github.streamloop/mcp): `io.github.streamloop/mcp`. These listings point to the hosted endpoint above. The Official MCP Registry entry links back to this documentation. ## Connect an agent Add the server URL to any MCP-capable client. On first use the client opens Streamloop's sign-in page. You sign in, approve the scopes, and the client gets a token. There's no API key to copy. **Claude** In Claude, open **Settings → Connectors**, choose **Add custom connector**, and enter `https://mcp.streamloop.app/mcp`. Then complete the sign-in prompt. **ChatGPT** Add a custom connector (MCP server) with the URL `https://mcp.streamloop.app/mcp`, and authorize when prompted. **Claude Code** Install the Streamloop plugin, which adds the server and the [Streamloop skills](https://github.com/streamloop/skills): ```text theme={null} /plugin marketplace add streamloop/skills /plugin install streamloop@streamloop ``` Or add only the server: `claude mcp add --transport http streamloop https://mcp.streamloop.app/mcp`. Any other agent that reads skills: `npx skills add streamloop/skills`. **Custom client** Point any MCP client that speaks **Streamable HTTP** at `https://mcp.streamloop.app/mcp`. An unauthenticated request gets a `401` with a `WWW-Authenticate` challenge, and the server publishes RFC 9728 protected-resource metadata at `https://mcp.streamloop.app/.well-known/oauth-protected-resource`. Standards-compliant clients find the authorization server from there. See [Authentication](/docs/api-reference/authentication). ## Permissions The agent gets only the scopes you approve. Each scope includes the ones above it: | Scope | Lets the agent | | - | - | | `streamloop:read` | Read streams, destinations, playlists, uploads, workspaces, credits and scenes | | `streamloop:write` | Everything in `read`, plus create and control streams, destinations, playlists, uploads and scenes | | `streamloop:destructive` | Everything in `write`, plus delete streams, uploads and scenes, discard a scene's draft, and revoke a scene's ingest keys and secrets | A tool the token's scopes don't cover answers `Error [INSUFFICIENT_SCOPE]` and names the scope it needs. > [!NOTE] > > No MCP tool can spend money. The agent can read your credit balance and usage, but buying credits happens in the [dashboard](https://streamloop.app/loops). ## Workspaces Everything (streams, media, playlists, destinations, scenes and credits) belongs to one [workspace](/docs/workspaces-and-team). The agent works in one workspace at a time: 1. The workspace it chose with `select_workspace` in this connection, if any. 2. Otherwise the workspace your client sends in an `X-Workspace-Id` header, if you configured one. 3. Otherwise your default workspace from sign-in. `get_selected_workspace` says which one is in use and why. An empty list is often the wrong workspace, not an empty account. ## Safety * **Irreversible tools need `confirm: true`.** Five tools delete or revoke something for good (marked **Yes, confirm** on the [Tools](/docs/api-reference/mcp/tools) page). Called without `confirm: true`, they change nothing and answer `Error [CONFIRM_REQUIRED]`, saying exactly what would go. A well-behaved agent then tells you and asks before it calls again. * **Changes on air ask first.** `publish_scene` and `revert_scene` on a scene that a running stream plays need `confirm: true`, because viewers see the change at once. Without it they answer `CONFIRM_REQUIRED` and name the streams. `confirm` always means "I accept what the refusal named". * **Tool hints.** Every tool declares the standard MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so clients can auto-approve reads and ask you before writes. `destructiveHint` is set on the tools that can't be undone and on those that change what viewers see (`stop_stream`, `swap_live_playlist`, `set_stream_destination_enabled`, `assign_playlist_order`, `publish_scene`, `revert_scene`, `control_scene`), plus `scene_remove` and `discard_scene_draft`. The scene, stream, playlist and smart order tools also publish an `outputSchema` (the answer or an error, `anyOf`); [MCP tools](/docs/api-reference/mcp/tools) lists the ones that don't. * **Unknown arguments are refused**, with the nearest valid field name, so a typo like `dryrun` never runs a real publish. ## Errors A failed tool call returns `isError: true`, one line of text with a stable code in brackets, and the same as `structuredContent`: ```text theme={null} Error [STREAM_LIVE]: Destination change refused ``` ```json theme={null} { "code": "STREAM_LIVE", "detail": "Destination change refused" } ``` Act on `code`, not the wording. `structuredContent` also carries what the refusal names: `errors` (each problem), `currentRevision`, or `publishedVersion` and `reverted` for `STALE_PUBLISH`. The codes are the REST API's: one code per meaning. If Streamloop's API doesn't answer, the code is `UNAVAILABLE`: try again. Common codes: | Code | Meaning | | - | - | | `INVALID_INPUT` | An argument is missing, wrong or unknown. | | `INSUFFICIENT_SCOPE` | The token lacks the scope the tool needs. | | `CONFIRM_REQUIRED` | The call needs `confirm: true`. Ask the user first. | | `NOT_FOUND` | No such id in the selected workspace. | | `STREAM_LIVE` | The stream must be stopped for this change. | | `REVISION_REQUIRED` | A scene change to something that exists needs the `revision` you read. | | `STALE_PUBLISH` | Someone published since the version your draft is based on (or since the version a revert saw). | | `MULTISTREAM_NOT_ENABLED` | A second destination needs multistream, and it is turned off for this workspace. | | `DESTINATION_LIMIT`, `YOUTUBE_DESTINATION_LIMIT` | At most 5 destinations per stream, and at most one YouTube channel. | | `IN_USE` | Something still uses it (for example, a playlist uses the upload). | | `CONFLICT` | Someone changed it since you read it. The error carries `currentRevision`. | | `CHECK_FAILED`, `SCENE_INVALID` | A scene change or publish would break the scene. Nothing changed, and the error lists each problem. | A failed sign-in reads `Authorization failed [CODE]: …`. Reconnect the server to get a new token. ## Scenes > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). A scene (id `scn_…`) is a live, designed stream: frames of text, pictures, video, web pages, tables of data and tickers, the sources they read, and a script. Agents edit a scene the same way the studio's own [AI assistant](/docs/scenes/ai-assistant) does, with four tools that act on **paths**: | Tool | What it does | | - | - | | `scene_get` | Reads anything by path. `""` lists the kinds of thing a scene holds; `element/*` lists the building blocks and their props; `frame/` returns one frame as JSX code, a `…` element. With `as: "image"` it returns a picture of the frame as it goes on air, with any problems (outside title-safe, overlapping, text too small). | | `scene_set` | Creates or changes one thing: a whole value, or exact text edits to its code. Every change is checked as the studio checks it. A refused change changes nothing and lists every problem. | | `scene_remove` | Deletes whole things, all or nothing. Anything still in use is refused, with what uses it. | | `scene_probe` | Fetches a URL once, changing nothing, to show what a table would read from it. | The main paths: | Path | What it is | | - | - | | `frame/` | One frame: a background and layers. One is on air at a time. `frame/*` lists them. | | `frame//` | One layer in a frame. | | `source//` | Something the scene reads: a camera, an RTMP or SRT input, a file, a playlist, a web page, a table (`data`) or a formula over tables. | | `control/` | A switch, text or choice the operator sets while it runs. | | `component/` | A reusable element, such as a lower third, written in TSX. | | `script/show.ts`, `script/state.ts` | The scene's behaviour, and the shape of its state. | | `tokens` | Named colours, fonts and sizes. | | `element/` | The building blocks (`Text`, `Image`, `Video`, `Stack`, `Ticker`, …). Read-only. | **Revisions.** Every answer carries a `revision` for what it read and a `draftRevision` for the whole draft. Send the `revision` back to `scene_set` or `scene_remove`, and the change is refused with `CONFLICT` (and the `currentRevision`) if someone edited it in between. Over the MCP it's required for anything that exists: without it the answer is `REVISION_REQUIRED`, and `"*"` replaces or removes whatever is there, on purpose. Studios open on the scene show the agent's edits live. **Publishing.** Edits change the draft only. Nothing reaches viewers until you publish: **1. Check** `publish_scene` with `dryRun: true` checks the whole scene and lists what would change, publishing nothing. **2. Publish** `publish_scene` with the `draftRevision` of your last answer publishes exactly the draft you saw. If a running stream plays the scene, it also needs `confirm: true`. An unchanged draft makes no new version. **3. Put it on a stream** `set_stream_scene` while the stream is stopped, then `start_stream`. The stream opens on the scene's first frame. While it runs, `control_scene` takes frames to air (`goFrame`), cues the next one (`next`), sets controls and replaces a table's rows, and `get_scene_state` says what's on air (`frame`, under `onAir`). `list_scene_versions` and `revert_scene` go back to an earlier version; `discard_scene_draft` throws the draft away. # MCP tools Source: https://streamloop.app/docs/api-reference/mcp/tools All 67 Streamloop MCP tools by area: what each one does, the OAuth scope it needs, and which ones are irreversible. The [MCP server](/docs/api-reference/mcp/overview) has **67 tools**. Names are `snake_case`, with no prefix. Each tool's own description (what an agent reads) says when to use it and which tool comes next. This page lists them by area. * **Scope** is the OAuth scope the tool needs. `write` includes `read`, and `destructive` includes both. * **Destructive** matches each tool's `destructiveHint` annotation: the tool can't be undone or changes what viewers see. **Yes, confirm** tools are irreversible: they need `confirm: true`, or they answer `Error [CONFIRM_REQUIRED]`, say exactly what would go, and change nothing. * Every scene, stream, playlist and smart order tool publishes an `outputSchema`, and so do `list_destinations`, `list_uploads`, `get_upload` and `get_connection_hints`. Each is the shape the tool answers **or** an error's `{ code, detail, … }` (`anyOf`), so a client that validates results still gets each refusal as the tool's error. These 16 publish none (read their answer's JSON): `get_account`, `list_workspaces`, `get_selected_workspace`, `select_workspace`, `create_rtmp_destination`, `start_youtube_connection`, `list_youtube_channels`, `create_youtube_destination`, `update_destination`, `create_upload`, `complete_upload`, `discover_external_media`, `import_upload_from_url`, `delete_upload`, `get_billing_overview`, `get_streaming_usage`. Every error answers `structuredContent` `{ code, detail, … }`. See [Errors](/docs/api-reference/mcp/overview#errors). ## Account and workspaces | Tool | What it does | Scope | Destructive | | - | - | - | - | | `get_account` | Who the token belongs to: id, email, name, language and the default run length for new streams. | `read` | | | `list_workspaces` | Every workspace you belong to, your role in each, and which one is in use. | `read` | | | `get_selected_workspace` | Which workspace this connection works in, and why (selected, client header, or sign-in default). | `read` | | | `select_workspace` | Switches the workspace for every later call on this connection. Membership is checked on every request. | `read` | | ## Streams | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_streams` | Finds streams and their ids, with state and schedule. Filters by name and status. | `read` | | | `get_stream` | One stream's full configuration: state, quality, framerate, destinations and schedule. | `read` | | | `create_stream` | Creates a new stream, with an empty video playlist and an empty audio playlist. It starts once it has a destination plus either a video playlist with items or a published scene (`set_stream_scene`). | `write` | | | `update_stream` | Changes a stream's settings (name, quality, framerate, duration, storage, backup stream) and its YouTube broadcast settings. | `write` | | | `check_stream_readiness` | Says whether `start_stream` would work right now, and lists every blocking issue (not credits, and not whether an upload has finished processing). Changes nothing. | `read` | | | `start_stream` | Starts streaming now. Puts video in front of the public. Read `state` in its answer: `invalid` means refused, with `issues`; `preparing` means accepted. | `write` | | | `stop_stream` | Stops a live or starting stream. Nothing is deleted. Doesn't cancel a repeating schedule. | `write` | Yes | | `schedule_stream` | Sets the window a stream runs in, once, daily or weekly, and arms it. | `write` | | | `cancel_stream_schedule` | Disarms a schedule and clears any repeat. Doesn't stop a stream that's live. | `write` | | | `get_stream_live_stats` | How a live stream is doing on a connected YouTube channel: watch URL, status, viewers, views, likes. | `read` | | | `list_stream_destinations` | Where a stream publishes, whether each destination is enabled, and its delivery state while live. | `read` | | | `add_stream_destination` | Adds a destination to a stream. Up to 5 ([multistream](/docs/platform/multistream)), at most one YouTube channel. | `write` | | | `set_stream_destination_enabled` | Turns one destination off or back on without removing it. Turning off works while live, and takes that output off the air at once. | `write` | Yes | | `remove_stream_destination` | Takes a destination off a stream. Refused while live: turn it off instead. | `write` | | | `delete_stream` | Deletes a stream, its playlists and its schedule. Uploads stay. | `destructive` | Yes, confirm | ## Destinations | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_destinations` | The workspace's destinations (connected YouTube channels and custom RTMP), with ids and status. | `read` | | | `get_connection_hints` | How to connect a named platform: the methods, the exact ingest URL, and where to find the stream key. | `read` | | | `create_rtmp_destination` | Adds a destination from an RTMP URL and stream key (Twitch, Facebook, Kick, X, TikTok, YouTube by key, or any RTMP server). | `write` | | | `start_youtube_connection` | Starts connecting a Google account. Returns a consent link for you to open. | `write` | | | `list_youtube_channels` | The YouTube channels the workspace has Google consent for. | `read` | | | `create_youtube_destination` | Turns a connected YouTube channel into a destination, with managed broadcasts. | `write` | | | `update_destination` | Renames a destination, replaces a custom RTMP stream key, or archives and restores it. | `write` | | ## Uploads | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_uploads` | Finds media and their ids (`obj_…`). | `read` | | | `get_upload` | One file in detail: duration, resolution, framerate, audio and video, and import progress. | `read` | | | `create_upload` | Step 1 of uploading a local file: returns a link to `PUT` the bytes to. | `write` | | | `complete_upload` | Step 2: call it after the bytes are uploaded, to start processing. | `write` | | | `discover_external_media` | Looks at a media link and lists what can be imported, with quality options. | `read` | | | `import_upload_from_url` | Imports chosen media from a link (a page, a direct file link, or a file attached in the chat). | `write` | | | `delete_upload` | Deletes a file and everything made from it. Refused while a playlist or recent stream uses it. | `destructive` | Yes, confirm | ## Playlists | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_playlists` | A stream's two playlists (video and audio), or a scene's playlists. | `read` | | | `get_playlist` | A playlist's items in play order, what a live stream plays now, and its version. | `read` | | | `apply_playlist_operations` | Adds, removes and reorders items, or switches between sequential and shuffle, as one all-or-nothing batch. | `write` | | | `copy_playlist` | Copies one playlist's contents into an empty playlist of the same kind. | `write` | | | `create_playlist` | Creates an empty playlist owned by a scene, for one of its playlist sources. | `write` | | | `swap_live_playlist` | Puts an edited playlist on the air while the stream is live, without going off air. | `write` | Yes | ## Smart order [Smart order](/docs/smart-order) playlists play in an order written from a plain-language description. | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_playlist_orders` | Finds saved orders and their ids. | `read` | | | `get_playlist_order` | One order, and whether it can run over a given playlist. | `read` | | | `create_playlist_order` | Writes and checks an order from a description. Doesn't apply it. | `write` | | | `refine_playlist_order` | Changes an order with a follow-up instruction, as a new version. | `write` | | | `assign_playlist_order` | Makes a playlist play in an order. A live stream picks it up for its next items. | `write` | Yes | | `unassign_playlist_order` | Returns a playlist to its own order. The saved order is kept. | `write` | | ## Credits and usage | Tool | What it does | Scope | Destructive | | - | - | - | - | | `get_billing_overview` | Credit balance, burn rate and standing: how long streams can keep running. | `read` | | | `get_streaming_usage` | Minutes streamed, credits spent and sessions over a period, by quality. | `read` | | ## Scenes > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). The four `scene_*` tools read and change a scene's draft by path. See [Scenes](/docs/api-reference/mcp/overview#scenes) on the overview for the paths, revisions and the publish flow. | Tool | What it does | Scope | Destructive | | - | - | - | - | | `list_scenes` | The workspace's scenes, newest first, with the version last published. | `read` | | | `create_scene` | Creates a scene with an empty draft. | `write` | | | `rename_scene` | Renames a scene. | `write` | | | `scene_get` | Reads anything in the draft by path, or a published version. `as: "image"` returns a picture of a frame or layer with any problems found. | `read` | | | `scene_set` | Creates or changes one thing in the draft: a whole value or exact text edits. Checked as the studio checks it. Changing what exists needs the `revision` you read (`"*"` on purpose), or it answers `REVISION_REQUIRED`. | `write` | | | `scene_remove` | Deletes whole things from the draft, all or nothing. Each path needs the revision you read (`"*"` on purpose). | `write` | Yes | | `scene_probe` | Fetches a URL once to show what a table would read from it. Changes nothing. | `read` | | | `publish_scene` | Publishes the draft as it was at `draftRevision` (required) as the next version. `dryRun` only checks; on air it needs `confirm: true`; `STALE_PUBLISH` when someone published since your draft's base. | `write` | Yes | | `list_scene_versions` | Published versions, newest first: number, when, by whom. | `read` | | | `revert_scene` | Publishes an earlier version again as the next version. Needs `expectedVersion` (the latest published version you saw); on air it needs `confirm: true`. | `write` | Yes | | `discard_scene_draft` | Puts the draft back to the published version. Every change since is lost. Needs the `draftRevision` you reviewed; `dryRun` lists what would be lost first. | `destructive` | Yes | | `delete_scene` | Deletes a scene and all its versions. Refused while a stream plays it. Without `confirm` it names the scene, its versions and the streams that play it. | `destructive` | Yes, confirm | | `set_stream_scene` | Makes a stopped stream play a scene's published version, or go back to its playlist. | `write` | | | `get_scene_state` | What each stream playing the scene has on air: the frame (`frame`), the next one (`next`), controls and sources. | `read` | | | `control_scene` | Acts on air: takes a frame to air with a transition (`goFrame`), cues the next (`next`), sets a control, replaces a table's rows, skips a track. Send an `actionId` (a new uuid per action) to retry safely after `SCENE_TIMEOUT`: the same `actionId` applies at most once. | `write` | Yes | | `list_scene_inputs` | The scene's ingest keys (never the key itself), secret names and IP cameras. | `read` | | | `create_scene_ingest_key` | Gets the RTMP, RTMPS and SRT addresses and key for an encoder such as OBS. The key is shown once. | `write` | | | `revoke_scene_ingest_key` | Revokes an ingest key. The encoder using it is cut off at once. | `destructive` | Yes, confirm | | `set_scene_secret` | Saves an API key that a table sends. It's sealed and never shown again. | `write` | | | `delete_scene_secret` | Deletes a secret. Tables that use it stop getting it. | `destructive` | Yes, confirm | # Execute up to 20 REST operations. Source: https://streamloop.app/docs/api-reference/meta/execute-up-to-20-rest-operations https://api.streamloop.app/v1/openapi.json post /batch Validates the complete envelope before dispatch, then executes operations sequentially through their normal REST routes. Results preserve input order. An operation failure does not stop later operations, and the batch is not transactional. # Service status (liveness/health) — unauthenticated. Source: https://streamloop.app/docs/api-reference/meta/service-status-livenesshealth-—-unauthenticated https://api.streamloop.app/v1/openapi.json get /status # This OpenAPI 3.1 document — unauthenticated. Source: https://streamloop.app/docs/api-reference/meta/this-openapi-31-document-—-unauthenticated https://api.streamloop.app/v1/openapi.json get /openapi.json # Apply playlist operations Source: https://streamloop.app/docs/api-reference/playlists/apply-playlist-operations https://api.streamloop.app/v1/openapi.json post /playlists/{id}/operations Edits the playlist's draft: operations apply in order, each seeing what the earlier ones made, and a refused one writes nothing. addObjects takes upload ids (obj_…, the id of POST /uploads and GET /uploads) of completed uploads (uploaded true; else OBJECT_NOT_READY). A stream that isn't live plays the draft as it is when it starts; a live stream keeps playing what it started with until POST /playlists/{id}/publish. # Copy a playlist Source: https://streamloop.app/docs/api-reference/playlists/copy-a-playlist https://api.streamloop.app/v1/openapi.json post /playlists/{id}/copy Spec copy between two playlists the caller owns. Target must be empty. Caller pushes target via stream launch to actually play it. # Create a scene playlist Source: https://streamloop.app/docs/api-reference/playlists/create-a-scene-playlist https://api.streamloop.app/v1/openapi.json post /playlists Creates an empty playlist owned by a scene, for one of its playlist sources (a stream's own two playlists come with the stream). Fill it with POST /playlists/{id}/operations. # Get a playlist Source: https://streamloop.app/docs/api-reference/playlists/get-a-playlist https://api.streamloop.app/v1/openapi.json get /playlists/{id} Fetches an object given its ID. # List playlists Source: https://streamloop.app/docs/api-reference/playlists/list-playlists https://api.streamloop.app/v1/openapi.json get /playlists Every stream owns two playlists (video and audio); a scene owns the playlists its sources play. Filter by streamId or sceneId. # Publish a playlist Source: https://streamloop.app/docs/api-reference/playlists/publish-a-playlist https://api.streamloop.app/v1/openapi.json post /playlists/{id}/publish Puts the playlist's draft on its live stream, replacing what it plays without going off air. Only for a stream that is live: a stream that isn't plays its playlists as they are when it starts, so edits made before POST /streams/{id}/start need no publish (here they are 409 NOT_LIVE). expectedDraftVersion is the version you reviewed (the playlist's version). Asynchronous: poll GET /playlists/{id} until publishStatus is IDLE (done) or FAILED. Refusals, by the problem's code: STALE_DRAFT (409; a newer draft, the problem carries currentVersion: re-read and retry), EMPTY_PLAYLIST, BROKEN_ITEM (an item's upload is incomplete or removed; carries objectId), METADATA_PENDING (a video's width and height had not arrived within 40 s; retry), ORIENTATION_CHANGED, RECIPE_MISMATCH, NOT_LIVE (409), NOT_FOUND (404). # Quickstart Source: https://streamloop.app/docs/api-reference/quickstart Go from an API key to a live 24/7 stream with a handful of curl calls: create a stream, upload a video, add it to the playlist, add a destination and start. This walkthrough takes you from nothing to a running stream with `curl` and an [API key](/docs/api-reference/authentication). The flow is: create the stream, upload a video, add it to the stream's playlist, add a destination, and start. Set your key and workspace once, so the snippets stay short. An API key carries no workspace, so every request names one in `X-Workspace-Id` (see [Conventions](/docs/api-reference/conventions#workspaces)): ```bash theme={null} export SL_KEY="sl_your_key_here" ``` **1. Find your workspace** ```bash theme={null} curl https://api.streamloop.app/v1/workspaces \ -H "X-API-Key: $SL_KEY" ``` Pick a workspace from the list and keep its `id`: ```bash theme={null} export SL_WS="wksp_…" ``` A `401` means the key is missing, wrong or revoked. **2. Create the stream** `quality` is one of `q_720p`, `q_1080p`, `q_1440p`, `q_2160p`. `framerate` is `f_24`, `f_25`, `f_30` or `f_60`: ```bash theme={null} curl -X POST https://api.streamloop.app/v1/streams \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Content-Type: application/json" \ -d '{ "name": "Lofi radio", "quality": "q_1080p", "framerate": "f_30" }' ``` Note the stream `id` (`stream_…`). The stream comes with two empty playlists, one for video and one for audio. **3. Upload a video** Uploading a local file takes three calls: create the upload, `PUT` the bytes to a presigned URL, and mark it complete. ```bash theme={null} # 1. Create the upload record. Note its id (obj_…). curl -X POST https://api.streamloop.app/v1/uploads \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Content-Type: application/json" \ -d '{ "name": "my-video.mp4", "type": "video", "mimeType": "video/mp4" }' # 2. Get a presigned URL and upload the file to it. curl -X POST https://api.streamloop.app/v1/uploads/obj_…/upload-url \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" curl -X PUT -H "Content-Type: video/mp4" --upload-file my-video.mp4 "" # 3. Mark it complete, which starts processing. curl -X POST https://api.streamloop.app/v1/uploads/obj_…/complete \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" ``` `GET /uploads/{id}` shows when the file is ready. **4. Add it to the playlist** Find the stream's video playlist, then add the upload to it: ```bash theme={null} curl "https://api.streamloop.app/v1/playlists?streamId=stream_…" \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" curl -X POST https://api.streamloop.app/v1/playlists/pl_…/operations \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Content-Type: application/json" \ -d '{ "operations": [ { "addObjects": { "objectIds": ["obj_…"], "position": "END" } } ] }' ``` Use the playlist whose `kind` is `video`. All operations in one call succeed or fail together. **5. Add a destination** Create an RTMP destination from your platform's ingest URL and stream key (RTMP is the protocol live platforms use to receive video), then add it to the stream: ```bash theme={null} curl -X POST https://api.streamloop.app/v1/destinations \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Content-Type: application/json" \ -d '{ "name": "My channel", "rtmpUrl": "rtmp://a.rtmp.youtube.com/live2", "rtmpKey": "xxxx-xxxx-xxxx-xxxx" }' curl -X POST https://api.streamloop.app/v1/streams/stream_…/destinations \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" \ -H "Content-Type: application/json" \ -d '{ "destinationId": "dest_…" }' ``` A stream can send to up to 5 destinations ([multistream](/docs/platform/multistream)), at most one of them YouTube. **6. Check it's ready to go live** ```bash theme={null} curl https://api.streamloop.app/v1/streams/stream_…/readiness \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" ``` ```json theme={null} { "ready": true, "issues": [] } ``` If `ready` is `false`, fix the listed `issues` (for example, the video is still processing) and check again. **7. Start streaming** ```bash theme={null} curl -X POST https://api.streamloop.app/v1/streams/stream_…/start \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" ``` The stream is live within about a minute and loops your video 24/7. Stop it any time with `POST /streams/{id}/stop`. **8. Monitor it** On a connected YouTube channel, poll live stats while it runs: ```bash theme={null} curl https://api.streamloop.app/v1/streams/stream_…/live-stats \ -H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS" ``` `GET /streams/{id}/destinations` shows each destination's delivery state. > [!TIP] > > Prefer not to call endpoints in order? Connect an AI agent to the [MCP server](/docs/api-reference/mcp/overview) and describe what you want. It runs the same operations as tools. See [Use Streamloop with AI](/docs/use-with-ai). ## What's next > **Schedule instead of start** > > Call `POST /streams/{id}/schedule` to go live automatically, once, daily or weekly. > **[Play a scene](/docs/api-reference/scenes)** > > Build a live, designed scene with the Scenes API and put it on the stream with `PUT /streams/{id}/scene`. # Scenes API Source: https://streamloop.app/docs/api-reference/scenes Build, check, publish and run Streamloop scenes over HTTP: edit a scene's draft as resources at paths, get a picture of a frame, publish it, and put it on a stream. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). The Scenes API builds and runs [scene loops](/docs/scenes/overview) from your own code. It edits a scene the same way the studio and its [AI assistant](/docs/scenes/ai-assistant) do: every change is built and checked as the studio checks it, and studios open on the scene see it at once. Nothing reaches viewers until you publish. | | | | - | - | | Base URL | `https://api.streamloop.app/v1/scenes` | | OpenAPI document | `https://api.streamloop.app/v1/scenes/openapi.json` (the endpoint pages below are generated from it) | | Authentication | `X-API-Key` with `X-Workspace-Id`, or an OAuth bearer token. See [Authentication](/docs/api-reference/authentication). | It follows the same [conventions](/docs/api-reference/conventions) as the REST API: problem documents with a stable `code`, cursor pages, `Idempotency-Key`, and `RateLimit-*` headers (600 requests a minute). ## The model A **scene** (id `scn_…`) is what a stream plays when it isn't a video loop. Its **draft** holds resources, each at a path: | Path | What it is | | - | - | | `frame/` | One frame: a background and layers. One is on air at a time, and the stream cuts between them. Read and written as `{ name, description?, transition?, code }`, where `code` is its `…` element in JSX. | | `frame//` | One layer of a frame, with its props. | | `source//` | Something the scene reads: a camera (`rtsp`), an input (`rtmp`, `srt`), a `file`, a `playlist`, a `web` page, a table (`data`) or a formula over tables (`derived`). | | `control/` | A switch, text or choice the operator sets while the scene runs. | | `component/` | A reusable element (a lower third, a score bug) written in TSX. | | `script/show.ts`, `script/state.ts` | The scene's behaviour, and the shape of its state, in TypeScript. | | `tokens` | Named colours, fonts and sizes. | | `element/` | The building blocks (`Text`, `Image`, `Video`, `Rect`, `Stack`, `Ticker`, …) with every prop. Read-only. | `GET /v1/scenes/{id}/resources` lists the kinds. A path ending in `/*` lists one kind, for example `frame/*` or `element/*`. Each answer names the paths to read next in `children`. All resources go through four operations: | Operation | HTTP | | - | - | | Read | `GET /v1/scenes/{id}/resources/{path}` | | Create or replace | `PUT /v1/scenes/{id}/resources/{path}` (JSON, or the code alone as `text/jsx`, `text/tsx` or `text/typescript`) | | Change part | `PATCH /v1/scenes/{id}/resources/{path}`: exact text edits `[{ "old": "…", "new": "…" }]`, or a JSON merge patch | | Remove | `DELETE /v1/scenes/{id}/resources/{path}`, or `POST /v1/scenes/{id}/remove` for several at once | `POST /v1/scenes/{id}/probe` fetches a URL once, changing nothing, to show what a table would read from it. ## Walkthrough This builds a one-frame scene, looks at it, publishes it and puts it on a stream. Set your key and workspace first. `GET /v1/workspaces` lists your workspaces. ```bash theme={null} export SL_KEY="sl_your_key_here" export SL_WS="wksp_…" H=(-H "X-API-Key: $SL_KEY" -H "X-Workspace-Id: $SL_WS") ``` **1. Create a scene** ```bash theme={null} curl "${H[@]}" -X POST https://api.streamloop.app/v1/scenes \ -H "Content-Type: application/json" \ -d '{ "name": "Lobby" }' ``` The answer is `201` with the scene. Note its `id` (`scn_…`). Its `version` is `0`: nothing is published yet. ```bash theme={null} export SCN=https://api.streamloop.app/v1/scenes/scn_… ``` **2. Write a frame** Send the frame's code as JSX. The frame's id is its path, here `frame/intro`: ```bash theme={null} curl "${H[@]}" -X PUT "$SCN/resources/frame/intro" \ -H "Content-Type: text/jsx" \ --data-binary ' ' ``` A new resource answers `201`, with `revision` (also the `ETag` header), a `diff` of what changed, and `draftRevision`. If the code doesn't build or check, the answer is `422 CHECK_FAILED`, nothing changes, and `errors` lists every problem with its line. `GET "$SCN/resources/element/Text"` shows every prop `Text` takes, with an example. **3. Look at it** Get a picture of the frame as it goes on air: ```bash theme={null} curl "${H[@]}" "$SCN/resources/frame/intro?as=image" -o intro.png ``` With `Accept: image/png` (or no `Accept`) the answer is a PNG, at most 1280 px wide. With `Accept: application/json` you get the picture as a data URL plus what drew (`drawn`) and what's wrong (`issues`): text outside title-safe, overlapping or too small, or a bound table with no rows. Add `overlay=grid` or `overlay=ids` to line things up, and `at=` to draw a moment after the frame goes on air. **4. Check, then publish** Publishing takes the `draftRevision` of the draft you checked. It's required: `GET /v1/scenes/{id}` and every resource answer carry the current one, also as the `Draft-Revision` header. So you publish exactly the draft you saw, even if someone edits it meanwhile. A dry run checks the whole scene and lists what would change, publishing nothing: ```bash theme={null} curl "${H[@]}" -X POST "$SCN/publish" \ -H "Content-Type: application/json" \ -d '{ "draftRevision": "…", "dryRun": true }' ``` Then publish: ```bash theme={null} curl "${H[@]}" -X POST "$SCN/publish" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: publish-lobby-1" \ -d '{ "draftRevision": "…" }' ``` ```json theme={null} { "version": 1, "publishedAt": "2026-10-07T12:00:00Z", "changes": ["add frame/intro"], "notes": [] } ``` `changes` lists what changed since the previous version, as resource paths. If the draft changed after your `draftRevision`, those edits aren't published: the answer says so with `"stale": true` and a note. A draft equal to what's published makes no version (`"unchanged": true`). If someone published after the version your draft is based on, publishing it would take their version back. The answer is `409 STALE_PUBLISH` with `publishedVersion`, `basedOn` and `reverted` (what would be undone). Read the draft again and publish its new `draftRevision`, or send `"overwritePublished": ` to replace that version on purpose. If that version is a revert (`publishedVersionIsRevert: true`), reading the draft again doesn't help: send `overwritePublished` to publish your draft over it, or discard the draft (`POST /discard`) to keep the reverted version. **5. Put it on a stream** Use the REST API to make a stopped stream play the scene's published version, then start it: ```bash theme={null} curl "${H[@]}" -X PUT https://api.streamloop.app/v1/streams/stream_…/scene \ -H "Content-Type: application/json" \ -d '{ "sceneId": "scn_…" }' curl "${H[@]}" -X POST https://api.streamloop.app/v1/streams/stream_…/start ``` The stream opens on the scene's first frame. While the stream runs, `PUT …/scene` answers `409 STREAM_LIVE`: stop it first. `{ "sceneId": null }` goes back to the stream's video playlist. ## On air Once a scene is on air: * **Publishing changes what viewers see at once.** So `POST /publish` (and `POST /revert`) on a scene a running stream plays answers `409 CONFIRM_REQUIRED`, naming the streams. Send it again with `"confirm": true` to accept that. `confirm` always means "I accept what the refusal named". * `GET /v1/scenes/{id}/live` says what's on air on each stream: under `onAir`, the frame on air (`frame`), the next one cued (`next`), control values and source status. * `POST /v1/scenes/{id}/live` acts like an operator, with the same names as the MCP's `control_scene`: `goFrame` with `target` (take `frame/` to air, with a transition such as `fade`), `next` (cue a frame), `setControl` with `controlId` and `value`, `setData` with `sourceId` and `rows` (replace a table's rows) and `skip` with `sourceId` (a playlist source's next item); `streamId` when several streams play it. A transition is a kind (`cut`, `fade`, `slide`, `wipe`, `dip`) or `{ "kind": …, "ms": … }` with `ms` a whole number of 0 or more; anything else, or a `streamId` that isn't a string, is `422 INVALID_INPUT`, said before what's published is looked at. A `504 SCENE_TIMEOUT` may or may not have been applied: send an `Idempotency-Key` (one per action) and retry with the same key and body, and it applies at most once. Without one, don't retry a `skip` or a button blind: read `GET …/live` first. The action is checked first against what's published (a frame, control, table or playlist source that isn't there, or a toggle given `"yes"`, is `422 INVALID_INPUT`); then it answers `409 SCENE_OFFLINE` when no stream plays the scene. * Any resource (or picture) reads from a published version with `?version=` or `?version=published`; the answer carries a `Scene-Version` header and no `Draft-Revision`. * `GET /versions` lists published versions; `POST /revert` with `{ "version": n, "expectedVersion": }` publishes an old one again as the next version (`409 STALE_PUBLISH` if someone published since; after a revert, publishing the draft asks for `overwritePublished`, or discard the draft to match); `POST /discard` with `{ "draftRevision": "…" }` puts the draft back to what's published and makes that version the draft's base, so the next unchanged publish answers `unchanged` and an edited one is the next version. Add `"dryRun": true` first to see what would be lost (`lost`). If the draft changed after your `draftRevision`, nothing is discarded (`409 CONFLICT`). Both take the precondition as `If-Match` instead (`""` on discard, `""` on revert): then a mismatch is `412` with the same code and details. Live inputs and keys: * `POST /v1/scenes/{id}/ingest-keys` with `{ "sourceId": "…" }` gives an encoder such as OBS its RTMP, RTMPS and SRT addresses for an `rtmp` or `srt` source. The key is in that answer only. * `PUT /v1/scenes/{id}/secrets/{name}` saves an API key that a table sends as `$secret:`. It's sealed and never shown again. * `/v1/scenes/{id}/cameras` adds and tests IP cameras. See [Cameras](/docs/scenes/cameras). ## Concurrency Every resource has a `revision`, sent as its `ETag`. Use it so you never overwrite someone's newer edit, whether from another script or a person in the studio: | Header | Effect | | - | - | | `If-Match: ` on `PUT`, `PATCH`, `DELETE` | Refused with `412 PRECONDITION_FAILED` if the resource changed since or no longer exists. The problem carries `currentRevision` (`null` when it's gone). A layer also accepts the revision of the frame it's in; a Stack's or Group's revision changes when anything below it changes, so a stale one can't remove what was added there since. A comma-separated list matches if any tag is current. The comparison is strong: a weak `W/"…"` tag never matches. | | `If-Match: *` | The resource must exist. | | `If-None-Match: *` on `PUT` | Create only: `412` if it already exists. | | `If-None-Match: ` on `GET` | `304 Not Modified` if nothing changed. | On `412`, read the resource again, reapply your change and retry. ## Idempotency Send an `Idempotency-Key` header on `POST` and `PATCH` to retry safely. The first answer to a key (per caller, method and path) is replayed for 24 hours, with `Idempotency-Replayed: true`; a retry that arrives while the first request is still running waits for its answer instead of running again. Reusing a key with a different request body is `422 IDEMPOTENCY_KEY_REUSED`. Use it on `publish`, `revert` and `live` in particular: after a `504 SCENE_TIMEOUT`, retry with the same key. ## Errors Errors are [RFC 7807](https://www.rfc-editor.org/rfc/rfc7807) problem documents (`application/problem+json`) with a stable `code`: ```json theme={null} { "type": "about:blank", "title": "Unprocessable Entity", "status": 422, "code": "CHECK_FAILED", "detail": "…", "errors": [ { "line": 2, "path": "frame/intro", "message": "…" } ] } ``` | Status | Codes | | - | - | | 400 | `BAD_REQUEST` (a body or query that can't be parsed), `WORKSPACE_REQUIRED` | | 401 | `UNAUTHENTICATED` | | 403 | `INSUFFICIENT_SCOPE`, `WORKSPACE_FORBIDDEN` | | 404 | `NOT_FOUND` | | 409 | `CONFLICT` (with `currentRevision`), `EDIT_NO_MATCH`, `EDIT_AMBIGUOUS`, `IN_USE`, `CONFIRM_REQUIRED`, `STALE_PUBLISH` (with `publishedVersion`, `basedOn`, `reverted`), `DRAFT_NOT_SAVED`, `NOT_PUBLISHED`, `SCENE_IN_USE`, `SCENE_OFFLINE`, `INGEST_KEY_REVOKED` | | 412 | `PRECONDITION_FAILED` (with `currentRevision`); a stale `If-Match` on `publish`, `discard`, `revert` or `DELETE /v1/scenes/{id}` keeps its code (`STALE_PUBLISH`, `CONFLICT`) with this status. A header and a body precondition that disagree are `422 INVALID_INPUT` | | 413 | `TOO_LARGE` | | 415 | `UNSUPPORTED_MEDIA_TYPE`: a body that is neither JSON nor code, or code sent as the wrong kind for its path | | 422 | `INVALID_INPUT` (a field, value or query parameter that doesn't fit; `detail` names it), `READ_ONLY` (a field or sub-path that can't be changed that way — a layer's type, children or repeat, a table's rows), `CHECK_FAILED`, `SCENE_INVALID` (with `errors`), `IDEMPOTENCY_KEY_REUSED`, `CAMERA_INVALID`, `SECRET_MISSING` | | 429 | `RATE_LIMITED` | | 5xx | `INTERNAL`, `UNAVAILABLE`, `SCENE_TIMEOUT`: retry with backoff | The full list is in the `Problem` schema of the [OpenAPI document](https://api.streamloop.app/v1/scenes/openapi.json). ## Scopes With an OAuth token, every `GET` and `probe` needs `streamloop:read`. Edits, publishing and live control need `streamloop:write`. Deleting a scene, discarding its draft, and revoking or deleting an ingest key, camera or secret need `streamloop:destructive`. > [!TIP] > > Prefer to describe the scene in words? Connect an AI agent to the [MCP server](/docs/api-reference/mcp/overview). Its `scene_get`, `scene_set`, `scene_remove` and `scene_probe` tools are these operations. # Change part of a resource Source: https://streamloop.app/docs/api-reference/scenes-api/draft/change-part-of-a-resource https://api.streamloop.app/v1/scenes/openapi.json patch /scenes/{id}/resources/{path} Either text replacements in its code — `[{ "old": "…", "new": "…" }]`, each `old` matching exactly once (code is stored formatted: copy `old` from what GET answered; 409 EDIT_NO_MATCH quotes the closest line) — or a JSON merge patch of its fields (`application/merge-patch+json` or JSON; null removes a field). One layer changes alone at its own path (`frame/intro/title` with `{ "props": { … } }`). Checked like PUT. # Create or replace a resource Source: https://streamloop.app/docs/api-reference/scenes-api/draft/create-or-replace-a-resource https://api.streamloop.app/v1/scenes/openapi.json put /scenes/{id}/resources/{path} The whole resource, as GET answers it: a `frame/` `{ name, description?, transition?, code }` (code: its `…` element in JSX) or `{ name, root }` (JSON); a component `{ description, props, tsx }`; a source its fields. Or the code alone, with `Content-Type: text/jsx` (a `frame/`), `text/tsx` (a component) or `text/typescript` (the script). Built and checked as the studio checks it: a refused change changes nothing and answers every problem (422 CHECK_FAILED, `errors`). `If-Match`: the revision you read; 412 PRECONDITION_FAILED (with `currentRevision`) if it changed since (`*`: it must exist); `If-None-Match: *` creates only. A new resource answers 201 with its Location. Code sent as the wrong kind for its path, or a body that is neither JSON nor code (`application/xml`), is 415. # List what a scene holds Source: https://streamloop.app/docs/api-reference/scenes-api/draft/list-what-a-scene-holds https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/resources The kinds of resource (frame, source, control, component, script, tokens, element), each with what it is and its paths. # Look at a URL before making a table of it Source: https://streamloop.app/docs/api-reference/scenes-api/draft/look-at-a-url-before-making-a-table-of-it https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/probe Fetches it once, changing nothing: status, content type, an outline of the JSON or the page's text, and the rows a table would get with `connector`, `pick` and `headers`. With `connector: "mcp"` the URL is an MCP server: without `tool` its tools are listed, with `tool` and `args` one tool's result and its rows (a tool that writes is refused, WRITE_TOOL; a server that wants a sign-in answers NEEDS_SIGNIN). # Read a resource Source: https://streamloop.app/docs/api-reference/scenes-api/draft/read-a-resource https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/resources/{path} A resource with its `revision` (also the `ETag`) and `children` (the paths to read next); a type (`frame`, `source/data`, `element/Text`: what it takes, with an example); or a list (a path ending in `/*`). `as=image` on a `frame/` or a layer answers a PNG of it as it goes on air, drawn by the channel's renderer — or, with `Accept: application/json`, the picture as a data URL with what drew (`drawn`) and what is wrong (`issues`: outside title-safe, overlapping, too small, a bound table with no rows). `version` reads a published version instead of the draft. `If-None-Match` with the ETag you hold answers 304 when nothing changed. # Remove a resource Source: https://streamloop.app/docs/api-reference/scenes-api/draft/remove-a-resource https://api.streamloop.app/v1/scenes/openapi.json delete /scenes/{id}/resources/{path} A frame, layer, source, component or control of the draft (the scene itself: DELETE /scenes/{id}). Refused (409 IN_USE) while something else uses it, naming what. `If-Match` guards it like PUT. # Remove several resources at once Source: https://streamloop.app/docs/api-reference/scenes-api/draft/remove-several-resources-at-once https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/remove All or nothing: what uses one of them is fine when it goes too. `revisions` guards each path like If-Match. # Add a camera Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/add-a-camera https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/cameras An RTSP camera on a public address (422 CAMERA_PRIVATE_ADDRESS otherwise) for an `rtsp` source of the draft. # Change a camera Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/change-a-camera https://api.streamloop.app/v1/scenes/openapi.json put /scenes/{id}/cameras/{camera} A password left out keeps the saved one; an empty one removes it. # Create an ingest key Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/create-an-ingest-key https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/ingest-keys For an `rtmp` or `srt` source of the draft: where an encoder (OBS, vMix) sends. The key is in this answer only. # Delete a secret Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/delete-a-secret https://api.streamloop.app/v1/scenes/openapi.json delete /scenes/{id}/secrets/{name} # List cameras Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/list-cameras https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/cameras In the order they were added. # List ingest keys Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/list-ingest-keys https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/ingest-keys Newest first. # List secrets Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/list-secrets https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/secrets By name; names only: a value is never returned. A table names one as `$secret:` in a header or a URL's query. # Remove a camera Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/remove-a-camera https://api.streamloop.app/v1/scenes/openapi.json delete /scenes/{id}/cameras/{camera} # Replace an ingest key Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/replace-an-ingest-key https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/ingest-keys/{key}/rotate A new key for the same source; the old one stops working. # Revoke an ingest key Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/revoke-an-ingest-key https://api.streamloop.app/v1/scenes/openapi.json delete /scenes/{id}/ingest-keys/{key} An encoder using it is cut off. # Save a secret Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/save-a-secret https://api.streamloop.app/v1/scenes/openapi.json put /scenes/{id}/secrets/{name} An API key or token a table sends (sealed at rest; at most 100 per scene). A table reads it as `$secret:`. # Test a camera Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/test-a-camera https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/cameras/test Connects once: refused, unreachable, timeout, auth, not_found or unsupported, or the codec, size and frame rate, with a snapshot (none for H.265). # The camera's last snapshot Source: https://streamloop.app/docs/api-reference/scenes-api/inputs/the-cameras-last-snapshot https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/cameras/{camera}/snapshot # Act on air Source: https://streamloop.app/docs/api-reference/scenes-api/live/act-on-air https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/live Take a `frame/` to air (`goFrame`, with a transition), cue one so the take is instant (`next`), set a control (`setControl`), replace a table's rows (`setData`), or move a playlist source on (`skip`). `streamId` may be left out when one stream plays the scene. Checked first, in this order: the body (422 INVALID_INPUT: an unknown op, a field the op doesn't take, a missing target or controlId, a transition that isn't one of its kinds or has an `ms` that isn't a whole number ≥ 0 or a field besides kind and ms, a `streamId` that isn't a string; the op from before frames, goScene, is refused naming goFrame), then against the published version (422 INVALID_INPUT: a `frame/`, control, table or playlist source it doesn't have, or a control value of the wrong type — a toggle takes true or false; 409 NOT_PUBLISHED when nothing is published); only then 409 SCENE_OFFLINE when nothing plays it. A 504 SCENE_TIMEOUT may or may not have been applied. Send an `Idempotency-Key` (one per action, kept by you): a repeat with the same key and body is applied at most once — its first answer is replayed (`Idempotency-Replayed: true`), and after a SCENE_TIMEOUT the retry reaches the stream with the same key, which applies it only if the first did not arrive. Without a key, do not retry an action that is not idempotent (skip, a button) blind: read GET /scenes/{id}/live first. # What is on air Source: https://streamloop.app/docs/api-reference/scenes-api/live/what-is-on-air https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/live Each stream that plays the scene (by stream id): its state, and in `onAir` what plays there (online, `frame`: the id of the `frame/` on air, `next`: the one cued; `scene` says the same as `frame` and is deprecated, kept for one release). # Discard the draft Source: https://streamloop.app/docs/api-reference/scenes-api/publishing/discard-the-draft https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/discard Puts the draft back to what is published: every change since is lost (studios open on the scene can still undo it). `draftRevision` is required (in the body, or as If-Match; sent both ways, they must name the same draft, else 422 INVALID_INPUT): the draft you reviewed; if it changed after that, nothing is discarded (code CONFLICT with `currentRevision`: read it again; status 409 for the body's draftRevision, 412 for If-Match). From then on the draft is based on that version, a revert too: publishing it unchanged answers `unchanged`. `dryRun: true` says what would be lost (`lost`, as resource paths) and discards nothing. 409 NOT_PUBLISHED when nothing is published. # List published versions Source: https://streamloop.app/docs/api-reference/scenes-api/publishing/list-published-versions https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id}/versions Newest first; a publish made while you page doesn't shift the pages. Read one with `GET /scenes/{id}/resources/{path}?version=`. # Publish an older version again Source: https://streamloop.app/docs/api-reference/scenes-api/publishing/publish-an-older-version-again https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/revert Its document becomes the next version (the draft is untouched: publishing the draft later is refused with STALE_PUBLISH until you send overwritePublished, and discard makes the draft this version too). `expectedVersion` is required — the latest published version you saw (`version` in GET /scenes/{id}), or send it as `If-Match: ""` (sent both ways, they must be the same number, else 422 INVALID_INPUT); if someone published since — found up front or by a revert that lands first — nothing changes: STALE_PUBLISH (status 409 for expectedVersion, 412 for If-Match) with `publishedVersion` and what the revert would also take off air (`reverted`). On air, it is refused with CONFIRM_REQUIRED like a publish: send `confirm: true` to accept that. # Publish the draft Source: https://streamloop.app/docs/api-reference/scenes-api/publishing/publish-the-draft https://api.streamloop.app/v1/scenes/openapi.json post /scenes/{id}/publish Publishes the draft exactly as it was at `draftRevision` (required: in the body, or as `If-Match: ""`; sent both ways, they must name the same draft, else 422 INVALID_INPUT; every draft answer and GET /scenes/{id} carry the current one), whatever was edited since. It is checked as the channel loads it — the document check, and the type check of `script/show.ts`, `script/state.ts`, every `frame/` and component — and becomes the next version. - When the draft changed after that revision but nobody published since, the answer says so: `stale: true` and a note (those edits are not in this version; they are still in the draft). - When a version was published after the one that revision's draft was based on, publishing it would take that version back: 409 STALE_PUBLISH (412 when the draftRevision came as If-Match) names `publishedVersion`, `basedOn` and what would be `reverted`. Read the draft again, or send `overwritePublished: ` to replace it anyway. When that version is a revert (`publishedVersionIsRevert: true`), reading again changes nothing: send `overwritePublished` to publish the draft over it, or discard the draft to keep the reverted version. Two publishes at once are answered in turn: the second is `unchanged` at the first one's version when it publishes the same draft, else that STALE_PUBLISH. - A stream that has the scene on air plays the new version at once: 409 CONFIRM_REQUIRED names the streams; send `confirm: true` to accept that. - A draftRevision no answer about this scene gave is 422 INVALID_INPUT in the body, 412 PRECONDITION_FAILED as If-Match; one with edits the server doesn't have yet is 409 DRAFT_NOT_SAVED. A draft equal to what is published makes no version (`unchanged: true`). `dryRun: true` checks it and lists what would change, publishing nothing. Refused with every problem (422 SCENE_INVALID, `errors`, each with its `path` and line for a type error). The answer lists the `changes` as resource paths and `notes` worth knowing. # Create a scene Source: https://streamloop.app/docs/api-reference/scenes-api/scenes/create-a-scene https://api.streamloop.app/v1/scenes/openapi.json post /scenes A new scene, nothing published. Its draft starts empty — build it with `PUT /scenes/{id}/resources/frame/{frame}` — or, to import one, from `draft`: a whole scene document in the studio's format. A name is 1 to 120 characters. # Delete a scene Source: https://streamloop.app/docs/api-reference/scenes-api/scenes/delete-a-scene https://api.streamloop.app/v1/scenes/openapi.json delete /scenes/{id} For good, with its versions, ingest keys and cameras. Refused (409 SCENE_IN_USE) while a stream points at it. With `If-Match` (the ETag GET answered), refused with 412 PRECONDITION_FAILED (with `currentRevision`) if the scene changed since (renamed or published): checked before anything is revoked, and again as the scene is deleted; a weak tag never matches. # Get a scene Source: https://streamloop.app/docs/api-reference/scenes-api/scenes/get-a-scene https://api.streamloop.app/v1/scenes/openapi.json get /scenes/{id} Its name, the version last published, the streams that play it, and `draftRevision`: the draft as it is now, what publish and discard take (also the `Draft-Revision` header). The `ETag` changes with its name and published version: send it as `If-Match` to rename only what you read. What is published reads as resources: `GET /scenes/{id}/resources/{path}?version=published`. # List scenes Source: https://streamloop.app/docs/api-reference/scenes-api/scenes/list-scenes https://api.streamloop.app/v1/scenes/openapi.json get /scenes The workspace's scenes, newest first (by when each was made, so an edit made while you page never moves one between pages). # Rename a scene Source: https://streamloop.app/docs/api-reference/scenes-api/scenes/rename-a-scene https://api.streamloop.app/v1/scenes/openapi.json patch /scenes/{id} A name is 1 to 120 characters. With `If-Match` (the ETag GET answered), refused with 412 PRECONDITION_FAILED if the scene changed since (edited, renamed or published), checked as it is written: of two renames with one ETag, one lands; a weak tag never matches. # Add a destination to a stream Source: https://streamloop.app/docs/api-reference/streams/add-a-destination-to-a-stream https://api.streamloop.app/v1/openapi.json post /streams/{id}/destinations Adds one of your destinations (GET /destinations) to the stream, enabled; returns the stream's destinations. Up to 5 per stream, one YouTube channel at most; a second destination needs multistream on the workspace (403 MULTISTREAM_NOT_ENABLED). Refused while the stream is live (409 STREAM_LIVE), and for a destination the stream's quality cannot reach (422 DESTINATION_INCOMPATIBLE, with reason). # Cancel a stream's schedule Source: https://streamloop.app/docs/api-reference/streams/cancel-a-streams-schedule https://api.streamloop.app/v1/openapi.json delete /streams/{id}/schedule Cancel a stream's schedule, including any daily or weekly repeat; the stream returns to draft. # Check stream readiness Source: https://streamloop.app/docs/api-reference/streams/check-stream-readiness https://api.streamloop.app/v1/openapi.json get /streams/{id}/readiness What would stop POST /streams/{id}/start, changing nothing: ready, and each blocking issue (code, error, suggestion; destinationId when it is about one destination). It checks that quality and framerate are set; that at least one destination is enabled and not archived, supports the stream's quality, and for YouTube has its broadcast settings (title, made-for-kids) and a connected channel; that the video playlist has items, and that there is an audio playlist when a video in it has no sound; that the playlists' media fits the stream's included storage (else set storageBackend to the large tier with PATCH /streams/{id}); and, for a stream that plays a scene, that the scene is published and valid and the stream is at most 1080p60. It does not check credits or YouTube live eligibility (start does), nor whether uploads have finished processing: a playlist only takes completed uploads, and their metadata (see GET /uploads/{id}) need not have arrived yet. # Create a stream Source: https://streamloop.app/docs/api-reference/streams/create-a-stream https://api.streamloop.app/v1/openapi.json post /streams Creates a stream with its two empty playlists (video and audio). Add where it publishes with POST /streams/{id}/destinations and when it runs with POST /streams/{id}/schedule. # Delete a stream Source: https://streamloop.app/docs/api-reference/streams/delete-a-stream https://api.streamloop.app/v1/openapi.json delete /streams/{id} # Enable or disable a stream's destination Source: https://streamloop.app/docs/api-reference/streams/enable-or-disable-a-streams-destination https://api.streamloop.app/v1/openapi.json patch /streams/{id}/destinations/{destinationId} Stops or resumes sending to one destination without removing it. Disabling works while the stream is live, except for its last enabled destination (409 LAST_DESTINATION); enabling is refused while live (409 STREAM_LIVE). # Get a stream Source: https://streamloop.app/docs/api-reference/streams/get-a-stream https://api.streamloop.app/v1/openapi.json get /streams/{id} Fetches an object given its ID. # Get live stream stats Source: https://streamloop.app/docs/api-reference/streams/get-live-stream-stats https://api.streamloop.app/v1/openapi.json get /streams/{id}/live-stats Fetches an object given its ID. # List a stream's destinations Source: https://streamloop.app/docs/api-reference/streams/list-a-streams-destinations https://api.streamloop.app/v1/openapi.json get /streams/{id}/destinations Where the stream publishes, in the order they were added: each destination, whether it is enabled, and its delivery state while the stream runs. # List streams Source: https://streamloop.app/docs/api-reference/streams/list-streams https://api.streamloop.app/v1/openapi.json get /streams List the streams you own. Supply the ids parameter to fetch a specific set; otherwise the full collection is returned, cursor-paginated. # Put a scene on a stream Source: https://streamloop.app/docs/api-reference/streams/put-a-scene-on-a-stream https://api.streamloop.app/v1/openapi.json put /streams/{id}/scene Makes the stream play a scene (its published version; the Scenes API builds and publishes it) instead of its video playlist; sceneId null goes back to the playlist. The scene must be in the stream's workspace (404 NOT_FOUND). Refused while the stream runs (409 STREAM_LIVE): stop it, set the scene, start it. # Remove a destination from a stream Source: https://streamloop.app/docs/api-reference/streams/remove-a-destination-from-a-stream https://api.streamloop.app/v1/openapi.json delete /streams/{id}/destinations/{destinationId} Takes the destination off the stream (the destination itself stays); returns the stream's remaining destinations. Refused while the stream is live (409 STREAM_LIVE): disable it instead. # Schedule a stream Source: https://streamloop.app/docs/api-reference/streams/schedule-a-stream https://api.streamloop.app/v1/openapi.json post /streams/{id}/schedule Sets the window the stream runs in and arms it. Times must be RFC3339 with an explicit UTC offset. repeat=daily re-arms the window +24h and repeat=weekly +7 days (or on the UTC weekdays in days); both require scheduleEndAt and a window shorter than 24h, while a one-off (the default) may be open-ended. days are the UTC weekdays of the start instant, so a 9pm Monday Eastern window repeats on TUESDAY. # Start a stream Source: https://streamloop.app/docs/api-reference/streams/start-a-stream https://api.streamloop.app/v1/openapi.json post /streams/{id}/start Starts the stream now. Allowed from draft, scheduled (it starts now instead of at its window), stopped, failed and invalid; any other state is 409 STREAM_ILLEGAL_TRANSITION. It first runs the checks GET /streams/{id}/readiness runs, plus a YouTube live-eligibility check; when one fails the answer is still 200, with the stream in state invalid (GET /streams/{id}/readiness says why; fix it and start again). 422 STREAM_GUARD_FAILED (the problem also carries reason and cause) is a refusal at the moment of starting. The stream plays its playlists as they are when it starts, so edits made while it is not live need no publish. Answers once the start is accepted (state preparing): it is live within about a minute, longer while media is prepared. # Stop a stream Source: https://streamloop.app/docs/api-reference/streams/stop-a-stream https://api.streamloop.app/v1/openapi.json post /streams/{id}/stop Stop the stream. Works from any running or scheduled state; the stream stops publishing and becomes inactive. # Update a stream Source: https://streamloop.app/docs/api-reference/streams/update-a-stream https://api.streamloop.app/v1/openapi.json patch /streams/{id} Changes a stream's settings. Its destinations, schedule and scene have their own routes. # Create an upload Source: https://streamloop.app/docs/api-reference/uploads/create-an-upload https://api.streamloop.app/v1/openapi.json post /uploads Creates an upload record for a direct single-PUT transfer. Its id (obj_…) is the media id everywhere: what POST /playlists/{id}/operations takes in addObjects.objectIds and what a playlist's items (objectID) and liveItems (objectId) name. name needs a file extension; mimeType must suit type (video, audio or image) and is the Content-Type the PUT must send. A file may be at most 15 GB, but one PUT carries at most 5 GB, so import a bigger file with POST /uploads/from-link instead; a fileSize given here is checked now (422 FILE_TOO_LARGE), and the stored size is checked again on complete. A name without an extension or a mimeType that doesn't suit type is 422 INVALID_INPUT. Next: POST /uploads/{id}/upload-url, PUT the bytes to that URL, then POST /uploads/{id}/complete. # Generate a presigned upload URL Source: https://streamloop.app/docs/api-reference/uploads/generate-a-presigned-upload-url https://api.streamloop.app/v1/openapi.json post /uploads/{id}/upload-url Answers a JSON string: a presigned storage URL, valid for 11 hours. Send the whole file to it in one HTTP PUT, the bytes as the body, with Content-Type exactly the upload's mimeType (it is part of the signature: another value is refused by storage with 403) and no Streamloop credentials. Storage answers 200 with no body; then POST /uploads/{id}/complete. Ask again for a fresh URL after it expires. The answer is never null: an upload already completed is 409 UPLOAD_ALREADY_COMPLETED, and a removed one 422 UPLOAD_REMOVED. # Get an upload Source: https://streamloop.app/docs/api-reference/uploads/get-an-upload https://api.streamloop.app/v1/openapi.json get /uploads/{id} One upload (obj_…). There is no separate processing state: uploaded turns true on POST /uploads/{id}/complete (or when an import finishes), and the media's facts (duration, width, height, fps, hasVideo, hasAudio) arrive a few seconds to about half a minute later; they are null until then. A playlist takes the upload as soon as uploaded is true; publishing a live playlist waits up to 40 s for a video's width and height (else 422 METADATA_PENDING, retry). An import shows its progress in externalSource.state; externalSource is null for a direct upload. # Import uploads from a link Source: https://streamloop.app/docs/api-reference/uploads/import-uploads-from-a-link https://api.streamloop.app/v1/openapi.json post /uploads/from-link Imports media from a link (YouTube, a cloud drive, a direct file URL…): one upload per selection, downloaded in the background; answers 202 with the new uploads. Each selection names an assetId and a qualityId from discovery, which this REST API does not offer: ask GraphQL (POST https://api.streamloop.app/graphql, the same X-API-Key) with `query { externalLinkContents(url: "") { provider items { id name durationSeconds qualities { id label height } } } }` — an item's id is the assetId, a quality's id the qualityId — or use the MCP tool discover_external_media. Then poll GET /uploads/{id}: externalSource.state.status goes PENDING, RESOLVING, DOWNLOADING, UPLOADING, then COMPLETED (uploaded true) or FAILED (state.error says why). The Location header is the first upload's; the body has every id. # List uploads Source: https://streamloop.app/docs/api-reference/uploads/list-uploads https://api.streamloop.app/v1/openapi.json get /uploads # Mark an upload complete Source: https://streamloop.app/docs/api-reference/uploads/mark-an-upload-complete https://api.streamloop.app/v1/openapi.json post /uploads/{id}/complete Call after the PUT to the upload URL succeeded. Checks the stored file's size against the cap (over it: 422 FILE_TOO_LARGE, and the stored file is deleted), sets uploaded true and starts reading the media's facts in the background (see GET /uploads/{id}). The upload can go into a playlist at once. Completing again is harmless; completing before the PUT finished is refused (nothing is stored yet). # Remove an upload Source: https://streamloop.app/docs/api-reference/uploads/remove-an-upload https://api.streamloop.app/v1/openapi.json delete /uploads/{id} Removes the upload from the media library for good: its stored bytes and every file derived from it are deleted, and it no longer lists. An upload that never finished (no complete after the PUT, an import that failed) is dropped too: nothing was stored. Refused with 409 IN_USE while a playlist or a recent stream still uses it (remove it from those playlists first), 404 NOT_FOUND, 422 UNSUPPORTED_TYPE for an image. Removing an already-removed upload succeeds. # Remove uploads Source: https://streamloop.app/docs/api-reference/uploads/remove-uploads https://api.streamloop.app/v1/openapi.json post /uploads/remove Removes up to 100 uploads from the media library, as DELETE /uploads/{id} does for one (an upload that never finished is left in place here: UPLOAD_INCOMPLETE; DELETE it). The call succeeds as a whole: removedIds lists what is gone (including ids that already were), refused lists each id left in place with its reason (NOT_FOUND, IN_USE, UNSUPPORTED_TYPE, UPLOAD_INCOMPLETE). When one id failed for a passing reason the whole call is 503 UNAVAILABLE naming it: what was removed stays removed, so send the call again. More than 100 ids is a 422. # How billing works Source: https://streamloop.app/docs/billing Streamloop runs on credits you spend only while a stream is live. Pay as you go or choose an optional monthly plan. See the price per resolution, extra destinations, scenes and storage. Streamloop runs on **credits**. You spend them only while a stream is live, and they never expire. Buy credits as you go, or choose an optional monthly plan that adds credits every month. Credits belong to your [workspace](/docs/workspaces-and-team), so everyone in it streams from the same balance. ## What you pay for All charges are metered, so you pay only for what you use: 1. **Streaming time**: the time your loop is on air. This is the main cost. 2. **Video preparation**: a one-time fee of about **\$0.005 per minute of encoding time** to convert each video into a stream-ready format. You're charged only when the conversion succeeds. 3. **Options you turn on**: extra destinations, extra storage and scene rendering. Each is described below. ### Streaming time The rate depends on the **resolution** and **frame rate** of the loop. You're billed by the minute while you're live. The figures below are the cost of a stream running **nonstop for 30 days**: | Resolution | 24 fps | 25 fps | 30 fps | 60 fps | | - | - | - | - | - | | 720p | \$4.00 | \$4.17 | \$5.00 | \$7.50 | | 1080p | \$8.00 | \$8.33 | \$10.00 | \$15.00 | | 1440p | \$16.00 | \$16.67 | \$20.00 | \$30.00 | | 4K (2160p) | \$24.00 | \$25.00 | \$30.00 | \$45.00 | A stream you run for a week costs about a quarter of these figures, and one you run for a day about a thirtieth. For example, 1080p at 30fps costs about **\$0.0139 per hour**, so about \$0.33 for a day. > **Exact hourly rates in credits** > > **\$1 = 1,000,000 credits.** Each hour live costs the rate below. Monthly figures are 720 hours > (30 days). > > | Resolution | 24 fps | 25 fps | 30 fps | 60 fps | > | - | - | - | - | - | > | 720p | 5,556 | 5,792 | 6,944 | 10,417 | > | 1080p | 11,111 | 11,569 | 13,889 | 20,833 | > | 1440p | 22,222 | 23,153 | 27,778 | 41,667 | > | 4K (2160p) | 33,333 | 34,722 | 41,667 | 62,500 | > > Video preparation is **5,000 credits (\$0.005) per minute of encoding time**. ### Extra destinations A loop can stream to [up to 5 destinations](/docs/platform/multistream). The first is included. Each extra destination that's turned on adds a flat fee, by resolution: | Resolution | Per extra destination, per 30 days | | - | - | | 720p | \$3.50 | | 1080p | \$6.00 | | 1440p | \$8.00 | | 4K (2160p) | \$12.50 | Like streaming time, it's charged only while the loop is live. ### Backup stream On a YouTube destination you can turn on **Backup stream**. Streamloop then publishes to YouTube's primary and backup ingest at the same time, at **no extra cost**. ### Extra storage Standard storage holds playlists up to **15 GiB**. A bigger playlist needs extra storage before the loop can start. In the loop's **General** tab, under **Storage**, switch on **Turn on extra storage for large playlists**. The change applies the next time the stream starts. Extra storage includes the first **30 GiB**. Only the part above 30 GiB is charged, at **\$0.35 per GiB-month**, and only while the loop is live. The playlist size is rounded up to a whole GiB. For example, a 40 GiB playlist pays for 10 GiB: \$3.50 for 30 days live. A file that appears in the playlist more than once counts once. ### Scenes [Scene loops](/docs/scenes/overview) pay the normal streaming rate plus a **scene rendering** charge. A 1080p 30fps scene loop costs **\$20 for 30 days**: \$10 of streaming plus \$10 of scene rendering. | Scene rendering, per 30 days | 24 fps | 30 fps | 60 fps | | - | - | - | - | | 720p | \$3.56 | \$4.44 | \$8.89 | | 1080p | \$8.00 | \$10.00 | \$20.00 | Scenes stream at up to 1080p60. The dashboard shows your exact price when you create a scene loop. ## What's free > **Idle loops** > > A loop that isn't live costs nothing. Set up as many as you like and pay only while one is > streaming. > **Your uploads** > > Keeping files in your [library](/docs/library) is free. Uploads that aren't in any playlist are > removed after 14 days. ## Ways to pay ### Monthly plan (optional) A monthly plan adds a fixed amount of credits to your balance every month, at a better rate than topping up. You can pick one during onboarding or from **Billing**. * **\$1 trial**: choose the plan's quality, and a \$1 card charge saves your card and starts a 7-day trial. You get enough credits to run one stream at that quality for the 7 days. Cancel during the trial and you pay nothing more. Each account can start one trial. * After the trial, the monthly plan for that quality starts. Your card is charged monthly and the full amount lands in your balance as credits. * Cancel any time from **Billing**. Unused credits stay yours. ### Top up as you go * Top up from **\$5** to **\$1,000** at a time. For more, email [contact@streamloop.app](mailto:contact@streamloop.app). * Pay by card or with crypto (USDC, ETH, BTC and more). * Card top-ups add a **5%** processing fee, at least **\$0.50**. Checkout shows the total before you pay, and the fee isn't refundable. Crypto has no fee. * Larger top-ups get bonus credit: **+5%** from \$100, **+7.5%** from \$300, **+10%** from \$500 and **+15%** at \$1,000. * Crypto top-ups get **+1.5%** extra, on top of the size bonus. For example, a \$500 crypto top-up adds \$557.50 of credits. ### Auto-refill **Auto-refill** tops up from your default card whenever your balance falls below an amount you set, so loops never stop mid-stream. It isn't available while a monthly plan is active, because the plan already adds credits every month. ## If you run low or out of credits * **Starting a stream** checks your balance first. If you don't have enough credits for the loop you've set up, it won't start until you top up. * **While you're live**, credits drain by the minute. If your balance runs out, your loops **stop automatically**, so you're never charged beyond what you've bought. Your videos and settings stay put. Top up and start again whenever you like. > [!TIP] > > A loop's **Costs** panel shows its hourly rate and a monthly estimate, including extra > destinations and storage, so you can top up before a 24/7 stream runs dry. ## Invoices and refunds * Download each invoice as a PDF from **Billing**. Add a company name, tax ID and address under billing details, and they appear on your invoices, including past ones. * You can request a refund on **unused credits from a purchase** from the matching invoice. A refund is capped at the credits you have left, and it goes back to your original payment method. ## FAQ > **Do credits expire?** > > No. Credits stay on your account until you use them. > **Do I need a subscription?** > > No. The monthly plan is optional. You can top up only when you need credits. > **How much does a month of 24/7 streaming cost?** > > About \$4 (720p), \$8 (1080p), \$16 (1440p) or \$24 (4K) per 30 days at 24fps, for one > destination. Higher frame rates, extra destinations and scenes cost more. > **What happens if I run out of credits mid-stream?** > > Your loops stop automatically. Nothing else is affected: your videos and settings remain, and > you can restart once you top up. > [!NOTE] > > Ready to start? [Try it on streamloop.app](https://streamloop.app). # Build and schedule your playlist Source: https://streamloop.app/docs/build-and-schedule-your-playlist Add videos to a loop, reorder them, play them in order, on shuffle or in a smart order, layer a separate audio track, schedule or repeat the loop, and edit the playlist while you're live. A video loop plays a **playlist** (one video or many) to your destinations on repeat. You can reorder the videos, shuffle them, add a separate audio track, schedule or repeat the loop, and change the playlist while you're streaming. ## Add your videos **1. Open the Playlist tab** In your loop, open the **Playlist** tab and select **Video**. **2. Add videos** Pick one of these: * **Upload** a file from your computer, or drag files onto the list. MP4, MOV and MKV work. Each file can be up to **15 GB**, on every plan and during the trial. * **From URL** imports from Google Drive, a YouTube link or a direct download link. Streamloop fetches the file on its servers, so you can close the tab while it works. A YouTube playlist link lists its videos and you confirm which ones to import. For each video you choose a quality, and for YouTube links you can import just a section. * **From library** reuses a file you already uploaded, without uploading it again. See [Library](/docs/library). **3. Reorder them** Drag any video to change its position. The order you see is the order it plays. > [!NOTE] > > Uploads that aren't in any playlist are removed automatically after 14 days. ## Choose how it loops The **Loop behavior** section sets the order each time the playlist repeats: * **In order**: play 1 → 2 → 3, then repeat. * **Shuffle**: random order, with no repeats until every video has played. * **Smart order**: describe the order in plain words ("an intro first, then the rest", "the news at the top of every hour") and an AI assistant builds it. See [Smart order](/docs/smart-order). ## Add a separate audio track Switch to **Audio** to add audio that plays *over* your video instead of its original sound. This suits music, radio and lofi channels. Audio can be one track or a whole playlist, with its own loop behavior. > [!TIP] > > Building a music or lofi channel? See [Build a 24/7 music or lofi channel](/docs/lofi-music-channel). ## Schedule when it goes live The loop's status panel has the **Schedule** and **Expiration** controls: * **Schedule loop**: turn this on and type a start time in plain words, like `in 2h` or `tomorrow 9am`, then click **Schedule the stream**. The loop shows **Scheduled to start** and goes live on its own. Before then you can **Start now** or **Cancel schedule**. * **Loop expiration**: set a run length or an end time, and the stream stops on its own. A run length (like 2 hours) starts counting when the stream goes live, not when you set it. * **Start when uploads finish**: the loop goes live as soon as your pending uploads are added. ### Repeat a schedule To run the same time window again and again, set **Recurrence** to **Daily** or **Weekly**: 1. Pick **Daily**, or pick **Weekly** and then the days of the week. 2. Enter a **From** and **To** time. Times use your local time zone. 3. Click **Schedule**. The window must be shorter than 24 hours, and it can end after midnight. Each run is a normal start and stop. On a [managed YouTube connection](/docs/platform/youtube), each run gets its own YouTube broadcast. While a repeating loop runs, **Stop for today** ends the current run and keeps the next one scheduled. **Cancel schedule** also turns repeat off. If a run can't start (for example, the destination needs reconnecting), the loop shows **Repeat is paused until this is fixed**. Fix the problem, then click **Schedule again**. ## Edit the playlist while you're live You don't have to stop a running stream to change what's playing: **1. Make your changes** While the stream is live, add, remove or reorder videos in the **Playlist** tab as usual. Nothing changes on air yet. Your edits are a draft. **2. Publish** Click **View changes** to review what's pending, then **Publish playlist**. The on-air version (shown as **Live (v1)**, **Live (v2)** and so on) updates without a restart. If a publish fails, click **Retry publish**. > [!NOTE] > > Resolution and frame rate changes need a restart. While a loop is live or scheduled, you can stop > sending to one of several destinations, but you can't add one. See > [Multistream](/docs/platform/multistream). # Features Source: https://streamloop.app/docs/features Loop pre-recorded video as a 24/7 live stream at up to 4K 60fps with HDR, send it to up to 5 platforms, design live shows with Scenes, and work as a team. Streamloop turns pre-recorded videos and audio into a continuous live stream that runs 24/7 on YouTube or any RTMP platform. Everything runs in the cloud: there's no OBS to set up, no encoding hardware and no PC left switched on. This page covers what Streamloop can do. To put it to work, start with the [quickstart](/docs/quickstart). ## Two kinds of loop | | Pre-recorded video | Scene | | - | - | - | | What it is | Your uploaded videos on repeat | A live show you design in the studio | | Maximum quality | 4K · 60 fps, with HDR | 1080p · 60 fps | | Playlists, background music, smart order | Yes | Yes | | Live inputs (RTMP, SRT, RTSP, HLS) | No | Yes | | Web pages, live data, overlays, transitions | No | Yes | | AI assistant that runs the studio | No | Yes | Both kinds stream 24/7 from the cloud, manage your YouTube channel, run on start and stop schedules, and can use a backup stream. See [Scenes](/docs/scenes/overview). ### What each one looks like ![The Edit loop page on the Playlist tab: five videos in order, the Upload, From library and From URL buttons, and the In order, Shuffle and Smart order choices.](https://mintcdn.com/sl-6d1f19fe/F9rqzgByX2JsL2bL/images/features/playlist-editor.webp?fit=max\&auto=format\&n=F9rqzgByX2JsL2bL\&q=85\&s=73ae128310af2d3bb4ee5a68f408263e) A pre-recorded video loop. On its Playlist tab you add videos with Upload, From library or From URL and drag them into order. Press Publish and the stream on air plays the new order, with no restart. ![The Scenes studio: a channel frame with a logo, a clock, a Now and Next bar and a news ticker, next to the assistant panel saying the channel is ready as a draft.](https://mintcdn.com/sl-6d1f19fe/F9rqzgByX2JsL2bL/images/features/scenes-studio-assistant.webp?fit=max\&auto=format\&n=F9rqzgByX2JsL2bL\&q=85\&s=bb89a02174e88d7b326652b316a202cb) A Scene loop in the studio. You ask the assistant for a channel with your logo, a clock, Now and Next and a news ticker. It builds the frame as a draft, and it goes on air when you press Publish. ## What you can do > **[Go live to YouTube](/docs/platform/youtube)** > > Connect your channel or paste a stream key, then loop your video to YouTube around the clock. > **[Stream to several platforms](/docs/platform/multistream)** > > Send one loop to up to 5 destinations: YouTube, Twitch, Kick, Facebook and more. > **[Loop a playlist](/docs/build-and-schedule-your-playlist)** > > Queue several videos, reorder them, and play them in order, on shuffle or in a smart order. > **[Schedule and repeat](/docs/build-and-schedule-your-playlist#schedule-when-it-goes-live)** > > Set a start time, an end time, or a window that repeats every day or week. > **[Design a live show](/docs/scenes/overview)** > > Combine live inputs, web pages, data and overlays in the studio. > **[Work with your team](/docs/workspaces-and-team)** > > Invite teammates to a shared workspace, each with their own sign-in. ## Stream destinations * **YouTube**: connect your channel through the official YouTube API, and Streamloop creates and manages the live broadcast for you. Streamloop meets YouTube's [live-streaming verification requirements](https://support.google.com/youtube/answer/6259859). You can also paste a stream key from YouTube Studio instead. See [YouTube](/docs/platform/youtube). * **Other platforms**: Twitch, Facebook, Kick, X, TikTok, LinkedIn, or any platform that accepts an RTMP stream URL and key. See [Twitch](/docs/platform/twitch) and [Other RTMP platforms](/docs/platform/other-rtmp). * **Several at once**: a loop can stream to up to 5 destinations from one encode (at most one of them YouTube). See [Multistream](/docs/platform/multistream). ## Video quality * **Resolution**: 720p, 1080p, 1440p or 4K (2160p) for video loops. Scene loops go up to 1080p. * **Frame rate**: 24, 25, 30 or 60fps. * **HDR**: stream high dynamic range video where your destination and your viewers' devices support it. Higher resolution and frame rate cost more per hour. See [Billing](/docs/billing). ## Playlists * **Queue multiple videos** in a single loop and **drag to reorder** them. * **Choose how it loops**: *In order*, *Shuffle* (no repeats until every video has played), or a [*Smart order*](/docs/smart-order) you describe in plain words, such as an intro first, a jingle every few songs, or the news at the top of every hour. * **Edit while you're live**: add, remove or reorder videos on a running stream, then publish the change. The on-air playlist updates with no stop and restart. See [Build and schedule your playlist](/docs/build-and-schedule-your-playlist). ## Your video and audio * **Common formats**: upload MP4, MOV or MKV video, and MP3, WAV, FLAC or AAC audio. Each file can be up to 15 GB. * **Import from a link**: pull media from **Google Drive**, **YouTube** or a direct download link. Streamloop fetches it on its servers at the quality you choose, so you can close the tab while it works. * **Separate audio track**: play one track or a whole audio playlist over your video, for music, radio and ambient channels. * **Library**: find, preview, rename and remove everything you've uploaded. See [Library](/docs/library). ## Scheduling * **Schedule a start**: set a future date and time, and the stream goes live on its own. * **Stop automatically**: give a loop a run length or an end time, or let it run indefinitely. * **Repeat**: run the same window every day, or on chosen days every week. * **Start when uploads finish**: go live as soon as your pending uploads are added. ## Team access Every account has a workspace. Invite teammates to it, and everyone works on the same loops, uploads and destinations with their own sign-in. See [Workspaces and team](/docs/workspaces-and-team). ## Reliability and security * **Always-on uptime**: Streamloop runs on distributed cloud infrastructure and maintains **over 99.9% uptime** (see the [status page](https://status.streamloop.app/)). It watches every stream and restarts it automatically if it stalls or drops. * **Encrypted credentials**: your YouTube authorization and stream keys are encrypted at rest. See [Reliability](/docs/reliability-guarantees). ## YouTube broadcast controls When you connect a YouTube channel, Streamloop sets up and runs the broadcast for you: * Set the stream **title, description, category, language and tags**. * Choose **visibility**: Public, Unlisted or Private. * Pick a **thumbnail**: automatic (the first frame of your video) or a custom image. * Toggle **recording** and **DVR**, and choose a **latency** mode (normal, low or ultra-low). * **Scheduled loops**: Streamloop creates the YouTube broadcast with your title when the loop goes live at its start time, and ends it when the loop stops. See [YouTube](/docs/platform/youtube) for the full setup. ## Pricing You pay with credits, only while a stream is live, and credits never expire. Top up as you go, or choose an optional monthly plan. | Resolution | 30 days nonstop, from | | - | - | | 720p | **\$4** | | 1080p | **\$8** | | 1440p | **\$16** | | 4K (2160p) | **\$24** | > [!NOTE] > > Prices are for one destination at 24fps. Higher frame rates, extra destinations and scenes cost > more. See [How billing works](/docs/billing). New accounts can start with a \$1 trial: it runs for 7 days at the quality you choose, then the monthly plan for that quality starts unless you cancel. The trial credits cover one video loop at that quality for the 7 days, so a scene runs through them sooner. See [Monthly plan](/docs/billing#monthly-plan-optional). ## FAQ > **Does Streamloop support HDR video?** > > Yes, for pre-recorded video loops at resolutions up to 4K and frame rates up to 60fps. Your > destination and your viewers' devices must also support HDR for them to see it. > **Can I stream to YouTube and Twitch at the same time?** > > Yes, with multistream: one loop can stream to up to 5 destinations, one of them YouTube. > See [Multistream](/docs/platform/multistream). > **Can multiple team members use Streamloop?** > > Yes. Invite teammates to your workspace. Each person signs in with their own account. See > [Workspaces and team](/docs/workspaces-and-team). ## Integrations > **[YouTube](/docs/platform/youtube)** > > Connect your YouTube channel so Streamloop creates and manages the live broadcast for you. > **[AI agents (MCP)](/docs/api-reference/mcp/overview)** > > Create and control streams from MCP-compatible AI tools through Streamloop's MCP server. > [!NOTE] > > Ready to go live? [Start streaming on streamloop.app](https://streamloop.app), or follow the > [quickstart](/docs/quickstart). # Streamloop docs Source: https://streamloop.app/docs/index Official Streamloop documentation: run pre-recorded video as a 24/7 live stream at up to 4K 60fps, send it to up to 5 platforms, and design live shows with Scenes. Welcome to the official documentation for **Streamloop** ([streamloop.app](https://streamloop.app)), the cloud tool that turns pre-recorded videos and audio into a continuous **24/7 live stream**. Stream at up to 4K 60fps with HDR, send one loop to several platforms, work with your team, and close your laptop after you go live. New here? The [quickstart](/docs/quickstart) takes you from a video file to a live stream in a few minutes. ## Start here > **[Quickstart](/docs/quickstart)** > > Go from a video file to a live 24/7 stream. > **[Features](/docs/features)** > > Video loops and scenes, quality, destinations, playlists and more. > **[How billing works](/docs/billing)** > > Credits, monthly plans and the price per quality. ## Build your stream > **[Build & schedule your playlist](/docs/build-and-schedule-your-playlist)** > > Add and reorder videos, shuffle, add an audio track, schedule and edit live. > **[Stream to several platforms](/docs/platform/multistream)** > > Send one loop to YouTube, Twitch, Kick and more at once. > **[Scenes](/docs/scenes/overview)** > > Design a live show with live inputs, web pages, data and an AI assistant. ## Keep it running > **[Reliability](/docs/reliability-guarantees)** > > How Streamloop keeps your stream live and what to check if it stops. > **[Workspaces and team](/docs/workspaces-and-team)** > > Invite teammates to share loops, uploads and credits. > **[Open your dashboard](https://streamloop.app/loops)** > > Jump into the app and create your first loop. # Library Source: https://streamloop.app/docs/library Find, preview, rename and remove the videos and audio you've uploaded to Streamloop, and reuse them across loops. The **Library** holds every video and audio file uploaded to your workspace. Open it from **Library** in the dashboard's top navigation. ## Find a file * **Search files** by name. * Filter by **File type** (**All**, **Video** or **Audio**) and by **Usage**: **In use** shows files in a loop's playlist, and **Unused** shows the rest. * Select a file to **Preview** it. ## Upload files Click **Upload files**, or drop video or audio files anywhere on the page. You can also upload from a loop's playlist. Everything lands in the same library. To use a library file in a loop, open the loop's **Playlist** tab and click **From library**. Nothing is uploaded twice. ## Rename a file Select a file and use **Rename file**. This only changes the name shown in Streamloop. Loops using the file keep playing it. ## Remove files **1. Select the files** Select one file, or several at once. **2. Remove them** Click **Remove** and confirm. > [!WARNING] > > Removing is permanent. The files are deleted from storage and can't be recovered. You can't remove a file that's used in a loop. Take it out of the loop's playlist first, then remove it. > [!NOTE] > > Uploads that aren't in any playlist are removed automatically after 14 days. # Build a 24/7 music or lofi channel Source: https://streamloop.app/docs/lofi-music-channel Run a 24/7 lofi, music, or radio stream on YouTube — loop a background video and play a separate audio playlist over it, cheaply and continuously. A 24/7 music or lofi channel is one of the most popular things to run on Streamloop. The recipe is simple: **loop a background video** and **play a separate audio playlist over it**. Because the audio is independent from the video, you can keep one calm visual on screen while a long list of tracks plays on rotation. ## What you'll need * A **background video** to loop — a calm scene, an animation, or any clip that looks fine on repeat. * Your **audio tracks** — the music or ambience your channel plays. ## Build the channel **1. Create a loop and add your background video** Create a loop, open the **Playlist** tab, and on the **Video** tab add your background clip. A single looping video is all you need on screen — see [Build and schedule your playlist](/docs/build-and-schedule-your-playlist). **2. Add your audio playlist** Switch to the **Audio** tab and upload your tracks (MP3, WAV, FLAC, or AAC). They play over the video instead of its original sound. Set **Loop behavior** to **Shuffle** for variety, or **In order** for a set rotation. **3. Pick a budget-friendly quality** A static visual doesn't need a high frame rate. Choosing **720p at 24fps** keeps a 24/7 channel at roughly **\$4/month** — see [Billing](/docs/billing) for the full breakdown. **4. Choose a destination and go live** Connect [YouTube](/docs/platform/youtube) or [another platform](/docs/platform/other-rtmp) from the loop's **General** tab, then start the stream, or schedule it to go live later. ## Keep it fresh You can add new tracks without taking the channel offline. Add them to the **Audio** playlist while the stream is live, then click **Publish playlist**. The rotation updates without a restart. Want a station jingle every few songs? Use a [smart order](/docs/smart-order) on the audio playlist, for example "shuffle the songs and play the jingle every three or four tracks." > [!NOTE] > > Ready to start your channel? Create your first loop on [streamloop.app](https://streamloop.app). # Stream to several platforms at once Source: https://streamloop.app/docs/platform/multistream Send one Streamloop loop to up to 5 destinations at the same time, such as YouTube, Twitch and Kick, from a single encode. With multistream, one loop streams to **up to 5 destinations** at the same time, for example your YouTube channel, Twitch and Kick. Streamloop encodes the stream once and sends the same picture and sound to each destination. Guide: [How to multistream to several platforms at once](https://streamloop.app/how-to/multistream). ## Add destinations **1. Stop the loop** You can only add destinations while the loop is stopped. A live or scheduled loop shows **Stop the loop to add destinations**. **2. Open the destination list** In the loop's **General** tab, find **Destinations**. **3. Add each destination** Click **Add destination** and pick a saved destination, or connect a new one. Repeat for each platform, up to 5. **4. Start the loop** Start or schedule the loop as usual. Each destination connects on its own. > [!NOTE] > > A loop can stream to **only one YouTube channel**. The other destinations can be any RTMP > platform. See [Twitch](/docs/platform/twitch) and [Other RTMP platforms](/docs/platform/other-rtmp). While the loop is stopped, each destination's menu lets you **Disable** or **Enable** it, or **Remove from loop**. A disabled destination stays in the list but receives nothing and costs nothing. ## What it costs The first destination is included in the streaming rate. Each extra destination that's turned on adds a flat fee while the loop is live: **\$3.50, \$6.00, \$8.00 or \$12.50 per 30 days** at 720p, 1080p, 1440p or 4K. The loop's **Costs** panel shows the total before you start. See [Billing](/docs/billing#extra-destinations). ## While the loop is live You can't add destinations or turn one back on while the loop is live or scheduled. You can **Stop sending** to a destination. It goes off the air at once, and the others keep streaming. You can turn it back on after the loop stops. You can't stop sending to the only destination still on air. Stop the loop instead. ### Destination status Each destination shows its own status, and the loop shows a summary such as **2 of 3 destinations live**: | Status | What it means | | - | - | | **Live** | The destination is receiving your stream. | | **Connecting** | Streamloop is connecting to the destination. | | **Not connecting** | The connection dropped or was refused. Streamloop keeps retrying. | | **Retrying later** | The platform keeps closing the connection. Streamloop waits, then tries again. | | **Paused** | A planned reconnect to the platform. It resumes on its own. | | **Off** | You disabled the destination or stopped sending to it. | | **Off after error** | The platform refused the stream for good (for example, a wrong stream key), so Streamloop turned this destination off. The others keep streaming. | When a destination turns off after an error, the row says why and what to fix, such as "Check the stream key and any username or password in this destination." Fix it, and turn the destination back on after the loop stops. ## Quality across destinations Every destination gets the same encode, so Streamloop picks settings that **all** of them accept. For example, if one platform takes a lower bitrate, every destination gets that bitrate. If a destination can't take the loop's resolution, the row says so: lower the resolution, or remove that destination. ## Good to know * Onboarding sets up one destination. Add more from the loop's **General** tab afterwards. * A YouTube destination can also run a [backup stream](/docs/platform/youtube#backup-stream). # Stream to Facebook, Kick, X, TikTok and other RTMP platforms Source: https://streamloop.app/docs/platform/other-rtmp Add Facebook, Kick, X, TikTok, LinkedIn, Telegram or any custom RTMP server as a Streamloop destination: where to find each stream key and what to watch out for. Any platform that accepts an **RTMP** (or encrypted **RTMPS**) stream works with Streamloop. RTMP is the standard way streaming tools send live video. The platform gives you two things: * a **server URL** (it starts with `rtmp://` or `rtmps://`), and * a **stream key**, a password that lets a streaming tool send video to your account. ## Add the destination **1. Open the destination picker** In your loop's **General** tab, click **Connect a destination** (or **Add destination** if your loop has a destination list). **2. Pick the platform** Pick the platform from the list, or **Custom RTMP** for anything else. For Facebook, X and Kick, Streamloop fills in the **RTMP URL** for you. **3. Paste the key** Paste the **Stream key** (and the **RTMP URL** if it's empty), give the destination a **Display name**, and click **Save destination**. > [!TIP] > > If a platform gives you one long URL that ends in the key, split it: everything up to the last > `/` is the RTMP URL, and the last part is the stream key. ## Where each platform shows its key | Platform | Where to find the URL and key | Watch out for | | - | - | - | | **Facebook** | Live Producer → pick the page or profile → Streaming software → Stream key | Keys are single-use by default. Turn on the persistent stream key, or the destination stops working after the first broadcast. | | **Kick** | Creator Dashboard → Settings → Stream Key | | | **X** | Media Studio → Producer → Sources | A key tied to one broadcast ends with it. Use a persistent source if your account has one. | | **TikTok** | LIVE Studio, or Go LIVE with third-party streaming enabled | The URL and key change for every LIVE session, so a long-running loop needs a new key each time. Third-party streaming isn't available on every account. | | **LinkedIn** | Create a live event with a third-party encoder. The URL and key appear shortly before it starts. | The key expires with the event. | | **Telegram** | In the channel or group, start a live stream and choose **Stream with** | Telegram issues a new key each time the video chat is recreated. | | **Anything else** | Look for "stream with your own software" or "OBS settings" | | For Twitch, see [Twitch](/docs/platform/twitch). For YouTube, see [YouTube](/docs/platform/youtube). > [!NOTE] > > For a 24/7 loop, always use a **persistent** key where the platform offers one. A key that belongs > to a single broadcast or session stops working when that broadcast ends. ## Good to know * **Video only.** Streamloop sends the video and sound. Titles, comments and audience settings stay on the platform. * **Quality.** YouTube accepts the widest range of video formats. For other platforms, Streamloop sends a widely supported format and keeps within each platform's limits, so you don't need to set a bitrate. * **Several platforms at once.** Add up to 5 destinations to one loop with [multistream](/docs/platform/multistream). * Stream keys are encrypted at rest and never shown again after you save them. # Stream to Twitch 24/7 Source: https://streamloop.app/docs/platform/twitch Loop pre-recorded video to Twitch around the clock with Streamloop: copy your Twitch stream key, add it as a destination and go live. Streamloop streams to Twitch with your **stream key**. A stream key is the password that lets a streaming tool send video to your channel. ## Get your stream key from Twitch In the Twitch **Creator Dashboard**, go to **Settings → Stream** and copy your **Primary Stream Key**. Twitch keys start with `live_`. > [!WARNING] > > Your stream key gives full control of your channel's live stream. Don't share it. If it leaks, > reset it in Twitch and update the destination in Streamloop. ## Add Twitch to your loop **1. Open the destination picker** In your loop's **General** tab, click **Connect a destination** (or **Add destination** if your loop has a destination list). **2. Choose Twitch** Pick **Twitch · RTMP stream key**. Streamloop fills in Twitch's **RTMP URL** for you. **3. Paste the key** Paste your **Stream key**, give the destination a **Display name**, and click **Save destination**. **4. Go live** Start the loop. Your title and category stay in Twitch: set them in the Creator Dashboard. ## Good to know * **Twitch ends broadcasts after about 48 hours.** Streamloop reconnects before that happens, so your loop keeps running. On Twitch, it shows up as a new broadcast. * **Video only.** Streamloop sends the video and sound. Your stream title, category and viewer counts stay in Twitch. * **Quality.** Streamloop keeps the stream within what Twitch accepts, so you don't need to set a bitrate. Twitch takes up to 1080p: if the destination says it doesn't accept the loop's resolution, lower the resolution in the loop's **General** tab. * **Twitch and YouTube together.** Add both to one loop with [multistream](/docs/platform/multistream). # YouTube (managed connection) Source: https://streamloop.app/docs/platform/youtube Connect your YouTube channel to Streamloop through Google sign-in and let Streamloop create and manage the live broadcast — title, privacy, thumbnail, and schedule. There are two ways to stream to YouTube with Streamloop. This page covers the **managed connection**, where you link your channel once and Streamloop runs the whole broadcast for you. If you'd rather paste a stream key from YouTube yourself, see [YouTube via RTMP stream key](/docs/platform/youtube-rtmp). Guide: [How to run a 24/7 YouTube live stream](https://streamloop.app/how-to/run-24-7-live-stream-pre-recorded-video). ## Why use the managed connection With a managed connection, Streamloop talks to YouTube through the official YouTube API, so it can: * **Create and run the broadcast** for you — no copying stream keys. * Let you set the **title, description, privacy, thumbnail, and schedule** from inside Streamloop. * Start the broadcast with your title and settings when the loop goes live, and end it when you stop the loop. ## Connect your channel **1. Open the destination picker** In your loop's **General** tab, find the destination section and click **Connect a destination** (or **Add destination** if your loop has a destination list). **2. Choose YouTube (Connect channel)** In the provider grid, pick **YouTube · Connect channel** (marked **recommended**). This starts Google sign-in. **3. Sign in and grant access** Complete the Google popup. On the consent screen, **leave every box checked** — including the YouTube permission. > [!WARNING] > > Google shows the YouTube permission as an optional checkbox. If you untick it, sign-in still > succeeds but Streamloop gets no access to your channel and the connection silently fails. > Keep all boxes checked. **4. Confirm it's connected** Your channel appears with a **Connected** badge. Pick it as the loop's destination and you're ready to set up the broadcast. > [!NOTE] > > A loop can stream to one YouTube channel at a time. To reach other platforms as well, add them as > extra destinations. See [Multistream](/docs/platform/multistream). ## Backup stream YouTube accepts a primary and a backup feed for the same broadcast. In the loop's **General** tab, turn on **Run backup stream for higher redundancy** under **Backup stream**. Streamloop then sends your stream to both at the same time, so YouTube can switch to the backup if the primary drops. The backup stream costs nothing extra. It's off by default, and it's only offered for YouTube destinations. See [Billing](/docs/billing#backup-stream). ## Set up the broadcast Once a YouTube channel is connected, the loop's **YouTube** tab unlocks. From there you control how the broadcast appears on your channel: * **Title, description, category, language, and tags.** * **Visibility** — Public, Unlisted, or Private. * **Thumbnail** — automatic (the first frame of your video) or a custom image. * **Recording and DVR**, plus a **latency** mode (normal, low, or ultra-low). Streamloop creates the broadcast on your channel when the loop goes live. A scheduled loop appears on YouTube at its start time; it does not show as an upcoming broadcast before then. See [YouTube broadcast controls](/docs/features#youtube-broadcast-controls) for the full list. ## Reconnect when access expires If Google's authorization expires, the destination shows **YouTube needs reauthorization**. Click **Reconnect** and complete sign-in again to keep streaming. Your stream keys and authorization are [encrypted at rest](/docs/reliability-guarantees#your-credentials-are-encrypted). ## Managed connection vs stream key | | Managed connection | [Stream key](/docs/platform/youtube-rtmp) | | - | - | - | | Setup | Sign in with Google once | Paste a key from YouTube | | Broadcast created by | Streamloop | You, in YouTube Studio | | Title / privacy / thumbnail | Set in Streamloop | Set in YouTube Studio | > [!NOTE] > > Ready to go live? Connect your channel on [streamloop.app](https://streamloop.app), or follow the > [quickstart](/docs/quickstart). # YouTube via RTMP stream key Source: https://streamloop.app/docs/platform/youtube-rtmp Stream a pre-recorded video to YouTube 24/7 using a stream key from YouTube's live control room — manual setup, no account connection. This page covers streaming to YouTube with a **stream key** you copy from YouTube yourself. If you'd rather connect your channel and let Streamloop manage the broadcast, use the [managed connection](/docs/platform/youtube) instead. Use a stream key when you want to create and control the broadcast in **YouTube Studio** — for example, on a channel you can't connect with Google sign-in. ## Get your stream key from YouTube **1. Open YouTube Live** In YouTube Studio, click **Create → Go live** to open the live control room. **2. Copy the stream key** On the **Stream** tab, copy the **Stream key** (it looks like `live_xxxx_xxxxxxxxxxxxxxx`). The ingest URL is `rtmp://a.rtmp.youtube.com/live2`. > [!WARNING] > > Use the channel's persistent stream key, not the one-off key of a single scheduled broadcast. > A one-off key stops working when that broadcast ends, and your 24/7 loop ends with it. If your > channel has never streamed, YouTube can take up to 24 hours to enable live streaming. ## Add it to Streamloop **1. Open the destination picker** In your loop's **General** tab, click **Connect a destination** (or **Add destination** if your loop has a destination list). **2. Choose YouTube (RTMP stream key)** Pick **YouTube · RTMP stream key**. Give it a **Display name**, leave the **RTMP URL** as the pre-filled `rtmp://a.rtmp.youtube.com/live2`, paste your **Stream key**, and click **Save destination**. > [!TIP] > > Shortcut: paste the stream key straight into the destination search box. Streamloop detects the > YouTube format and connects it for you — you'll see **"Connecting YouTube key…"** while it links. ## What you manage where With a stream key, **you** own the broadcast settings. Set the title, description, privacy, and thumbnail in **YouTube Studio** — Streamloop only sends the video to the key you provided. For title, privacy, thumbnail, and schedule control inside Streamloop, switch to the [managed connection](/docs/platform/youtube). > [!NOTE] > > Ready to go live? Add your key on [streamloop.app](https://streamloop.app), or follow the > [quickstart](/docs/quickstart). # Quickstart Source: https://streamloop.app/docs/quickstart Go from a video file to a 24/7 live stream in minutes: create a loop, add your video, connect a destination and go live, with no OBS and no PC left running. This guide takes you from a video file to a live 24/7 stream. It takes a few minutes. > [!NOTE] > > You need a **video file** and a **destination** to stream to: a YouTube channel, or a stream key > from Twitch, Facebook, Kick, X, TikTok or any other RTMP platform. A *loop* is a stream you set up once and leave running. There are two kinds: * **Pre-recorded video** loops play your uploaded videos on repeat, at up to 4K 60fps. This guide covers them. * **Scene** loops are live shows you design in the studio, with live inputs, web pages and overlays. Choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). ## Your first stream When you sign up at [streamloop.app](https://streamloop.app), Streamloop walks you through your first loop step by step. You pick the type, a name and a destination, connect the destination, pick the quality, add your media and choose how to pay. The steps below follow the same path from the dashboard, for your next loops. **1. Create a loop** On the **Loops** page, click **New loop**. Enter a **Loop name** (for example `Nature sounds`) and pick a **Resolution** and **Framerate** (24, 30 or 60 fps). If you see a choice between **Pre-recorded video** and **Scene**, click **Choose video**. Otherwise click **Create loop**. The loop opens on its **Playlist** tab. **2. Add your video** On **Video**, click **Upload** to add a file (MP4, MOV or MKV, up to 15 GB per file), or **From URL** to import from Google Drive, YouTube or a direct link. Add more videos to build a playlist, or add an **Audio** track to play over the video. See [Build and schedule your playlist](/docs/build-and-schedule-your-playlist). **3. Connect a destination** Open the **General** tab. Under **Destination**, click **Connect a destination**. Pick **YouTube · Connect channel** to link your channel with Google, or pick a platform (or **Custom RTMP**) and paste its stream key. See [YouTube](/docs/platform/youtube), [Twitch](/docs/platform/twitch) and [Other RTMP platforms](/docs/platform/other-rtmp). **4. Check the quality** Still in **General**, **Output quality** shows the resolution (720p, 1080p, 1440p or 2160p (4K)) and frame rate (24, 25, 30 or 60 fps). Higher quality costs more per hour, and 1080p at 30fps suits most streams. Streamloop also streams HDR video when your destination supports it. See [Billing](/docs/billing) for prices. **5. Go live** When the loop shows **Stream is idle**, click **Start stream**. Your video starts looping to your destination within a minute or two. To go live later, turn on **Schedule loop**, enter a time like `tomorrow 9am`, and click **Schedule the stream**. > [!TIP] > > Your loop is live. It keeps streaming 24/7 from the cloud. You can close your laptop and it stays > on air. > [!NOTE] > > Streaming uses credits from your balance. To try Streamloop, start a monthly plan with a \$1 > trial: 7 days of one stream, card required. Or top up as you go. See [Billing](/docs/billing). ## What's next > **[Build and schedule your playlist](/docs/build-and-schedule-your-playlist)** > > Reorder videos, shuffle, add an audio track, repeat the schedule and edit while live. > **[Stream to several platforms](/docs/platform/multistream)** > > Send one loop to up to 5 destinations at once. > **[How billing works](/docs/billing)** > > Credits, monthly plans, the price per resolution and what happens if you run low. > **[Reliability](/docs/reliability-guarantees)** > > How Streamloop keeps your stream live around the clock and what to check if it stops. # Reliability Source: https://streamloop.app/docs/reliability-guarantees How Streamloop keeps your 24/7 stream live — cloud hosting, continuous health monitoring with automatic restart, distributed failover, and encrypted credentials. A 24/7 stream is only useful if it stays up while you're asleep, at work, or away from your computer. Streamloop runs your stream in the cloud and watches it continuously, bringing it back on its own if anything goes wrong. ## Always on, in the cloud Your stream runs on Streamloop's servers, not on your machine. There's no OBS to keep open and no PC to leave switched on — once a loop is live, you can close your laptop and it keeps streaming. ## Monitoring and automatic recovery Streamloop checks the health of every live stream around the clock. If a stream stalls, drops, or a destination briefly refuses the connection, Streamloop restarts it and reconnects automatically — usually before viewers notice. Streams run on distributed infrastructure with standby capacity, so a single failure on our side doesn't take you off air. In practice, Streamloop maintains **over 99.9% uptime**; the [status page](https://status.streamloop.app/) shows current and past uptime. > [!NOTE] > > We don't claim a perfect 100%. Platforms like YouTube and Twitch occasionally enforce their own > limits or maintenance windows. When that happens, Streamloop keeps trying and brings your stream > back as soon as the destination accepts it again. ## Your credentials are encrypted Connecting a destination means trusting us with sensitive keys, so we protect them: * Your **YouTube authorization** and **RTMP stream keys** are **encrypted at rest**. * They're kept in **isolated storage**, separate from the rest of your account data. * They're used only to publish your stream to the destination you picked — never shared. * In [scenes](/docs/scenes/overview), camera passwords and the keys your tables use are encrypted and never shown again. Live input stream keys are shown once, when you create them. See [YouTube integration](/docs/platform/youtube) for how the YouTube connection works. ## Troubleshooting ### Your stream stopped or isn't showing **1. Check your destination** Open YouTube, Twitch, or your RTMP platform and confirm it shows an incoming stream. A destination-side issue (a disabled channel, a changed key) can stop the broadcast even when Streamloop is healthy. **2. Check your credit balance** A stream stops if your credits run out. Top up and start it again — see [Billing](/docs/billing). **3. Reconnect an expired YouTube channel** If your YouTube connection has expired, reconnect it from the destination settings, then restart the stream. ### One of several destinations is down On a [multistream](/docs/platform/multistream) loop, each destination has its own status, and one failing destination never takes the others off the air. * **Not connecting** or **Retrying later**: Streamloop keeps trying on its own. Check that the platform still accepts your stream. * **Off after error**: the platform refused the stream for good, often because of a changed stream key. The row says what to fix. Update the destination, and turn it back on after the loop stops. * **Off**: you turned it off, or stopped sending to it. ### A repeating loop didn't start If a run of a [repeating schedule](/docs/build-and-schedule-your-playlist#repeat-a-schedule) can't start, the loop shows **Repeat is paused until this is fixed**. Fix the problem shown (often credits or a destination that needs reconnecting), then click **Schedule again**. ### A scene shows a slate or a blank layer In a [scene](/docs/scenes/overview), a live input or camera that stops sending switches to its fallback, such as a "Stream starting soon" slate. The stream itself stays live. Check the source's status in the studio's **Live** panel: * **Waiting** or **Down**: no feed is arriving. Check your encoder or camera, and that it uses the current stream key. * **Key revoked**: the key was replaced. Paste the new key into your encoder. * **Can't start**: a setting is missing. Open the source to see which. See [Live inputs](/docs/scenes/live-inputs) and [Cameras](/docs/scenes/cameras). > [!NOTE] > > Building an always-on channel? Start on [streamloop.app](https://streamloop.app), or follow the > [quickstart](/docs/quickstart). # AI assistant Source: https://streamloop.app/docs/scenes/ai-assistant Describe your show in plain words and the Streamloop studio's AI assistant builds the scenes, tables and playlists, then helps you run it. It asks before it changes anything on air. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). The studio's **Assistant** builds and edits your show from plain-language requests. Ask for "a lo-fi radio frame with the current track and a clock" or "a scoreboard from this sheet", and it creates the frames, layers, tables and playlists. ## Start with the assistant A new, empty show opens on the assistant's prompt: 1. Describe the show you want, or pick one of the starter ideas. 2. Attach references if you have them: drop or paste pictures, logos, a brand sheet or data files. 3. Send. The **Assistant** panel opens on the right and shows each step as it works. You can open the assistant at any time from the **Assistant** tab in the right panel. ## What it can do * **Build and edit** frames, layers, components, scripts and controls. * **Bring in data**: try a URL to see what it returns, and turn it into a [table](/docs/scenes/data-and-sheets). * **Fill playlists** with your media. * **Search the web** for feeds, pages and facts to use in your show. * **Check its work** by looking at a picture of the frame, and line it up against a reference you attached. * **Ask you** when a request is unclear and a wrong guess would cost real work, with a few options to pick from. Each change the assistant makes is one undo step. ## It asks before going on air Anything that changes what viewers see right now needs your approval first: taking a frame to air, starting an ad break, and similar actions. The assistant shows what it wants to do, and you allow or deny it. Edits to the draft don't reach viewers until you [publish](/docs/scenes/build-a-scene#publish). ## Privacy * The assistant never sees your [secrets](/docs/scenes/data-and-sheets#keep-keys-secret) or camera passwords. * Chats are saved per scene for each person, so you can pick up where you left off, up to 50 chats per scene. ## Limits The assistant has a daily usage limit per workspace. If you reach it, try again the next day (UTC). # Build a scene Source: https://streamloop.app/docs/scenes/build-a-scene Lay out a live show in the Streamloop studio: start from a layout or the AI assistant, add sources and layers, publish, and go live. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). The studio is where you design a scene loop's show. It opens when you create a scene loop, or when you click **Open studio** on a loop's **Scene** tab. ## Start your show A new, empty show opens on a large prompt. You have two ways to start: * **Describe the show** to the [AI assistant](/docs/scenes/ai-assistant). You can attach, drop or paste references such as pictures, logos, a brand sheet or data files. * **Or start from scratch**: click **Choose a layout** or **Blank frame**. **Choose a layout** opens the **New frame** picker: **Blank**, or a frame split into slots, such as **Grid**, **Speaker**, **Picture in picture**, **Screen share** or **Split**. Each slot is a placeholder: drop a source on it. For a ready-made frame, press `Cmd+K` (`Ctrl+K` on Windows) to open the command menu and choose **New frame:** and a template, for example **New frame: Lower third**. The templates are: | Template | What it is | | - | - | | Full camera | One live input edge to edge. | | Interview | Host and guest side by side, with name straps. | | Picture in picture | Main feed with a corner inset. | | Lower third | Camera with an animated name strap. | | Starting soon | Animated backdrop, title and countdown. | | Scoreboard | Teams and a live score. | | Headlines | A grid of cards from a data table. | | Be right back | Holding slide with a looping clip. | | Blank | An empty 1080p canvas. | ## Find your way around The top bar belongs to the loop: **‹ Loop name** takes you back to the loop's page, and the **General**, **Scene** and **YouTube** tabs open those parts of the loop. * **Left sidebar**: **Sources & layers**, **Scripts**, **Components** and **Live** (the [show control](/docs/scenes/show-control) panel). Press `[` to fold it away. * **Canvas**: your frames side by side. Drag, resize and layer things directly on it. * **Right panel**: **Design** (the selected layer's settings), **Code** and **Assistant**. Press `]` to fold it away. ## Add sources and layers A **source** is anything with picture, sound or data. A **layer** shows a source (or text, an image or a shape) in a frame. 1. In **Sources & layers**, click **Add source** and pick a kind: * **Live inputs**: [RTMP input, SRT input or HLS stream](/docs/scenes/live-inputs), or an [IP camera](/docs/scenes/cameras). * **Media**: a video playlist, a video file, or a [web page](/docs/scenes/web-pages). * **Data**: [Google Sheets, Excel or CSV, a JSON feed, web page data or a formula](/docs/scenes/data-and-sheets). 2. Drag the source onto a frame to show it there. 3. Select a layer to change its size, position, style and animation in **Design**. To add another frame, click **New frame** (the + button next to **Layers**). Each frame has its own **transition**: cut, fade, slide, wipe or dip. ## Your changes save automatically Every change saves as you work: the top bar shows **All changes saved**. Teammates who open the same scene see each other's edits in real time. Saved changes are a **draft**. Nothing reaches viewers until you publish. ## Publish Click **Publish** to make your draft the live version. The top bar then shows the version, for example **v3 · published 2 min ago**. * Before publishing, Streamloop checks the show. If something is wrong, such as a layer that shows a source you deleted, the problems appear right under **Publish**. Fix them and publish again. * While the loop is live, publishing swaps in the new version without stopping the stream. * The version menu lists earlier versions. **Restore** one to publish it again as a new version. Nothing is lost. ## Go live **Go live** starts the loop. It stays disabled until you've published the scene once, because the stream always shows the published version. You can also start, stop and schedule the loop from its page, like any other loop. ## Remove a scene To turn a scene loop back into a video loop, stop the loop, open its **Scene** tab, and choose **Remove scene**. You can't add or remove a scene while the loop is live. # IP cameras Source: https://streamloop.app/docs/scenes/cameras Put an internet-reachable IP camera on air in a Streamloop scene: add its RTSP address, test the connection, and keep its password safe. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). An **IP camera** source pulls a camera's live feed straight from the internet over **RTSP**, the protocol most IP cameras speak. Streamloop connects to the camera, so nothing needs to run on your side. ## Before you start * **The camera must be reachable from the internet**: a public IP address, or a port forward on your router (RTSP usually uses port 554). Cameras on a private or local network address are refused. * If your camera can push RTMP itself, use an [RTMP input](/docs/scenes/live-inputs) instead. * You need the camera's RTSP address, for example `rtsp://203.0.113.7:554/stream1`, and its username and password if it has one. The camera's manual or app lists the address. ## Add a camera **1. Add the source** In **Sources & layers**, click **Add source** and pick **IP camera**. Drag it onto a frame. **2. Enter the details** Enter the **RTSP URL**, **Username** and **Password**. Leave **Transport** on **TCP** unless your camera only works over UDP. TCP gets through most routers and firewalls. **3. Test the connection** Click **Test connection**. A working camera shows its resolution, frame rate and a snapshot, which the canvas uses as its preview. Cameras that send H.265 video work, but show no snapshot. **4. Save and publish** Click **Save camera**, then [publish](/docs/scenes/build-a-scene#publish) the scene. ## If the test fails | Result | What to check | | - | - | | Refused | The address points to a private network. Use the camera's public address or a port forward. | | Unreachable | The camera or router isn't accepting connections. Check the port forward and that the camera is on. | | Timeout | The camera didn't answer in time. Check the address and port. | | Auth | The username or password is wrong. | | Not found | The address is right but the stream path isn't. Check the camera's manual for its RTSP path. | | Unsupported | The camera sends a format Streamloop can't play. Try another stream profile on the camera. | ## Keep it working * **Passwords stay private.** The camera's password is encrypted and never shown again, not even to your teammates or the AI assistant. The field shows **Saved (blank keeps it)**: leave it blank to keep the saved password. * **Dynamic DNS.** If your camera uses a dynamic DNS name and its address changes, open the camera and click **Save camera** again so Streamloop picks up the new address. * **When the camera drops**, the layer follows its **If it drops** setting, such as a "Camera reconnecting" slate. See [Live inputs](/docs/scenes/live-inputs#when-a-feed-drops). # Data and sheets Source: https://streamloop.app/docs/scenes/data-and-sheets Feed live data into a Streamloop scene from Google Sheets, Excel or CSV files, JSON feeds, web pages and MCP servers, and show it in text, scoreboards and lists that update on air. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). A **table** brings rows of data into your show: scores, headlines, schedules, prices, names. Layers show a table's values, and when the data changes, the stream updates on its own. ## Kinds of table | Kind | Where the rows come from | | - | - | | **Google Sheets** | A shared Google Sheet. The first row is the header. | | **Excel or CSV** | A spreadsheet file you upload. | | **JSON feed** | A URL that returns JSON, checked on a schedule. | | **Web page data** | Elements picked from a web page with a CSS selector, one row per match. | | **MCP server** | A tool on an MCP server, such as Stripe or Notion. Each refresh calls the tool, and its answer becomes the rows. | | **Formula** | A table computed in TypeScript from other tables and controls. It recomputes when they change. | ## Connect a Google Sheet **1. Share the sheet** In Google Sheets, click **Share** and set General access to **Anyone with the link** as a **Viewer**. Make sure the first row holds the column names. **2. Add the table** In the studio's **Sources & layers**, click **Add source** and pick **Google Sheets**. Paste the **Sheet URL**, and pick the **Tab** if the sheet has several. **3. Pick how often it refreshes** Under **Refresh**, choose how often Streamloop reads the sheet, such as every 30 seconds. **4. Show it in a frame** Bind a layer to the table: a text layer can show one cell, and a list or grid can repeat for every row. The [AI assistant](/docs/scenes/ai-assistant) can do this for you. **5. Publish** [Publish](/docs/scenes/build-a-scene#publish) the scene. From then on, edits in the sheet reach the stream at the next refresh. ## Use a JSON feed or web page For a **JSON feed**, enter the URL and how often to check it. For **Web page data**, enter the page URL and a CSS selector: each element it matches becomes a row. Both must be public `http://` or `https://` addresses. If the feed needs a key, add it under **Headers** (for example `Authorization: Bearer …`) or in the URL. ## Use an MCP server An MCP server is a service that AI assistants can call. Many apps have one, and a scene can read from it like a feed. **1. Add the table** In **Sources & layers**, click **Add source** and pick **MCP server**. Paste the **Server URL**. **2. Sign in** If the server needs a sign-in, click **Connect** next to **Sign-in**. A window opens where you sign in to the server. If the server takes an API key instead, add it under **Headers**. **3. Choose the tool** Pick a **Tool** from the server's list. If the tool needs input, type it under **Arguments**. Use **Pick** to choose the part of the answer that holds the rows, and **Refresh** for how often to call the tool. A table only reads. Streamloop refuses a tool that changes something, such as one that sends, updates or deletes, as a table. The sign-in and any key are saved as secrets of the scene, like the keys below. ## Keep keys secret A key you type into a table's headers or URL (like `?api_key=…`) is saved as a **secret** of the scene. The table keeps only the secret's name, so the key: * is never part of the show or its published versions, * is never shown again, to you or your teammates, and * is never seen by the AI assistant. To change a key, type it again. A changed key reaches the running stream the next time you publish. Streamloop refuses to publish a show that has a raw key in a header like `Authorization` instead of a secret. ## Update data while live You don't have to wait for a refresh. In the **Live** panel, **Send to air** pushes a table's current rows to the running show at once. See [Show control](/docs/scenes/show-control). # Live inputs Source: https://streamloop.app/docs/scenes/live-inputs Send a live feed from OBS, vMix or a hardware encoder into a Streamloop scene over RTMP, RTMPS or SRT, or pull an HLS stream or an IP camera's RTSP feed, with a fallback slate when the feed drops. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). A **live input** brings a live feed into your show. Your encoder (OBS, vMix or a hardware encoder) pushes video to Streamloop, and the scene shows it like any other layer. | Kind | Use it for | | - | - | | **RTMP input** | OBS, vMix or a hardware encoder pushes to a server URL and a stream key. RTMPS gives an encrypted link. | | **SRT input** | A low-latency push over SRT. Good on unstable networks. | | **HLS stream** | Pull a live stream from an `.m3u8` URL. | | **IP camera** | Pull a camera's RTSP feed from the internet. See [Cameras](/docs/scenes/cameras). | ## Add an RTMP or SRT input **1. Add the source** In the studio's **Sources & layers**, click **Add source** and pick **RTMP input** or **SRT input**. Drag it onto a frame. **2. Create a stream key** Open the source's settings and click **Create stream key**. Streamloop shows the **Server URL**, **Server URL (RTMPS)** and **Stream key**, or for SRT, the **SRT URL**. **3. Copy it now** Copy the key or SRT URL right away. **It's shown only once** and isn't kept in the show. If you lose it, create a new key. **4. Set up your encoder** Paste the details into your encoder (see below) and start streaming from it. **5. Publish** [Publish](/docs/scenes/build-a-scene#publish) the scene. A new key reaches the stream when you publish. ### Encoder settings **RTMP** * **OBS**: Settings → Stream → Service **Custom**. Paste the **Server URL** (use the RTMPS one for an encrypted link) as Server, and the stream key as Stream Key. * **vMix**: Add Destination → Custom RTMP Server, with the same URL and key. **SRT** * **OBS**: Settings → Stream → Service **Custom**. Paste the **SRT URL** as Server and leave Stream Key empty. * **vMix and hardware encoders**: use caller mode to the host and port in the SRT URL, with the **Stream ID** shown, and set the packet size to **1128**. At the usual 1316 the encoder connects but no picture arrives. ## Replace a key Click **New key (the old one stops working)** to issue a new key. The old key stops working, so update your encoder and publish. Use this if a key leaks. ## Input status Each input shows its status in the studio: | Status | Meaning | | - | - | | **Live** | Your encoder is sending. | | **Waiting** | No feed is arriving yet. | | **Down** | The feed was lost. | | **Key revoked** | The key was replaced. Paste the new key into your encoder. | | **Idle** | The input isn't in a frame that's on air or cued, so it isn't running. | | **Can't start** | A setting it needs is missing. Open the source to see which. | ## When a feed drops Every live input has an **If it drops** setting that decides what viewers see while the feed is gone: * **Show a slate**: a holding card with your text, such as "Stream starting soon". This is the default. * **Hide the layer**: viewers see what's behind the layer until the signal returns. * **Switch to** another source, such as a looping clip. Streamloop waits a few seconds before switching, so brief drops don't flash the fallback on air. When the feed comes back, the input returns on its own. ## Good to know * Guests can't join from a browser. Send each camera or encoder as an RTMP, RTMPS, SRT, RTSP or HLS input. * The scene stream keeps running when an input drops. Only that layer falls back. # Scenes Source: https://streamloop.app/docs/scenes/overview Scenes turn a Streamloop loop into a live show: live inputs, web pages, live data, overlays and transitions, designed in the studio with an AI assistant and streamed 24/7 from the cloud. > [!NOTE] > > Scenes are available to every workspace. Choose **Scene** when you create a loop. A **Scene** loop is a live show you design in the Streamloop studio. Instead of playing videos one after another, it lays out everything on one canvas: your playlists, live camera or encoder feeds, web pages, text, logos and live data. Streamloop renders the show in the cloud around the clock, with no OBS and no PC left running. ## Video loop or scene loop? | | Pre-recorded video | Scene | | - | - | - | | Upload and loop videos, background music, smart order | Yes | Yes | | Maximum quality | 4K · 60 fps | 1080p · 60 fps | | Live inputs: RTMP, RTMPS, SRT, RTSP, HLS | No | Yes | | Web pages: dashboards, alerts, chats, visualizers | No | Yes | | Live data: Google Sheets, CSV and JSON feeds | No | Yes | | Image and logo overlays, text, clocks and countdowns | No | Yes | | Multi-layer frames and transitions (cut, fade, slide, wipe, dip) | No | Yes | | Fallback slate when a live input drops | No | Yes | | Edit together in real time | No | Yes | | Switch frames on a schedule you set | No | Yes | | AI assistant that builds and runs the show | No | Yes | Both stream 24/7 from the cloud, manage your YouTube channel, run on start and stop schedules, and can use a backup stream. ## Scenes and frames A scene is what you build and publish. It holds one or more **frames**, such as "Starting soon", "Interview" and "Be right back". One frame is on air at a time. You cut between them from the **Live** panel, or the show's script does it for you on a schedule or when data changes. ## The words you'll meet * **Scene**: what you build in the studio and publish. A scene loop plays it. * **Show**: everything one scene plays, with all its frames, sources and data. * **Frame**: one arrangement of layers. One frame is on air at a time. * **Layer**: one thing in a frame, such as a video, text, an image or a shape. * **Source**: anything with picture or sound: a file, a playlist, a web page or a live input. Each source shows as live or down. * **Table**: rows of data from a sheet, a file or a feed, which layers can show. * **Control**: a switch or value you set while the show runs, like a "Breaking news" toggle. * **Transition**: how one frame gives way to the next. * **Publish**: makes your edits the live version. Until you publish, edits are a draft nobody sees on air. ## Create a scene loop **1. Start a new loop** On the **Loops** page, click **New loop**, enter a **Loop name**, and pick the **Resolution** and **Framerate**. **2. Choose scene** Click **Choose scene**. If you pick 1440p or 4K, the scene streams at 1080p, the scene maximum. **3. Build it in the studio** The studio opens on your new show. See [Build a scene](/docs/scenes/build-a-scene). To turn an existing loop into a scene loop, stop it, open its **Scene** tab, and click **Design a scene**. To go back to a video loop, stop the loop and choose **Remove scene**. ## Pricing A scene loop pays the normal streaming rate plus a scene rendering charge. A 1080p 30fps scene loop costs **\$20 for 30 days**. See [Billing](/docs/billing#scenes). ## Next steps > **[Build a scene](/docs/scenes/build-a-scene)** > > Lay out your show, publish it and go live. > **[Live inputs](/docs/scenes/live-inputs)** > > Bring in OBS, vMix or a hardware encoder over RTMP or SRT. > **[AI assistant](/docs/scenes/ai-assistant)** > > Describe the show and let the assistant build it. > **[Run the show](/docs/scenes/show-control)** > > Switch frames, set controls and update data while you're live. # Run the show while live Source: https://streamloop.app/docs/scenes/show-control Switch scenes with transitions, flip controls, update data and publish changes while your Streamloop scene loop is on air, without stopping the stream. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). Once your scene loop is live, you run the show from the studio's **Live** panel. Open it from the left sidebar. ## What's on air The top of the **Live** panel shows the frame on air, and the next frame if one is cued. If it says **Stream offline**, the loop isn't running: start it with **Go live** or from the loop's page. ## Switch frames Under **Cut to**, click a frame to take it to air. Streamloop plays the frame's **transition**: a cut, fade, slide, wipe or dip. You can also right-click a frame on the canvas and choose **Go live**, or **Cue next** to cue it so the switch is instant. ## Set controls **Controls** are switches and values you set while the show runs, for example a "Breaking news" toggle, a guest's name in a lower third, or a button that starts a countdown. They appear under **Controls** in the **Live** panel. A change shows on air right away. Add controls while building the show (or ask the [AI assistant](/docs/scenes/ai-assistant)), then publish. ## Watch your sources Under **Sources**, each source shows its status (**Live**, **Waiting**, **Down** and so on), and a playlist shows the track that's playing. A live input that's down shows its fallback on air. See [Live inputs](/docs/scenes/live-inputs#when-a-feed-drops). ## Update data Under **Tables**, **Send to air** pushes a table's current rows to the running show at once, without waiting for the next refresh. ## Publish while live Edits in the studio are a draft until you click **Publish**. While the loop is live, publishing swaps in the new version without stopping the stream. Viewers see the change, but the broadcast never drops. ## Ask the assistant The [AI assistant](/docs/scenes/ai-assistant) can run the show too: take a frame to air, cue the next frame so the switch is instant, skip a playlist to its next track, set a control or start an ad break. It asks for your approval before each of these. ## Automate it A show can switch frames on its own: on a schedule you set (for example, a "Starting soon" frame before the hour), when data changes, or when a control flips. Ask the assistant to set this up, or write a script in **Scripts**. # Web pages Source: https://streamloop.app/docs/scenes/web-pages Show any public web page in a Streamloop scene: alerts, chats, dashboards and visualizers, with custom CSS and a transparent background. > [!NOTE] > > To use Scenes, choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). A **Web page** source puts a live web page on air, like a browser source in OBS. Use it for alerts, chat overlays, live dashboards, visualizers or any page with a public URL. ## Add a web page **1. Add the source** In **Sources & layers**, click **Add source** and pick **Web page**. Drag it onto a frame. **2. Enter the URL** Enter the page's **URL**. It must be a public `http://` or `https://` address. **3. Adjust the look** Size the layer on the canvas. The page renders at the size of the layer that shows it. **4. Publish** [Publish](/docs/scenes/build-a-scene#publish) the scene to put the page on air. ## Settings * **Transparent**: clears the page's own background, so parts of the page with nothing on them show the scene behind. Turn it on for overlays and alerts. When it's off, the page sits on white. * **Custom CSS**: styles added to the page as its last stylesheet, for example to hide a header or change a font. Up to 60 KB. * **Reload**: reloads the page on air at the interval you pick. Use it for pages that don't update themselves. * Scrollbars are always hidden. ## What you see is what airs The canvas preview is a picture taken by the same renderer that plays the page on air, with your custom CSS. If the preview looks right, the stream looks the same. ## If the page doesn't show * Check that the URL opens in a private browser window without signing in. Streamloop can only show public pages. * Some pages take a while to draw their first picture. Give them a moment after you publish. # Smart order Source: https://streamloop.app/docs/smart-order Describe in plain words how your playlist should play (an intro first, a jingle every few songs, the news on the hour) and Streamloop builds the order for you. **In order** and **Shuffle** cover most channels. When you need a rule that's a little smarter, use **Smart order**: you describe the order in plain words, and an AI assistant builds it. You preview the result, then apply it to your playlist. This page assumes you already have a playlist. See [Build and schedule your playlist](/docs/build-and-schedule-your-playlist). ## What you can ask for The dashboard suggests a few to start from: * "Play the first video once as an intro, then play the rest." * "Play everything in order, then shuffle on every loop after." * "Shuffle, but keep several videos between any repeats." * "Play the longest videos first, then the shorter ones." * "Always end each loop with the same outro video." You can also combine rules, refer to videos by name, or use the time of day ("play the morning clips before noon"). ## Create a smart order **1. Open Loop behavior** In your loop, open the **Playlist** tab. Under **Loop behavior**, choose **Smart order**. **2. Describe the order** Under **Describe how you want your playlist to play**, pick a suggestion or type your own. Be specific about edge cases. "Play the intro once at the start, then loop the rest in order, never repeating the intro" works better than "play the intro first." **3. Refine it** The assistant replies in a chat. If it asks a question, answer it. To change the result, send another message: **Refine — add a rule, swap, or start over**. **4. Check the preview** The preview lists the items in the order they will play. After you change your playlist, click **Refresh preview** to see the new order. **5. Apply it** Click **Apply this order**. On a live loop, the new order takes over at the next item, with no restart. > [!NOTE] > > Each message to the Smart order assistant costs about **\$0.005** in credits. ### If an item is missing If your order mentions a video by name that isn't in the playlist, Streamloop still applies it and shows **Order advisories** that list what's missing. Add the item, rename it to match, or ask the assistant for a version that doesn't need it. ## Scheduled breaks > [!NOTE] > > Scheduled breaks are rolling out gradually and may not be available in your workspace yet. A scheduled break interrupts the playlist at a set time, even in the middle of a long video. For example: "Play the news video at the top of every hour." At the break, the current video stops, the break plays, and then the interrupted video continues from where it stopped. * Describe the break as part of your order. The preview shows each break under **Scheduled breaks**. * Times follow your own time zone. * Breaks work on the **video** playlist only. To run breaks only during part of the day (for example 06:00–22:00), combine a smart order with a [repeating schedule](/docs/build-and-schedule-your-playlist#repeat-a-schedule). ## Use it on the audio playlist You can apply a smart order to the **Audio** playlist instead, for example "shuffle the songs and play the station jingle every three or four tracks." A loop can use one smart order at a time, on either its video or its audio playlist. To move it, click **Revert** on the playlist that has it first. ## Turn it off or remove it * **Revert** takes the smart order off this playlist. The playlist goes back to a built-in order, and the saved order stays in **Your saved orders** for later. * **Remove order** deletes a saved order. You can't remove an order that a playlist still uses: switch those playlists to a different order first. ## If something goes wrong while you're live A smart order can never take your stream off the air. If the order hits a problem while picking the next item, that slot plays in the playlist's natural order instead. Streamloop tries the smart order again after a few items. ## Next steps * [Build and schedule your playlist](/docs/build-and-schedule-your-playlist): uploads, loop behavior and editing while live. * [Build a 24/7 music or lofi channel](/docs/lofi-music-channel): a smart order with jingles suits radio channels. # Use Streamloop with AI Source: https://streamloop.app/docs/use-with-ai Connect Claude, ChatGPT, Cursor or Claude Code to Streamloop and run your loops, destinations and scenes by asking in plain words. You can connect an AI assistant such as Claude, ChatGPT or Cursor to your Streamloop account. Then you describe what you want ("loop these videos to YouTube every weekday at 9") and the assistant does the steps for you: uploads, playlists, destinations, schedules and scenes. The assistant connects through Streamloop's **MCP server**. MCP (Model Context Protocol) is the standard way AI apps use outside tools. You sign in with your Streamloop account, so there's no key to copy, and you choose what the assistant may do. For a short tour of what you can ask for, see the [live streaming MCP server page](https://streamloop.app/features/mcp). > [!NOTE] > > The scene features below work in every workspace. Choose **Scene** when you create a loop. See [Scenes](/docs/scenes/overview). ## Connect your assistant The server address is `https://mcp.streamloop.app/mcp`. **Claude** 1. In Claude, open **Settings → Connectors**. 2. Choose **Add custom connector** and enter `https://mcp.streamloop.app/mcp`. 3. Connect it, sign in to Streamloop, and approve the access. **ChatGPT** 1. In ChatGPT, add a custom connector (MCP server) with the URL `https://mcp.streamloop.app/mcp`. 2. Sign in to Streamloop when ChatGPT asks, and approve the access. **Cursor** Add the server to your Cursor MCP settings (`.cursor/mcp.json` in a project, or `~/.cursor/mcp.json` for all projects): ```json theme={null} { "mcpServers": { "streamloop": { "url": "https://mcp.streamloop.app/mcp" } } } ``` Cursor opens the Streamloop sign-in page the first time it connects. **Claude Code** Install the Streamloop plugin. It adds the MCP server and the Streamloop skills (see below): ```text theme={null} /plugin marketplace add streamloop/skills /plugin install streamloop@streamloop ``` Then run `/mcp`, pick **streamloop**, and sign in. **Other apps** Any app that supports remote MCP servers over Streamable HTTP with OAuth works. Add `https://mcp.streamloop.app/mcp` and sign in when asked. Technical details are in the [MCP server reference](/docs/api-reference/mcp/overview). ### Choose what the assistant may do Your assistant asks for a set of permissions when you connect it. The sign-in screen lists them, and you allow or deny them together: | Permission | The assistant can | | - | - | | **Read** (`streamloop:read`) | See your loops, destinations, playlists, uploads, scenes and credit balance. | | **Write** (`streamloop:write`) | Also create and change them, and start and stop loops. | | **Delete** (`streamloop:destructive`) | Also delete loops, uploads and scenes for good. | You can't pick some of them on that screen: **Allow** gives the assistant the whole list, and **Deny** gives it nothing. The assistant can't buy credits or spend money: top-ups happen in the [dashboard](https://streamloop.app/loops). You can see and revoke connected assistants any time in the **Connected Apps** panel. See [API tokens and connected apps](/docs/account-access). ### Workspaces The assistant works in one [workspace](/docs/workspaces-and-team) at a time, your default one at first. If you're in several, say which one: "switch to my team's workspace". If a loop you know exists doesn't show up, the assistant is probably in another workspace. ## The Streamloop skills The skills are instructions an assistant loads when a task needs them, the same guides Streamloop's own studio [AI assistant](/docs/scenes/ai-assistant) uses. They are public at [github.com/streamloop/skills](https://github.com/streamloop/skills). Claude Code gets them with the plugin above; any other agent that reads skills gets them with: ```bash theme={null} npx skills add streamloop/skills ``` | Skill | Helps with | | - | - | | `running-24-7-loops` | Uploading or importing videos and music, filling and ordering playlists, scheduling (once, daily, weekly), starting, and changing a live loop's playlist without going off air. | | `streaming-to-destinations` | YouTube (connected channel or stream key), Twitch, Facebook, Kick, X, TikTok, LinkedIn, Telegram or any RTMP server, and multistreaming to several at once. | | `managing-workspaces-and-credits` | Which workspace is in use, switching workspaces, the credit balance and how long loops can keep running, and past usage. | | `building-scenes` | A scene from nothing to on air: overlays, lower thirds, score bugs, tickers, weather or news corners, live data, cameras. | | `composing-frames` | The layout of a scene's frames: colour, type and broadcast conventions, from a description or a reference picture, checked with a picture. | | `animating-frames` | How layers enter and leave, tweens, rolling numbers and how a frame transitions in. | | `shaping-data` | Tables from typed rows, files, URLs, Google Sheets or web pages, and formulas that filter, sort or join them. | | `binding-live-data` | Showing live values in layers: show state, table rows, now playing, controls, the clock. | | `writing-components` | Reusable lower thirds, score bugs and cards. | | `scripting-the-show` | Timers, reactions to data and feeds, the operator's controls (toggles, buttons, choices) and state. | | `handling-media` | RTMP, RTMPS, SRT, RTSP and HLS feeds, IP cameras, files, web pages, playlists, fallbacks when a feed drops, and sound levels. | | `coding-web-pages` | A web page written in code (React, Tailwind) shown as a layer, for things the built-in elements can't make. | | `operating-live-scenes` | Running a scene on air: taking frames to air, setting controls, live corrections, and stream keys for OBS or vMix. | Other assistants get the same tools without the skills. They still work, because the server explains its tools to every assistant. ## What to ask Some prompts to start with: * "Make a 24/7 lofi loop from these three videos and stream it to my YouTube channel." * "Also send it to my Twitch channel." * "Stream my loop every weekday from 9am to 5pm New York time." * "Why won't my stream start?" * "How many credits do I have, and how long will my loops keep running?" * "Build a weather lower third for my scene loop." (scenes) * "Show the top 5 rows of this Google Sheet as a ticker." (scenes) * "Put up the guest's lower third now." (scenes) * "Cut to the intermission frame with a fade." (scenes) ## What to expect * **It asks before anything irreversible.** Deleting a loop, an upload or a scene, discarding a scene's draft, or revoking a stream key needs an explicit confirmation. The assistant should tell you what will happen and wait for your yes. * **It should check with you before starting a loop.** Starting a loop puts video in front of the public. Streamloop tells the assistant to ask you first unless you just asked for the start, but it doesn't block the start itself. * **It asks before changing a scene that's on air.** Publishing a change to a scene that's on air needs a confirmation, because viewers see it at once. Streamloop refuses the publish until the assistant confirms. * **Scene edits stay in the draft until you publish.** If you have the studio open on the scene, you see the assistant's edits live. Nothing goes on air until the assistant publishes, and it can do a dry run first that lists what would change. * **Some steps need you.** Connecting a YouTube account needs you to open a Google consent link. For a local file, the assistant may hand you an upload link if it can't upload the file itself. * **Some limits still apply.** A loop has at most 5 destinations ([multistream](/docs/platform/multistream)) and at most one YouTube channel. You can't add or remove destinations while a loop is live, but you can turn one off. ## Next steps > **[MCP server reference](/docs/api-reference/mcp/overview)** > > Endpoint, permissions, errors and how scene editing works. > **[All MCP tools](/docs/api-reference/mcp/tools)** > > Every tool, what it does and the permission it needs. # Workspaces and team Source: https://streamloop.app/docs/workspaces-and-team Share loops, uploads, destinations and credits with your team in a Streamloop workspace. Invite members, set roles and switch between workspaces. A **workspace** is where your loops live. Everything in it is shared by its members: loops, playlists, uploads, destinations, smart orders, scenes and the credit balance. Each person signs in with their own account, so nobody shares a password. Every account starts with a personal workspace. You can belong to several workspaces and switch between them. ## Switch workspace Open the workspace menu in the dashboard and choose a workspace. The dashboard then shows that workspace's loops, library and billing. ## Invite teammates Owners and admins can invite people. **1. Open the workspace settings** In the workspace menu, choose **Manage workspace**, or **Invite members…**. **2. Invite by email** Under **Invite a teammate**, enter their email, pick a role and click **Invite**. They get an email invitation. Pending invitations appear under **Pending invitations**, where you can **Revoke** one. **3. Or share an invite link** Copy the **Workspace invite link**. Anyone with the link can join as a member. If the link spreads too far, click **Regenerate invite link**: the old link stops working. People you invite see the invitation in their workspace menu and can **Accept** or **Decline** it. ## Roles Every member can manage loops, uploads and schedules. Roles decide who manages the workspace itself: | Role | Can do | | - | - | | **Owner** | Everything, including deleting the workspace and making other members owners. | | **Admin** | Invite people, rename the workspace, and manage members. | | **Member** | Work on loops, uploads, destinations and schedules. | To change a role, open **Members** in **Manage workspace** and pick the new role next to the person. Owners can change anyone except other owners. Admins can manage members. ## Leave or delete a workspace * **Leave workspace** removes you from it. You need another workspace to switch to, and a workspace always keeps at least one owner. * **Delete workspace** is for owners only. It permanently deletes the workspace and all of its loops. You can't delete your only workspace. ## Using the API or AI agents To give your own scripts or AI tools access, see [API tokens and connected apps](/docs/account-access).