From c9506258e6b890b6e8957e3645b592f57bdbd018 Mon Sep 17 00:00:00 2001 From: Taylor Caldwell Date: Tue, 15 Sep 2026 13:43:45 -0700 Subject: [PATCH 1/2] docs: publish Livestream API section Bring the livestream-api/ guides and endpoint pages from the livestream-api branch onto main and add the "Livestream API" product to docs.json navigation. openapi.json and the x-api/broadcasts/* stub pages are intentionally not taken from the branch: main's spec (v2.168) already contains every broadcast change made on that branch, and the branch copy (v2.167) is otherwise stale. --- docs.json | 67 ++++++++ livestream-api/authentication.mdx | 101 ++++++++++++ .../broadcasts/create-broadcast.mdx | 92 +++++++++++ .../broadcasts/delete-broadcast.mdx | 18 +++ .../broadcasts/get-broadcast-by-id.mdx | 15 ++ livestream-api/broadcasts/get-broadcast.mdx | 21 +++ livestream-api/broadcasts/get-broadcasts.mdx | 20 +++ livestream-api/broadcasts/list-broadcasts.mdx | 35 +++++ livestream-api/broadcasts/overview.mdx | 45 ++++++ .../broadcasts/publish-or-end-broadcast.mdx | 113 ++++++++++++++ livestream-api/chat/accessing-live-chat.mdx | 102 ++++++++++++ livestream-api/chat/delete-chat-message.mdx | 13 ++ livestream-api/chat/get-chat-history.mdx | 15 ++ livestream-api/chat/mute-chat-user.mdx | 17 ++ livestream-api/chat/send-chat-message.mdx | 22 +++ livestream-api/chat/unmute-chat-user.mdx | 13 ++ livestream-api/getting-started.mdx | 61 ++++++++ livestream-api/introduction.mdx | 51 ++++++ .../create-scheduled-broadcast.mdx | 69 ++++++++ .../delete-scheduled-broadcast.mdx | 11 ++ .../get-scheduled-broadcast.mdx | 13 ++ .../scheduled-broadcasts/go-live.mdx | 34 ++++ .../list-scheduled-broadcasts.mdx | 21 +++ .../scheduled-broadcasts/overview.mdx | 147 ++++++++++++++++++ .../update-scheduled-broadcast.mdx | 19 +++ livestream-api/sources/create-source.mdx | 89 +++++++++++ livestream-api/sources/delete-source.mdx | 18 +++ .../sources/get-recommended-region.mdx | 32 ++++ livestream-api/sources/get-source.mdx | 24 +++ livestream-api/sources/list-sources.mdx | 21 +++ livestream-api/sources/overview.mdx | 25 +++ livestream-api/sources/update-source.mdx | 22 +++ livestream-api/typical-workflow.mdx | 45 ++++++ 33 files changed, 1411 insertions(+) create mode 100644 livestream-api/authentication.mdx create mode 100644 livestream-api/broadcasts/create-broadcast.mdx create mode 100644 livestream-api/broadcasts/delete-broadcast.mdx create mode 100644 livestream-api/broadcasts/get-broadcast-by-id.mdx create mode 100644 livestream-api/broadcasts/get-broadcast.mdx create mode 100644 livestream-api/broadcasts/get-broadcasts.mdx create mode 100644 livestream-api/broadcasts/list-broadcasts.mdx create mode 100644 livestream-api/broadcasts/overview.mdx create mode 100644 livestream-api/broadcasts/publish-or-end-broadcast.mdx create mode 100644 livestream-api/chat/accessing-live-chat.mdx create mode 100644 livestream-api/chat/delete-chat-message.mdx create mode 100644 livestream-api/chat/get-chat-history.mdx create mode 100644 livestream-api/chat/mute-chat-user.mdx create mode 100644 livestream-api/chat/send-chat-message.mdx create mode 100644 livestream-api/chat/unmute-chat-user.mdx create mode 100644 livestream-api/getting-started.mdx create mode 100644 livestream-api/introduction.mdx create mode 100644 livestream-api/scheduled-broadcasts/create-scheduled-broadcast.mdx create mode 100644 livestream-api/scheduled-broadcasts/delete-scheduled-broadcast.mdx create mode 100644 livestream-api/scheduled-broadcasts/get-scheduled-broadcast.mdx create mode 100644 livestream-api/scheduled-broadcasts/go-live.mdx create mode 100644 livestream-api/scheduled-broadcasts/list-scheduled-broadcasts.mdx create mode 100644 livestream-api/scheduled-broadcasts/overview.mdx create mode 100644 livestream-api/scheduled-broadcasts/update-scheduled-broadcast.mdx create mode 100644 livestream-api/sources/create-source.mdx create mode 100644 livestream-api/sources/delete-source.mdx create mode 100644 livestream-api/sources/get-recommended-region.mdx create mode 100644 livestream-api/sources/get-source.mdx create mode 100644 livestream-api/sources/list-sources.mdx create mode 100644 livestream-api/sources/overview.mdx create mode 100644 livestream-api/sources/update-source.mdx create mode 100644 livestream-api/typical-workflow.mdx diff --git a/docs.json b/docs.json index 49d1019e4..1d94f7ba9 100644 --- a/docs.json +++ b/docs.json @@ -1231,6 +1231,73 @@ } ] }, + { + "product": "Livestream API", + "tabs": [ + { + "tab": "Documentation", + "groups": [ + { + "group": "Overview", + "pages": [ + "livestream-api/introduction", + "livestream-api/getting-started", + "livestream-api/typical-workflow", + "livestream-api/authentication" + ] + }, + { + "group": "Stream Sources", + "pages": [ + "livestream-api/sources/overview", + "livestream-api/sources/get-recommended-region", + "livestream-api/sources/create-source", + "livestream-api/sources/list-sources", + "livestream-api/sources/get-source", + "livestream-api/sources/update-source", + "livestream-api/sources/delete-source" + ] + }, + { + "group": "Broadcasts", + "pages": [ + "livestream-api/broadcasts/overview", + "livestream-api/broadcasts/create-broadcast", + "livestream-api/broadcasts/list-broadcasts", + "livestream-api/broadcasts/get-broadcasts", + "livestream-api/broadcasts/publish-or-end-broadcast", + "livestream-api/broadcasts/delete-broadcast", + "livestream-api/broadcasts/get-broadcast", + "livestream-api/broadcasts/get-broadcast-by-id" + ] + }, + { + "group": "Scheduled Broadcasts", + "pages": [ + "livestream-api/scheduled-broadcasts/overview", + "livestream-api/scheduled-broadcasts/create-scheduled-broadcast", + "livestream-api/scheduled-broadcasts/list-scheduled-broadcasts", + "livestream-api/scheduled-broadcasts/get-scheduled-broadcast", + "livestream-api/scheduled-broadcasts/update-scheduled-broadcast", + "livestream-api/scheduled-broadcasts/delete-scheduled-broadcast", + "livestream-api/scheduled-broadcasts/go-live" + ] + }, + { + "group": "Live Chat", + "pages": [ + "livestream-api/chat/accessing-live-chat", + "livestream-api/chat/get-chat-history", + "livestream-api/chat/send-chat-message", + "livestream-api/chat/mute-chat-user", + "livestream-api/chat/unmute-chat-user", + "livestream-api/chat/delete-chat-message" + ] + } + ] + } + ] + }, { "product": "Enterprise", "hidden": true, diff --git a/livestream-api/authentication.mdx b/livestream-api/authentication.mdx new file mode 100644 index 000000000..bb87df941 --- /dev/null +++ b/livestream-api/authentication.mdx @@ -0,0 +1,101 @@ +--- +title: Authentication +sidebarTitle: Authentication +description: "The Livestream API supports OAuth 2.0 Authorization Code Flow with PKCE (recommended) and OAuth 1.0a 3-legged user context. Learn the requirements for both flows." +keywords: ["Livestream authentication", "OAuth 2.0", "PKCE", "broadcast.read", "broadcast.write", "OAuth 1.0a", "HMAC-SHA1", "user context"] +--- + +All Livestream endpoints support both **OAuth 2.0 Authorization Code Flow with PKCE** and **OAuth 1.0a 3-legged (user context)**. **OAuth 2.0 is strongly recommended** for all new integrations. + +## OAuth 2.0 (recommended) + +Use **OAuth 2.0 Authorization Code Flow with PKCE** to obtain a user-context access token, then send it as a Bearer token on every request. + +### Requirements + +- Your application must be allow-listed for the Livestream API (contact your X partner manager). +- Request the `broadcast.read` and `broadcast.write` scopes at minimum — these cover most operations. Add `offline.access` if you need a refresh token. +- The authorizing user's numeric ID must exactly match the `:user_id` in the path. Mismatches are rejected with `400 Bad Request`. + +### Scopes + +| Scope | Purpose | +|:------|:--------| +| `broadcast.read` | Read stream sources, broadcasts, scheduled broadcasts, and chat history. | +| `broadcast.write` | Create, update, publish, and end broadcasts and scheduled broadcasts; manage sources; send and moderate chat messages. | +| `offline.access` | Issue a refresh token so you can renew the access token without re-prompting the user. | + +### Helpful additional scopes + +Request these alongside the core Livestream scopes when your integration needs the related functionality. + +| Scope | Purpose | +|:------|:--------| +| `tweet.read` | Useful for reading the user's Post that carries the broadcast information. | +| `tweet.write` | Helpful when posting clips, reposting the stream from other accounts, and similar publishing. | +| `like.read` | Required to subscribe to the user's `like.create` events in the X Activity API, enabling like notifications. | +| `follows.read` | Required to subscribe to the user's `follow.follow` and `subscriptions.subscribe` events, useful for common stream overlays. | + +### Sending requests + +Include the access token in the `Authorization` header: + +``` +Authorization: Bearer YOUR_ACCESS_TOKEN +``` + +In the cURL examples throughout this documentation, `-H "Authorization: Bearer $ACCESS_TOKEN"` uses an access token obtained via this flow. + +For the full flow (authorization URL, PKCE challenge, token exchange, refresh), see the platform-wide guide on [OAuth 2.0 Authorization Code Flow with PKCE](/fundamentals/authentication/oauth-2-0/authorization-code). + +--- + +## OAuth 1.0a (also supported) + +OAuth 1.0a 3-legged user context is still accepted for existing integrations. + +### Requirements + +- Use OAuth 1.0a with **HMAC-SHA1** signatures (3-legged user context). +- Include a properly constructed `Authorization: OAuth ...` header on every request. +- Your application must be allow-listed for the Livestream API. +- The application must have **Read + Write** (or ReadWriteDm) access level. +- The numeric user ID from the OAuth 1.0a access token must exactly match the `:user_id` in the path. Mismatches are rejected with `400 Bad Request`. + +### Signing requests + +You sign each request yourself using your consumer key/secret and access token/secret. Every request must include an `Authorization: OAuth ...` header built from the standard OAuth 1.0a parameters: + +| Parameter | Value | +|:----------|:------| +| `oauth_consumer_key` | Your app's consumer (API) key | +| `oauth_token` | The broadcasting user's access token | +| `oauth_signature_method` | `HMAC-SHA1` | +| `oauth_timestamp` | Current Unix time in seconds | +| `oauth_nonce` | Unique random string, one per request | +| `oauth_version` | `1.0` | +| `oauth_signature` | Base64-encoded HMAC-SHA1 signature of the request | + +The signature base string is three parts joined by `&`: the uppercased HTTP method, the percent-encoded base request URL (scheme, host, and path — **no query string**), and the percent-encoded, sorted parameter string. + +**What goes into the parameter string:** + +- All the `oauth_*` parameters above (except `oauth_signature`) +- Any URL query-string parameters (for example `pagination_token` when listing broadcasts) + + +**Do not include the JSON request body.** Every write endpoint here sends `application/json`, and under OAuth 1.0a only `application/x-www-form-urlencoded` bodies are folded into the signature — adding JSON body fields to the base string is the single most common cause of `401 Unauthorized`. + + +The signing key is `percentEncode(consumer_secret)&percentEncode(token_secret)`. Use strict RFC 3986 percent-encoding throughout: also escape `! * ' ( )`, and encode spaces as `%20` (never `+`). Many built-in helpers (e.g. JavaScript's `encodeURIComponent`) are not strict enough on their own. + +A finished header looks like this (sent as a single line): + +``` +Authorization: OAuth oauth_consumer_key="CONSUMER_KEY", +oauth_token="ACCESS_TOKEN", oauth_signature_method="HMAC-SHA1", +oauth_timestamp="1772031973", oauth_nonce="a1b2c3d4e5", +oauth_version="1.0", oauth_signature="bYsf2K%2Fa9c0vR..." +``` + +For more detail on OAuth 1.0a itself, see the platform-wide guide on [authorizing a request](/fundamentals/authentication/oauth-1-0a/authorizing-a-request) and [creating a signature](/fundamentals/authentication/oauth-1-0a/creating-a-signature). diff --git a/livestream-api/broadcasts/create-broadcast.mdx b/livestream-api/broadcasts/create-broadcast.mdx new file mode 100644 index 000000000..c680cdfde --- /dev/null +++ b/livestream-api/broadcasts/create-broadcast.mdx @@ -0,0 +1,92 @@ +--- +title: Create Broadcast +sidebarTitle: Create Broadcast +description: "POST /2/users/:user_id/broadcasts creates a new broadcast bound to an existing stream source. Broadcasts start in the NOT_STARTED state." +keywords: ["create broadcast", "POST broadcasts", "media_key", "share_url", "is_low_latency"] +--- + +Creates a new broadcast bound to an existing source. Starts in `NOT_STARTED`. + +**Endpoint:** `POST /2/users/:user_id/broadcasts` + + +The source must already be actively receiving an RTMP feed (`is_stream_active: true`), or this endpoint returns a 404. See the [broadcast lifecycle overview](/livestream-api/broadcasts/overview). + + +## Request body + +```json +{ + "source_id": "6ep48v6ar5q4", + "region": "eu-central-1", + "is_low_latency": true +} +``` + +| Field | Type | Required | Description | +|:------|:-----|:---------|:------------| +| `source_id` | string | Yes | ID of an existing source owned by this user. | +| `region` | string | Yes | Must match the source's region. | +| `is_low_latency` | boolean | No | Requests low-latency mode (lower delay for interactive/chatty broadcasts, trading some quality/stability). If omitted, defaults to `false` (standard latency); recommended to set explicitly. Note: the returned `is_high_latency` field is not a reliable inverse of this value (it has been observed to stay `true` regardless), so treat `is_high_latency` as informational only. | + +## Example request + +```bash +curl -X POST "https://api.x.com/2/users/172483972/broadcasts" \ + -H "Authorization: Bearer $USER_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"source_id":"6ep48v6ar5q4","region":"eu-central-1","is_low_latency":true}' +``` + +## Success response + +```json +{ + "broadcast": { + "id": "1AxRnanzLOrxl", + "broadcast_id": "1AxRnanzLOrxl", + "media_key": "28_2026675106832068613", + "media_id": "2026675106832068613", + "created_at_ms": "1772031973914", + "updated_at_ms": "1772031973914", + "broadcast_source": "producer", + "available_for_replay": false, + "user_id": "1DYEXPNqZwqEg", + "twitter_user_id": "172483972", + "user_display_name": "Example User", + "username": "example", + "twitter_username": "example", + "profile_image_url": "https://pbs.twimg.com/profile_images/..._reasonably_small.jpeg", + "state": "NOT_STARTED", + "is_locked": false, + "friend_chat": false, + "has_moderation": true, + "height": 1080, + "width": 1920, + "camera_rotation": 0, + "has_location": false, + "lat": 0.0, + "lng": 0.0, + "private_chat": false, + "chat_option": 0, + "no_hearts": false, + "is_high_latency": true + }, + "video_access": { + "hls_url": "https://prod-fastly-eu-central-1.video.pscp.tv/.../ps_dynamic_hls-producer.m3u8?type=live", + "https_hls_url": "https://prod-fastly-eu-central-1.video.pscp.tv/.../ps_dynamic_hls-producer.m3u8?type=live" + }, + "share_url": "https://x.com/i/broadcasts/1AxRnanzLOrxl" +} +``` + +## Key fields returned at creation + +- `media_key` / `media_id`: Required later for [chat access](/livestream-api/chat/accessing-live-chat) and some playback. +- `video_access`: Playback URLs (HLS variants). `lhls*` and `replay_url` may appear later or for certain modes. +- `share_url`: Public viewer link (`https://x.com/i/broadcasts/{id}`). +- `state`: Always starts `"NOT_STARTED"`. +- `is_high_latency`: Informational; does not reliably reflect the `is_low_latency` you sent (see the note on the `is_low_latency` field above). +- `twitter_user_id` vs `user_id` vs `owner_id`: `twitter_user_id` is your numeric X user id (returned as a string) — use this for the `:user_id` path parameter in every endpoint. `user_id` (e.g. `"1DYEXPNqZwqEg"`) is a separate alphanumeric livestream-platform id; do not use it in paths. Note also that the Source object's `owner_id` is delivered as a JSON number and will lose precision for real (19-digit) user IDs — never rely on it; use `twitter_user_id` or your authenticated id instead. + +After creation, start pushing your RTMP feed to the source's `rtmps_url` + `rtmp_stream_key` if you haven't already, then [publish](/livestream-api/broadcasts/publish-or-end-broadcast). diff --git a/livestream-api/broadcasts/delete-broadcast.mdx b/livestream-api/broadcasts/delete-broadcast.mdx new file mode 100644 index 000000000..352542cf3 --- /dev/null +++ b/livestream-api/broadcasts/delete-broadcast.mdx @@ -0,0 +1,18 @@ +--- +title: Delete Broadcast +sidebarTitle: Delete Broadcast +description: "DELETE /2/users/:user_id/broadcasts/:broadcast_id deletes the broadcast record and associated metadata." +keywords: ["delete broadcast", "DELETE broadcast", "cleanup"] +--- + +Deletes the broadcast record and associated metadata. Use for cleanup of test or unwanted broadcasts. + +**Endpoint:** `DELETE /2/users/:user_id/broadcasts/:broadcast_id` + +## Response + +Typically `{ "success": true }` or a minimal body on `200`. + + +Don't depend on the exact body shape of delete responses — treat any 2xx as success. + diff --git a/livestream-api/broadcasts/get-broadcast-by-id.mdx b/livestream-api/broadcasts/get-broadcast-by-id.mdx new file mode 100644 index 000000000..e7f3b5e2c --- /dev/null +++ b/livestream-api/broadcasts/get-broadcast-by-id.mdx @@ -0,0 +1,15 @@ +--- +title: Get Broadcast by ID +sidebarTitle: Get Broadcast by ID +description: "GET /2/broadcasts/:id returns a broadcast owned by the authenticated user, looked up by broadcast ID alone." +keywords: ["get broadcast by id", "GET broadcasts id", "broadcast lookup", "broadcast.fields"] +openapi: "get /2/broadcasts/{id}" +--- + +Returns a broadcast owned by the authenticated user, looked up by its alphanumeric `broadcast_id` alone. This route replaces the deprecated [Get Broadcast](/livestream-api/broadcasts/get-broadcast) (`GET /2/users/:user_id/broadcasts/:broadcast_id`) — the owning user ID is no longer part of the path, and ownership is still enforced through the authenticated user context. + +Use `broadcast.fields` to select which Broadcast fields to return. Many fields (image URLs, `start_ms`, `tweet_id`, viewer counts) only populate after publish or later in the lifecycle. + + +The announcement post is created asynchronously after publish, so `tweet_id` is typically not present immediately — re-fetch the broadcast a moment later to read it. + diff --git a/livestream-api/broadcasts/get-broadcast.mdx b/livestream-api/broadcasts/get-broadcast.mdx new file mode 100644 index 000000000..99b3799e0 --- /dev/null +++ b/livestream-api/broadcasts/get-broadcast.mdx @@ -0,0 +1,21 @@ +--- +title: Get Broadcast +sidebarTitle: Get Broadcast +description: "GET /2/users/:user_id/broadcasts/:broadcast_id returns full current metadata for one broadcast, including live viewer counts and the announcement post ID." +keywords: ["get broadcast", "broadcast lookup", "viewer counts", "tweet_id", "total_watching"] +tag: "DEPRECATED" +--- + +**Endpoint:** `GET /2/users/:user_id/broadcasts/:broadcast_id` + + +**This route is deprecated.** Use [`GET /2/broadcasts/:id`](/livestream-api/broadcasts/get-broadcast-by-id) instead, which omits the user ID from the path — the broadcast must still be owned by the authenticated user. + + +Returns full current metadata for one broadcast, including live viewer counts (`total_watching` / `total_watched`), `tweet_id` once published, image thumbnails, and more. + +Many Broadcast fields (image URLs, `start_ms`, `tweet_id`, viewer counts, status title) only populate after publish or later in the lifecycle. + + +The announcement post is created asynchronously after publish, so `tweet_id` is typically not present in the publish response — re-fetch the broadcast with this endpoint a moment later to read it. + diff --git a/livestream-api/broadcasts/get-broadcasts.mdx b/livestream-api/broadcasts/get-broadcasts.mdx new file mode 100644 index 000000000..66262f213 --- /dev/null +++ b/livestream-api/broadcasts/get-broadcasts.mdx @@ -0,0 +1,20 @@ +--- +title: Get Broadcasts +sidebarTitle: Get Broadcasts +description: "GET /2/broadcasts returns the authenticated user's broadcasts, or bulk-looks up broadcasts by ID with the ids query parameter." +keywords: ["get broadcasts", "list broadcasts", "GET broadcasts", "ids", "bulk lookup", "broadcast.fields"] +openapi: "get /2/broadcasts" +--- + +Returns broadcasts owned by the authenticated user. This route replaces the deprecated [List Broadcasts](/livestream-api/broadcasts/list-broadcasts) (`GET /2/users/:user_id/broadcasts`) — the owning user ID is no longer part of the path, and ownership is still enforced through the authenticated user context. + +The endpoint has two modes: + +- **List** — call it with no `ids` to page through the authenticated user's broadcasts using `pagination_token`. +- **Bulk lookup** — pass a comma-separated `ids` list (up to 100) to retrieve those specific broadcasts in a single request. Every returned broadcast must be owned by the authenticated user; IDs the caller does not own are omitted from `data` and reported in `errors`. + +Use `broadcast.fields` to select which Broadcast fields to return. Many fields (image URLs, `start_ms`, `tweet_id`, viewer counts) only populate after publish or later in the lifecycle. + + +The announcement post is created asynchronously after publish, so `tweet_id` is typically not present immediately — re-fetch the broadcast a moment later to read it. + diff --git a/livestream-api/broadcasts/list-broadcasts.mdx b/livestream-api/broadcasts/list-broadcasts.mdx new file mode 100644 index 000000000..2cc579dae --- /dev/null +++ b/livestream-api/broadcasts/list-broadcasts.mdx @@ -0,0 +1,35 @@ +--- +title: List Broadcasts +sidebarTitle: List Broadcasts +description: "GET /2/users/:user_id/broadcasts returns the user's broadcasts with cursor pagination (up to 100 per page)." +keywords: ["list broadcasts", "GET broadcasts", "pagination_token", "cursor pagination"] +tag: "DEPRECATED" +--- + +**Endpoint:** `GET /2/users/:user_id/broadcasts` + + +**This route is deprecated.** Use [`GET /2/broadcasts`](/livestream-api/broadcasts/get-broadcasts) instead, which omits the user ID from the path, supports OAuth 2.0 user context, and adds bulk lookup by `ids`. Ownership is still enforced through the authenticated user context. + + +Supports cursor pagination. + +## Query parameters + +| Name | Type | Required | Description | +|:-----|:-----|:---------|:------------| +| `pagination_token` | string | No | `nextToken.offset` (or `previousToken.offset`) from a prior response. | + +## Response (example) + +```json +{ + "broadcasts": [ + { ...broadcast object... } + ], + "nextToken": { "offset": "..." }, + "previousToken": { "offset": "..." } +} +``` + +Returns up to **100 broadcasts per page** (the page size is fixed and not client-configurable). Use the `offset` string from `nextToken` as the next `pagination_token` to fetch the following page. diff --git a/livestream-api/broadcasts/overview.mdx b/livestream-api/broadcasts/overview.mdx new file mode 100644 index 000000000..e65537642 --- /dev/null +++ b/livestream-api/broadcasts/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Broadcast Lifecycle +sidebarTitle: Overview +description: "Broadcasts are ephemeral live sessions tied to a stream source, moving through NOT_STARTED, RUNNING, and ENDED states." +keywords: ["broadcast lifecycle", "broadcast states", "NOT_STARTED", "RUNNING", "ENDED", "go live"] +--- + +Broadcasts are ephemeral sessions tied to a source. A broadcast starts in `NOT_STARTED`, moves to `RUNNING` when published, and ends in `ENDED` (or another terminal state). + +## Broadcast states + +| State | Meaning | +|:------|:--------| +| `NOT_STARTED` | Created but not yet live. Initial state returned by Create Broadcast. | +| `PRE_PUBLISHED` | Transitional state while the broadcast is being prepared to go live. | +| `RUNNING` | Live and publicly visible. Set after a successful `PUBLISH`. | +| `ENDED` | Finished normally after `END`. Terminal — a broadcast cannot be restarted. | +| `TIMED_OUT` | Terminal. The broadcast was not published in time, or ingest stopped before it went live. | +| `CANCELED` | Terminal. The broadcast was canceled before or during its lifecycle. | + +The only transitions you trigger directly are `NOT_STARTED` → `RUNNING` (via `PUBLISH`) and `RUNNING` → `ENDED` (via `END`). The remaining states are set by the platform. + + +**Critical rule:** The chosen stream source must already be actively receiving an RTMP feed (`is_stream_active: true`) **before** you call Create Broadcast — otherwise you get a 404. There is a short delay after your encoder connects before this flips `true`, so poll `GET /2/users/:user_id/sources/:source_id` until `is_stream_active` is `true` before creating the broadcast. + + +## Endpoints + +| Endpoint | Method & Path | New Method & Path | +|:---------|:--------------|:------------------| +| Create Broadcast | [`POST /2/users/:user_id/broadcasts`](/livestream-api/broadcasts/create-broadcast) | — | +| List Broadcasts | [`GET /2/users/:user_id/broadcasts`](/livestream-api/broadcasts/list-broadcasts) | [`GET /2/broadcasts`](/livestream-api/broadcasts/get-broadcasts) | +| Get Broadcast | [`GET /2/users/:user_id/broadcasts/:broadcast_id`](/livestream-api/broadcasts/get-broadcast) | [`GET /2/broadcasts/:id`](/livestream-api/broadcasts/get-broadcast-by-id) | +| Publish or End a Broadcast | [`PUT /2/users/:user_id/broadcasts/:broadcast_id/state`](/livestream-api/broadcasts/publish-or-end-broadcast) | — | +| Delete Broadcast | [`DELETE /2/users/:user_id/broadcasts/:broadcast_id`](/livestream-api/broadcasts/delete-broadcast) | — | + +All routes accept both OAuth 2.0 and OAuth 1.0a [user context authentication](/livestream-api/authentication). The new routes (those without `user_id` in the path) are the recommended routes and return additional fields (for example, field selection with `broadcast.fields`). + + +The original routes are being replaced as backend work completes and we drift away from the legacy Periscope endpoints. New routes will be added to the table above as they ship — use them in place of the original routes wherever a replacement is available. + + + +To schedule a broadcast in advance — one-time or recurring, with automatic or manual publishing — see the [Livestream Scheduling API](/livestream-api/scheduled-broadcasts/overview). + diff --git a/livestream-api/broadcasts/publish-or-end-broadcast.mdx b/livestream-api/broadcasts/publish-or-end-broadcast.mdx new file mode 100644 index 000000000..bafcf762c --- /dev/null +++ b/livestream-api/broadcasts/publish-or-end-broadcast.mdx @@ -0,0 +1,113 @@ +--- +title: Publish (Go Live) or End a Broadcast +sidebarTitle: Publish or End Broadcast +description: "PUT /2/users/:user_id/broadcasts/:broadcast_id/state publishes a broadcast (go live) with state PUBLISH, or ends it with state END." +keywords: ["publish broadcast", "go live", "end broadcast", "broadcast state", "chat_option", "should_not_tweet"] +--- + +Both actions use the same endpoint with different `state` values. + +**Endpoint:** `PUT /2/users/:user_id/broadcasts/:broadcast_id/state` + +## Publish (state: "PUBLISH") + +Transitions `NOT_STARTED` → `RUNNING`. The broadcast becomes publicly visible on X. By default an announcement post is created. (Set `should_not_tweet: true` to suppress it.) + +### Request body + +```json +{ + "state": "PUBLISH", + "title": "Live from the studio! Q&A time.", + "should_not_tweet": false, + "locale": "en", + "chat_option": 2 +} +``` + +| Field | Type | Required | Description | +|:------|:-----|:---------|:------------| +| `state` | string | Yes | Must be `"PUBLISH"`. | +| `title` | string | No | Broadcast title / status text (also used for the announcement post if sent). Subject to X post length limits. | +| `should_not_tweet` | boolean | No | Set `true` to suppress the automatic announcement post. Default `false` (a post is sent). | +| `locale` | string | No | BCP 47 tag (e.g. `"en"`, `"en_US"`). Affects discovery/recommendation. | +| `chat_option` | integer | No | Controls who can chat — see the values table below. If omitted, the platform applies its default (observed as `3`, verified accounts). | + +### Chat option values + +`chat_option` is an integer enum controlling who can participate in chat: + +| Value | Meaning | +|:------|:--------| +| `0` | None (no option set) | +| `1` | Chat disabled | +| `2` | Everyone | +| `3` | Verified accounts | +| `4` | Accounts the broadcaster follows | +| `5` | The broadcaster's subscribers | + +If you omit `chat_option` when publishing, the platform applies its default (observed as `3`, verified accounts). + +### Example request + +```bash +curl -X PUT "https://api.x.com/2/users/172483972/broadcasts/1AxRnanzLOrxl/state" \ + -H "Authorization: Bearer $USER_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"PUBLISH","title":"Live from the studio! Q&A time.","chat_option":2}' +``` + +### Response (on success) + +```json +{ + "broadcast": { + ... full broadcast object with state: "RUNNING", + "status": "Live from the studio! Q&A time.", + "start_ms": "1772032035735", + "language": "en", + "chat_option": 2, + ... + } +} +``` + +- `start_ms` is set when it becomes live. +- The announcement post is created **asynchronously**. `tweet_id` is typically not present in this publish response — re-fetch the broadcast (`GET .../broadcasts/:broadcast_id`) a moment later to read `tweet_id` once the post exists. +- `tweet_error`: when present, an empty string means the post succeeded and a non-empty value describes why it failed (the broadcast still goes live regardless). This field may be absent from the response entirely. +- `language` defaults to `"en"` if you don't send `locale`. + +## End (state: "END") + +Transitions `RUNNING` → `ENDED`. Irreversible; you cannot restart a broadcast. + +### Request body (strict) + +```json +{ + "state": "END" +} +``` + + +Do **not** include `title`, `should_not_tweet`, `locale`, or `chat_option` when ending. The server explicitly rejects them with `400` if any of these fields are present. + + +### Example request + +```bash +curl -X PUT "https://api.x.com/2/users/172483972/broadcasts/1AxRnanzLOrxl/state" \ + -H "Authorization: Bearer $USER_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"END"}' +``` + +### Response + +```json +{ + "success": true +} +``` + +After ending, stop your encoder. A replay may be available later if `available_for_replay` was `true` (or per platform policy). diff --git a/livestream-api/chat/accessing-live-chat.mdx b/livestream-api/chat/accessing-live-chat.mdx new file mode 100644 index 000000000..2a2ce16bc --- /dev/null +++ b/livestream-api/chat/accessing-live-chat.mdx @@ -0,0 +1,102 @@ +--- +title: Accessing Live Chat +sidebarTitle: Accessing Live Chat +description: "Receive live chat messages for your broadcasts in real time by subscribing to the broadcast.chat event on the X Activity API." +keywords: ["live chat", "broadcast.chat", "X Activity API", "XAA", "webhook", "subscription", "broadcast.read"] +--- + +Live chat is delivered through the [X Activity API](/x-api/activity/introduction) (XAA). Subscribe once to the `broadcast.chat` event for a user with the `broadcast.read` scope, and XAA pushes chat messages for every broadcast that user owns to your webhook in real time. There is no per-broadcast subscription and no separate handoff step. + +To send chat messages, use the [Send a Chat Message](/livestream-api/chat/send-chat-message) endpoint. + +## Prerequisites + +- A [developer app](https://developer.x.com/en/portal/petition/essential/basic-info) in a Project with the X Activity API and Webhooks enabled. +- OAuth 2.0 Authorization Code Flow with PKCE for the broadcasting user, granting the `broadcast.read` scope. See [Authentication](/livestream-api/authentication). +- The X user ID whose broadcasts you want to receive chat for (see [Getting Started](/livestream-api/getting-started)). + + +`broadcast.chat` is a private event and can only be created for users who have authorized your app. + + +## Step 1: Register a webhook + +XAA delivers events over a webhook you host. Create the webhook once per environment and reuse its `webhook_id` for every subscription. + +See the [Webhooks](/x-api/webhooks/introduction) guide for the full flow, including the CRC validation your endpoint must implement. + +## Step 2: Subscribe to `broadcast.chat` for the user + +Create one subscription per broadcasting user. The subscription covers all broadcasts that user owns — you do not need to resubscribe when a new broadcast starts. + +**Endpoint:** `POST https://api.x.com/2/activity/subscriptions` + +**Auth:** OAuth 2.0 user context with the `broadcast.read` scope (the token must belong to the user in `filter.user_id`). + +### Example request + +```bash +curl -X POST "https://api.x.com/2/activity/subscriptions" \ + -H "Authorization: Bearer $USER_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "event_type": "broadcast.chat", + "filter": { "user_id": "1111111111111111111" }, + "webhook_id": "2090847910112202752", + "tag": "live-chat" + }' +``` + +### Request fields + +| Field | Required | Description | +|:------|:---------|:------------| +| `event_type` | Yes | Must be `broadcast.chat`. | +| `filter.user_id` | Yes | The broadcasting user's ID. Must match the authenticated user. | +| `webhook_id` | Yes | ID of the webhook that will receive events. | +| `tag` | No | Caller-defined label echoed back on each event. | + +### Success response + +```json +{ + "data": { + "subscription": { + "subscription_id": "1998240115200000001", + "event_type": "broadcast.chat", + "filter": { "user_id": "1111111111111111111" }, + "webhook_id": "2090847910112202752", + "tag": "live-chat", + "created_at": "2026-09-15T14:30:00.000Z", + "updated_at": "2026-09-15T14:30:00.000Z" + } + } +} +``` + +Store the `subscription_id`; use it to update or delete the subscription later. See [List](/x-api/activity/get-x-activity-subscriptions), [Update](/x-api/activity/update-x-activity-subscription), and [Delete](/x-api/activity/deletes-x-activity-subscription) subscription endpoints for management. + +## Step 3: Receive chat events on your webhook + +Once the broadcast is live and viewers send chat messages, XAA POSTs `broadcast.chat` events to your webhook using the standard XAA event envelope: + +```json +{ + "data": { + "event_uuid": "2080761390344937796", + "filter": { "user_id": "1111111111111111111" }, + "event_type": "broadcast.chat", + "tag": "live-chat", + "payload": { }, + "includes": { } + } +} +``` + +`filter.user_id` identifies the broadcast owner. The `payload` describes the chat message; `includes` may contain related user objects. See [Event payloads](/x-api/activity/event-payloads) for the full shape. + +Respond to each delivery with a `2xx` status within the timeout described in the [Webhooks](/x-api/webhooks/introduction) guide, then process the event asynchronously. + +## Pulling chat after a broadcast ends + +Real-time chat is delivered only while the broadcast is live. To read chat after a broadcast has ended, use [Get Chat History](/livestream-api/chat/get-chat-history). diff --git a/livestream-api/chat/delete-chat-message.mdx b/livestream-api/chat/delete-chat-message.mdx new file mode 100644 index 000000000..3678d06ad --- /dev/null +++ b/livestream-api/chat/delete-chat-message.mdx @@ -0,0 +1,13 @@ +--- +title: Delete a Chat Message +sidebarTitle: Delete a Chat Message +description: "DELETE /2/broadcasts/:id/chat/:message_id removes a specific chat message from a running broadcast." +keywords: ["delete chat message", "broadcast chat moderation", "DELETE broadcasts chat", "live chat", "broadcast.write"] +openapi: "delete /2/broadcasts/{id}/chat/{message_id}" +--- + +Removes a specific chat message from a running broadcast owned by the authenticated user. `:id` is the alphanumeric `broadcast_id`, and `:message_id` is the id of the chat message to delete — the `timestamp` returned by [Send a Chat Message](/livestream-api/chat/send-chat-message). + + +To also stop the author from posting further messages, use [Mute a Chat User](/livestream-api/chat/mute-chat-user), which supports removing the prompting message in the same call. + diff --git a/livestream-api/chat/get-chat-history.mdx b/livestream-api/chat/get-chat-history.mdx new file mode 100644 index 000000000..445b3240f --- /dev/null +++ b/livestream-api/chat/get-chat-history.mdx @@ -0,0 +1,15 @@ +--- +title: Get Chat History +sidebarTitle: Get Chat History +description: "GET /2/broadcasts/:id/chat returns a paginated list of timestamped chat messages for a broadcast owned by the authenticated user, available after the broadcast has finished." +keywords: ["get chat history", "GET broadcasts chat", "live chat", "broadcast chat", "broadcast.read"] +openapi: "get /2/broadcasts/{id}/chat" +--- + +Gets a paginated list of timestamped chat messages for a broadcast. Only accessible after the broadcast has finished. `:id` is the alphanumeric `broadcast_id`, and the authenticated user must own the broadcast. + +Messages are returned newest-first. Use `max_results` (1–200, default 100) to page, and `pagination_token` to continue from the `meta.next_token` returned by the previous response. + + +For live chat ingestion while a broadcast is running, use the [X Activity API](/x-api/activity/introduction) `broadcast.chat` event instead. This endpoint is for retrieving the message history after the broadcast ends. + diff --git a/livestream-api/chat/mute-chat-user.mdx b/livestream-api/chat/mute-chat-user.mdx new file mode 100644 index 000000000..0a17b00a1 --- /dev/null +++ b/livestream-api/chat/mute-chat-user.mdx @@ -0,0 +1,17 @@ +--- +title: Mute a Chat User +sidebarTitle: Mute a Chat User +description: "POST /2/broadcasts/:id/chat/mutes prevents a user from posting further messages in a running broadcast chat, optionally for a bounded time out and with a message removed in the same call." +keywords: ["mute chat user", "broadcast chat moderation", "time out", "POST broadcasts chat mutes", "live chat", "broadcast.write"] +openapi: "post /2/broadcasts/{id}/chat/mutes" +--- + +Prevents a user from posting further messages in a running broadcast chat. The authenticated user must own the broadcast. `:id` is the alphanumeric `broadcast_id`. + +Pass an optional `end_at_ms` in the request body to time the mute out at a specific Unix timestamp in milliseconds. Omit `end_at_ms` to mute the user indefinitely. + +Pass an optional `message_id` in the request body to remove a specific chat message at the same time. This is useful when muting is in response to a particular message. + + +To reverse this, use [Unmute a Chat User](/livestream-api/chat/unmute-chat-user). To remove an individual message without muting its author, use [Delete a Chat Message](/livestream-api/chat/delete-chat-message). + diff --git a/livestream-api/chat/send-chat-message.mdx b/livestream-api/chat/send-chat-message.mdx new file mode 100644 index 000000000..b528c2951 --- /dev/null +++ b/livestream-api/chat/send-chat-message.mdx @@ -0,0 +1,22 @@ +--- +title: Send a Chat Message +sidebarTitle: Send a Chat Message +description: "POST /2/broadcasts/:id/chat posts a plain-text chat message to a live broadcast, attributed to the authenticated user." +keywords: ["send chat message", "POST broadcasts chat", "live chat", "broadcast chat", "broadcast.write"] +openapi: "post /2/broadcasts/{id}/chat" +--- + +Posts a plain-text chat message to a live broadcast. The message is attributed to the authenticated user. `:id` is the alphanumeric `broadcast_id`, and `text` must be from 1 through 140 characters. + +The broadcast must be currently live. Its chat settings and the authenticated user's eligibility determine whether the user may send a message. For example, chat may be disabled or limited to verified users, subscribers, or users followed by the host. + +The response's `timestamp` is the server timestamp of the message in nanoseconds, returned as a decimal string. It also serves as the message id — for example, as the `reply_to` value of a follow-up message or the `message_id` when [deleting the message](/livestream-api/chat/delete-chat-message). + +## Errors + +- A request returns `400 Bad Request` if the broadcast cannot be found, is not currently live, or the message is invalid. +- It returns `403 Forbidden` if the authenticated user is not permitted to chat in the broadcast. + + +This endpoint sends messages only. To receive chat messages, use the read-access WebSocket flow described in [Accessing Live Chat](/livestream-api/chat/accessing-live-chat). + diff --git a/livestream-api/chat/unmute-chat-user.mdx b/livestream-api/chat/unmute-chat-user.mdx new file mode 100644 index 000000000..7424e9b67 --- /dev/null +++ b/livestream-api/chat/unmute-chat-user.mdx @@ -0,0 +1,13 @@ +--- +title: Unmute a Chat User +sidebarTitle: Unmute a Chat User +description: "DELETE /2/broadcasts/:id/chat/mutes/:user_id allows a previously muted user to post messages in a running broadcast chat again." +keywords: ["unmute chat user", "broadcast chat moderation", "DELETE broadcasts chat mutes", "live chat", "broadcast.write"] +openapi: "delete /2/broadcasts/{id}/chat/mutes/{user_id}" +--- + +Allows a muted user to post messages in a running broadcast chat again. The authenticated user must own the broadcast. `:id` is the alphanumeric `broadcast_id`, and `:user_id` is the numeric id of the user to unmute. + + +To mute a user, use [Mute a Chat User](/livestream-api/chat/mute-chat-user). + diff --git a/livestream-api/getting-started.mdx b/livestream-api/getting-started.mdx new file mode 100644 index 000000000..52bfee10c --- /dev/null +++ b/livestream-api/getting-started.mdx @@ -0,0 +1,61 @@ +--- +title: Getting Started +sidebarTitle: Getting Started +description: "Prerequisites for the X Livestream API: app allow-listing, OAuth credentials (OAuth 2.0 recommended, OAuth 1.0a also supported), user permissions, and an RTMP encoder." +keywords: ["Livestream API access", "allow-list", "OAuth 2.0", "OAuth 1.0a", "prerequisites", "RTMP encoder", "getting started"] +--- + +To use the X Livestream API, you need the following: + + + + Your X application (OAuth client) must be explicitly added to the Livestream API allow-list. Contact your X partner manager or account team to request access. + + + All Livestream endpoints support both OAuth 2.0 and OAuth 1.0a. OAuth 2.0 is the recommended method: request the `broadcast.read` and `broadcast.write` scopes at minimum, which cover most operations. OAuth 1.0a is still accepted for existing integrations. Store your credentials securely. + + + The broadcasting account must have permission to go live. Protected/private X accounts are not permitted to create broadcasts. + + + You will need an external encoder (e.g., OBS Studio, ffmpeg, or similar) to push video to the RTMPS ingest URL provided by a stream source. + + + +## Finding your user ID + +The Livestream endpoints enforce that the numeric X user ID from the access token must exactly match the `:user_id` in the URL path. Mismatches are rejected with HTTP `400 Bad Request`. + +A common way to obtain the user ID is to call `GET /2/users/me` using the same credentials you will use for the Livestream endpoints. + + +When using OAuth 1.0a, the numeric user ID is also the prefix of the access token, before the `-` — e.g. `172483972-xxxxx` → `172483972` — so you can avoid an extra request. + + +Example response from `GET /2/users/me`: + +```json +{ + "data": { + "id": "172483972", + "name": "Example User", + "username": "example", + "verified": true, + "verified_type": "blue", + "subscription_type": "Premium", + "public_metrics": { ... }, + "profile_image_url": "..." + } +} +``` + +## Next steps + + + + How to sign requests with OAuth 2.0 or OAuth 1.0a. + + + The end-to-end flow from source to live broadcast. + + diff --git a/livestream-api/introduction.mdx b/livestream-api/introduction.mdx new file mode 100644 index 000000000..6a261d3a4 --- /dev/null +++ b/livestream-api/introduction.mdx @@ -0,0 +1,51 @@ +--- +title: Livestream API +sidebarTitle: Introduction +description: "Create and manage live video broadcasts on X with the Livestream API: persistent RTMP/RTMPS stream sources, the full broadcast lifecycle, and reading and sending live chat messages." +keywords: ["Livestream API", "live video", "broadcast", "RTMP", "RTMPS", "live streaming", "go live", "X live"] +--- + +The X Livestream API enables authorized third-party applications and services to create and manage live video broadcasts on X. + +It provides a complete set of endpoints for the broadcast lifecycle: + +- Managing persistent RTMP/RTMPS **stream sources** (ingest points) +- Creating, publishing (going live), monitoring, and ending **broadcasts** +- Creating and managing **scheduled broadcasts** with one-time and recurring schedules +- **Reading live chat** via the X Activity API +- **Sending chat messages** to live broadcasts +- **Moderating live chat** by removing messages and muting users + +Endpoints are hosted under `https://api.x.com`. + + + + Prerequisites, allow-listing, and credentials. + + + From stream source to live broadcast, step by step. + + + +--- + +## Core concepts + +Two resources form the core of the API: + +- **Stream Source** — A persistent RTMP/RTMPS ingest point (an ingest URL plus a unique stream key). You create a source once for an encoder/region and reuse it across many broadcasts. A source exists independently of any broadcast. +- **Broadcast** — An ephemeral live session bound to a source. A broadcast moves through a lifecycle (`NOT_STARTED` → `RUNNING` → `ENDED`) and represents a single "go live" event that viewers watch on X. + +The typical flow is: create a source → start your encoder → create a broadcast → publish (go live) → end. Sources are reusable; broadcasts are one-time sessions. + +You can also schedule broadcasts in advance — one-time or recurring — with the [Livestream Scheduling API](/livestream-api/scheduled-broadcasts/overview). Scheduled broadcasts publish automatically at their scheduled start time or wait for an explicit go-live request. + +### Key identifiers + +| Identifier | Example | What it is | +|:-----------|:--------|:-----------| +| Source id | `6ep48v6ar5q4` | Identifies a stream source. Also returned as `rtmp_stream_key`. | +| Stream key | `6ep48v6ar5q4` | Push your encoder feed to the source's `rtmps_url` using this key. | +| Broadcast id | `1AxRnanzLOrxl` | Identifies a broadcast (`id` / `broadcast_id`). Used in the broadcast endpoints, as the chat room, and to send chat messages. | +| Scheduled broadcast id | `2075599796786561024` | Identifies a scheduled broadcast (`scheduled_broadcast_id`). Numeric scheduler id required in the body of update requests. | +| Media key | `28_2026675106832068613` | Identifies the media for chat/playback (`media_key`). Used to obtain a chat token. | diff --git a/livestream-api/scheduled-broadcasts/create-scheduled-broadcast.mdx b/livestream-api/scheduled-broadcasts/create-scheduled-broadcast.mdx new file mode 100644 index 000000000..b5fba5e1f --- /dev/null +++ b/livestream-api/scheduled-broadcasts/create-scheduled-broadcast.mdx @@ -0,0 +1,69 @@ +--- +title: Create a Scheduled Broadcast +sidebarTitle: Create Scheduled Broadcast +description: "POST /2/broadcasts/scheduled creates a one-time or recurring scheduled broadcast, with automatic or manual publishing." +keywords: ["create scheduled broadcast", "POST broadcasts scheduled", "recurrence", "manual_publish", "recurring schedule"] +openapi: "post /2/broadcasts/scheduled" +--- + +Creates a one-time or recurring scheduled broadcast owned by the authenticated user. The response returns `201 Created` with the newly created broadcast; for a recurring series, the response is the first occurrence. + +## Required fields + +| Field | Description | +|:------|:------------| +| `source_id` | Existing source ID, equal to the source's `rtmp_stream_key`. See [Stream Sources](/livestream-api/sources/overview). | +| `scheduled_start_ms` | Scheduled start time, Unix epoch milliseconds as a decimal string. | + +## Publishing behavior + +Set `manual_publish` to `true` when you want to publish explicitly with [`POST /2/broadcasts/scheduled/:id/live`](/livestream-api/scheduled-broadcasts/go-live). Otherwise, the scheduler publishes automatically at `scheduled_start_ms`. + +## Optional fields + +- `scheduled_end_ms` sets the scheduled end time as Unix epoch milliseconds. If omitted, the broadcast has no scheduled end and runs until it is stopped or ends manually. +- `title` and `description` set the broadcast's status text and description. +- `thumbnail_media_id` is a numeric media ID used as the pre-live slate image. +- `chat_option` controls chat permissions and is a numeric string. +- `available_for_replay` enables replay after the broadcast ends. +- `is_locked` locks the broadcast. +- `locale` sets the broadcast's locale. +- `telecast_id` associates the broadcast with an existing telecast (numeric string). +- `recurrence` creates a recurring series — see below. + +## Example: one-time broadcast + +```json +{ + "source_id": "c47khpz1zuq9", + "scheduled_start_ms": "1784000000000", + "scheduled_end_ms": "1784003600000", + "title": "Product launch", + "description": "Live from our launch event", + "available_for_replay": true, + "manual_publish": true +} +``` + + +Save both `broadcast_id` (alphanumeric, used in endpoint paths) and `scheduled_broadcast_id` (numeric, required in the body of [update requests](/livestream-api/scheduled-broadcasts/update-scheduled-broadcast)). + + +## Example: recurring schedule + +Add `recurrence` with a `frequency` (`Daily` or `Weekly`) and the number of `repeats` as a decimal string: + +```json +{ + "source_id": "c47khpz1zuq9", + "scheduled_start_ms": "1784000000000", + "scheduled_end_ms": "1784003600000", + "title": "Weekly livestream", + "recurrence": { + "frequency": "Weekly", + "repeats": "4" + } +} +``` + +The response returns the first scheduled occurrence. Use the [list endpoint](/livestream-api/scheduled-broadcasts/list-scheduled-broadcasts) to retrieve all generated occurrences. Recurrence cannot be added or changed later through the [update endpoint](/livestream-api/scheduled-broadcasts/update-scheduled-broadcast). diff --git a/livestream-api/scheduled-broadcasts/delete-scheduled-broadcast.mdx b/livestream-api/scheduled-broadcasts/delete-scheduled-broadcast.mdx new file mode 100644 index 000000000..fe51e47e5 --- /dev/null +++ b/livestream-api/scheduled-broadcasts/delete-scheduled-broadcast.mdx @@ -0,0 +1,11 @@ +--- +title: Delete a Scheduled Broadcast +sidebarTitle: Delete Scheduled Broadcast +description: "DELETE /2/broadcasts/scheduled/:id deletes a scheduled broadcast, with optional roll-forward deletion of subsequent recurring occurrences." +keywords: ["delete scheduled broadcast", "DELETE broadcasts scheduled", "roll_forward"] +openapi: "delete /2/broadcasts/scheduled/{id}" +--- + +Deletes a scheduled broadcast. `:id` is the alphanumeric `broadcast_id`. + +For a recurring schedule, optionally pass `roll_forward=true` to apply the deletion to subsequent occurrences. diff --git a/livestream-api/scheduled-broadcasts/get-scheduled-broadcast.mdx b/livestream-api/scheduled-broadcasts/get-scheduled-broadcast.mdx new file mode 100644 index 000000000..4fbfaed27 --- /dev/null +++ b/livestream-api/scheduled-broadcasts/get-scheduled-broadcast.mdx @@ -0,0 +1,13 @@ +--- +title: Get a Scheduled Broadcast +sidebarTitle: Get Scheduled Broadcast +description: "GET /2/broadcasts/scheduled/:id returns one scheduled broadcast by its alphanumeric broadcast_id." +keywords: ["get scheduled broadcast", "GET broadcasts scheduled id", "scheduled broadcast lookup"] +openapi: "get /2/broadcasts/scheduled/{id}" +--- + +Returns one scheduled broadcast. `:id` is the alphanumeric `broadcast_id`, **not** the numeric `scheduled_broadcast_id`. + + +Poll this endpoint to observe state changes — for example, until the state is `Running` after a [go-live request](/livestream-api/scheduled-broadcasts/go-live), or after the scheduler publishes an automatic broadcast. + diff --git a/livestream-api/scheduled-broadcasts/go-live.mdx b/livestream-api/scheduled-broadcasts/go-live.mdx new file mode 100644 index 000000000..75f688109 --- /dev/null +++ b/livestream-api/scheduled-broadcasts/go-live.mdx @@ -0,0 +1,34 @@ +--- +title: Go Live on a Scheduled Broadcast +sidebarTitle: Go Live +description: "POST /2/broadcasts/scheduled/:id/live publishes a scheduled broadcast that was created or updated with manual_publish: true." +keywords: ["go live", "publish scheduled broadcast", "POST broadcasts scheduled live", "manual_publish"] +openapi: "post /2/broadcasts/scheduled/{id}/live" +--- + +Publishes a scheduled broadcast that was created or updated with `manual_publish: true`. `:id` is the alphanumeric `broadcast_id`. This endpoint has no request body. + + +Calling this endpoint for an automatically published schedule is rejected. + + +## Before calling this endpoint + + + + Create or update the broadcast with `manual_publish: true`. + + + Push video to the broadcast's source. + + + The source must be actively receiving video for the broadcast to publish. + + + +## If a go-live request fails + +- Confirm `manual_publish` is `true`. +- Confirm the source ID is correct. +- Confirm RTMP video is actively reaching the source. +- Confirm the broadcast has not already ended or been deleted. diff --git a/livestream-api/scheduled-broadcasts/list-scheduled-broadcasts.mdx b/livestream-api/scheduled-broadcasts/list-scheduled-broadcasts.mdx new file mode 100644 index 000000000..5acd06439 --- /dev/null +++ b/livestream-api/scheduled-broadcasts/list-scheduled-broadcasts.mdx @@ -0,0 +1,21 @@ +--- +title: List Scheduled Broadcasts +sidebarTitle: List Scheduled Broadcasts +description: "GET /2/broadcasts/scheduled returns scheduled broadcasts owned by the authenticated user, with time-window filtering and pagination." +keywords: ["list scheduled broadcasts", "GET broadcasts scheduled", "pagination_token", "max_results"] +openapi: "get /2/broadcasts/scheduled" +--- + +Returns scheduled broadcasts owned by the authenticated user. + +## Query parameter behavior + +- `max_results` accepts values from 1 through 100 and defaults to 20. +- `oldest_start_time` and `newest_start_time` filter by scheduled start time, as Unix timestamps in milliseconds (inclusive at both ends). +- `pagination_token` is the `meta.next_token` from the previous response. + +If there are no matching broadcasts, the response contains `meta.result_count: 0` and omits `data`. + + +Continue requesting pages until a response contains no data. A `next_token` can be returned for any non-empty page and does not guarantee that another non-empty page exists. + diff --git a/livestream-api/scheduled-broadcasts/overview.mdx b/livestream-api/scheduled-broadcasts/overview.mdx new file mode 100644 index 000000000..10eecc68c --- /dev/null +++ b/livestream-api/scheduled-broadcasts/overview.mdx @@ -0,0 +1,147 @@ +--- +title: Livestream Scheduling API +sidebarTitle: Overview +description: "Create, manage, and publish scheduled broadcasts on X with one-time and recurring schedules, automatic or manual publishing, and OAuth 2.0 support." +keywords: ["Livestream Scheduling API", "scheduled broadcast", "recurring schedule", "manual publish", "broadcast.read", "broadcast.write"] +--- + +Use the Livestream Scheduling API to create, manage, and publish scheduled broadcasts on X. + +The API supports one-time and recurring schedules. A scheduled broadcast is associated with an existing [livestream source](/livestream-api/sources/overview). It can publish automatically at its scheduled start time or wait for an explicit go-live request. + +## Before you begin + +### Authentication + +All requests require user-context authentication. The authenticated user owns the broadcasts accessed through these endpoints. + +Supported authentication methods: + +- **OAuth 2.0 Authorization Code Flow with PKCE.** Read routes require `broadcast.read`. Create, update, delete, and go-live routes require both `broadcast.read` and `broadcast.write`. +- **OAuth 1.0a user context.** List and get routes accept apps with Read, Read and write, or Read, write, and Direct Messages permissions. Create, update, delete, and go-live routes require Read and write or Read, write, and Direct Messages permissions. + +OAuth 2.0 is recommended for new integrations. + + +This differs from the Livestream [management endpoints](/livestream-api/authentication) (sources and broadcasts), which currently accept only OAuth 1.0a. + + +### Base URL + +``` +https://api.x.com +``` + +### Livestream source + +You must [create a livestream source](/livestream-api/sources/create-source) before scheduling a broadcast. Supply the source's `rtmp_stream_key` as `source_id`. + +For a manual go-live request, begin sending RTMP video to the source before calling the [live endpoint](/livestream-api/scheduled-broadcasts/go-live). The source must be active for the broadcast to publish. + +### Timestamps and IDs + +- Timestamp fields use Unix epoch time in milliseconds. +- Send timestamps and numeric IDs as decimal strings to preserve precision. +- The `broadcast_id` is an alphanumeric ID used in endpoint paths. +- The `scheduled_broadcast_id` is a numeric scheduler ID. Save both values returned by create, list, or get. Updates require the numeric ID in the request body. + +## Scheduled broadcast object + +```json +{ + "scheduled_broadcast_id": "2075599796786561024", + "broadcast_id": "1DxLddgepQbxm", + "state": "Created", + "title": "Product launch", + "description": "Live from our launch event", + "source_id": "c47khpz1zuq9", + "scheduled_start_ms": "1784000000000", + "scheduled_end_ms": "1784003600000", + "thumbnail_media_id": "2075599000000000000", + "chat_option": "1", + "locale": "en", + "available_for_replay": true, + "recurring_schedule_id": null, + "telecast_id": null, + "manual_publish": true +} +``` + +Fields that do not apply may be omitted. + +Common `state` values include: + +| State | Meaning | +|:------|:--------| +| `Created` | Scheduled and not yet live | +| `Running` | Currently live | +| `Ended` | The broadcast has ended | +| `Error` | The broadcast could not be started or completed | + +## Endpoints + +| Endpoint | Method & Path | +|:---------|:--------------| +| [Create a scheduled broadcast](/livestream-api/scheduled-broadcasts/create-scheduled-broadcast) | `POST /2/broadcasts/scheduled` | +| [List scheduled broadcasts](/livestream-api/scheduled-broadcasts/list-scheduled-broadcasts) | `GET /2/broadcasts/scheduled` | +| [Get a scheduled broadcast](/livestream-api/scheduled-broadcasts/get-scheduled-broadcast) | `GET /2/broadcasts/scheduled/:id` | +| [Update a scheduled broadcast](/livestream-api/scheduled-broadcasts/update-scheduled-broadcast) | `PUT /2/broadcasts/scheduled/:id` | +| [Delete a scheduled broadcast](/livestream-api/scheduled-broadcasts/delete-scheduled-broadcast) | `DELETE /2/broadcasts/scheduled/:id` | +| [Go live on a scheduled broadcast](/livestream-api/scheduled-broadcasts/go-live) | `POST /2/broadcasts/scheduled/:id/live` | + +## Recommended workflow + +### Automatically published broadcast + + + + + Leave `manual_publish` omitted or set to `false`. + + + Start pushing video before `scheduled_start_ms`. + + + Publishing happens automatically at the scheduled start time. + + + Observe state changes via [Get a scheduled broadcast](/livestream-api/scheduled-broadcasts/get-scheduled-broadcast). + + + +### Manually published broadcast + + + + + Set `manual_publish: true`. + + + + Call [`POST /2/broadcasts/scheduled/:id/live`](/livestream-api/scheduled-broadcasts/go-live). + + + Poll until the state is `Running`. + + + +## Errors and troubleshooting + +Common HTTP responses: + +| Status | Meaning | +|:-------|:--------| +| `400 Bad Request` | Missing or invalid input, invalid schedule, unknown broadcast, incomplete full-replacement update, or a go-live request for a schedule that is not configured for manual publishing. | +| `401 Unauthorized` | Missing, expired, or invalid user access token. | +| `403 Forbidden` | The token lacks the required scope or the authenticated user is not permitted to access the resource. | +| `429 Too Many Requests` | Rate limit exceeded. | +| `503 Service Unavailable` | A dependent livestream or scheduling service could not complete the request. | + +If a go-live request fails: + +- Confirm `manual_publish` is `true`. +- Confirm the source ID is correct. +- Confirm RTMP video is actively reaching the source. +- Confirm the broadcast has not already ended or been deleted. + +If an update reports incomplete broadcast data, retrieve the current broadcast and resend all fields that should remain set. diff --git a/livestream-api/scheduled-broadcasts/update-scheduled-broadcast.mdx b/livestream-api/scheduled-broadcasts/update-scheduled-broadcast.mdx new file mode 100644 index 000000000..51ca3f597 --- /dev/null +++ b/livestream-api/scheduled-broadcasts/update-scheduled-broadcast.mdx @@ -0,0 +1,19 @@ +--- +title: Update a Scheduled Broadcast +sidebarTitle: Update Scheduled Broadcast +description: "PUT /2/broadcasts/scheduled/:id updates a scheduled broadcast. This is a full replacement, not a partial update — resend every field that should remain set." +keywords: ["update scheduled broadcast", "PUT broadcasts scheduled", "full replacement", "roll_forward"] +openapi: "put /2/broadcasts/scheduled/{id}" +--- + +Updates a scheduled broadcast. `:id` is the alphanumeric `broadcast_id`, while the body's required `scheduled_broadcast_id` is the numeric scheduler ID (from create, list, or get). + + +**Full replacement, not a partial update.** Before updating, retrieve the current broadcast, change the desired fields, and resend every field that should remain set. Omitting a previously set optional field can clear it. Recurrence cannot be added or changed through this endpoint. + + +Set `roll_forward` to `true` to shift the head of an existing recurring series. + + +If an update reports incomplete broadcast data, retrieve the current broadcast with [Get a scheduled broadcast](/livestream-api/scheduled-broadcasts/get-scheduled-broadcast) and resend all fields that should remain set. + diff --git a/livestream-api/sources/create-source.mdx b/livestream-api/sources/create-source.mdx new file mode 100644 index 000000000..973e70f8f --- /dev/null +++ b/livestream-api/sources/create-source.mdx @@ -0,0 +1,89 @@ +--- +title: Create Stream Source +sidebarTitle: Create Stream Source +description: "POST /2/users/:user_id/sources creates a new persistent RTMP/RTMPS ingest point with a unique stream key and recommended encoder configuration." +keywords: ["create stream source", "POST sources", "RTMP ingest point", "stream key", "rtmps_url"] +--- + +Creates a new RTMP ingest point. + +**Endpoint:** `POST /2/users/:user_id/sources` + +## Path parameters + +| Name | Type | Required | Description | +|:-----|:-----|:---------|:------------| +| `user_id` | string | Yes | Numeric X user ID of the broadcaster. Must match the authenticated token. | + +## Request body + +```json +{ + "name": "My Primary Encoder", + "region": "eu-central-1" +} +``` + +| Field | Type | Required | Description | +|:------|:-----|:---------|:------------| +| `name` | string | Yes | Human-readable label for this source (for your own organization). Does not affect functionality. | +| `region` | string | Yes | AWS-style region code (e.g. `"us-east-1"`, `"eu-central-1"`). Use the value from [`/2/region`](/livestream-api/sources/get-recommended-region). Determines the ingest server location. | + +## Example request + +```bash +curl -X POST "https://api.x.com/2/users/172483972/sources" \ + -H "Authorization: Bearer $USER_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name":"My Primary Encoder","region":"eu-central-1"}' +``` + +## Success response (200) + +```json +{ + "source": { + "id": "6ep48v6ar5q4", + "owner_id": 172483972, + "name": "My Primary Encoder", + "video_service": { + "value": 0, + "name": "...", + "original_name": "..." + }, + "is360": false, + "creation_time": 1772029802844, + "creation_origin": { + "value": 1, + "name": "PublicApi", + "original_name": "PUBLIC_API" + }, + "recommended_configuration": { + "audio_codec": "AAC", + "audio_sampling_rate": 44100, + "audio_bitrate": 128000.0, + "audio_num_channels": 2, + "video_codec": "H.264/AVC", + "video_bitrate": 4000000.0, + "video_framerate": 30.0, + "video_keyframe_interval_in_seconds": 3.0, + "video_width": 1280, + "video_height": 720 + }, + "rtmp_region": "eu-central-1", + "rtmp_url": "rtmp://de.pscp.tv:80/x", + "rtmps_url": "rtmps://de.pscp.tv:443/x", + "rtmp_stream_key": "6ep48v6ar5q4", + "is_stream_active": false + } +} +``` + +## Notes + +- Newly created sources have no `stream_attributes` or `compatibility_info` (those appear after the source first receives video). +- `rtmp_stream_key == id`. Use the `rtmps_url` (port 443) in production for TLS. +- The `recommended_configuration` tells you ideal encoder settings (bitrate, resolution, keyframe interval, etc.). +- `is_stream_active` becomes `true` while the source is currently receiving an RTMP feed. +- `owner_id` is a JSON **number**, not a string — for real (19-digit) X user IDs it exceeds the safe-integer range of many languages (e.g. JavaScript) and loses precision when parsed normally. Do not rely on `owner_id`; use your authenticated numeric user id (or a broadcast's `twitter_user_id`, which is a string). +- `video_service`, `creation_origin` (and similar `{ value, name, original_name }` objects) are classification fields. `value` is a stable integer; `name` / `original_name` are opaque labels. You can safely ignore these unless you need the integer code. diff --git a/livestream-api/sources/delete-source.mdx b/livestream-api/sources/delete-source.mdx new file mode 100644 index 000000000..460f44d2c --- /dev/null +++ b/livestream-api/sources/delete-source.mdx @@ -0,0 +1,18 @@ +--- +title: Delete Stream Source +sidebarTitle: Delete Stream Source +description: "DELETE /2/users/:user_id/sources/:source_id permanently deletes a stream source. No active broadcast may be using it." +keywords: ["delete stream source", "DELETE source", "remove ingest point"] +--- + +Permanently deletes a source. No active broadcast may be using it. + +**Endpoint:** `DELETE /2/users/:user_id/sources/:source_id` + +## Response + +HTTP `200` with a minimal JSON success body. + + +Don't depend on the exact body shape of delete responses — treat any 2xx as success. + diff --git a/livestream-api/sources/get-recommended-region.mdx b/livestream-api/sources/get-recommended-region.mdx new file mode 100644 index 000000000..5ad556cae --- /dev/null +++ b/livestream-api/sources/get-recommended-region.mdx @@ -0,0 +1,32 @@ +--- +title: Get Recommended Region +sidebarTitle: Get Recommended Region +description: "GET /2/region returns the recommended AWS region for low-latency ingest based on the caller's location, via a 307 redirect." +keywords: ["get region", "recommended region", "GET /2/region", "AWS region", "ingest region"] +--- + +Returns the recommended AWS region for low-latency ingest based on the caller's location. + +**Endpoint:** `GET /2/region` + +**Auth:** User context — OAuth 2.0 (with `broadcast.read` scope) recommended, or OAuth 1.0a 3-legged. See [Authentication](/livestream-api/authentication). + +## Behavior + +Sign and send the request to `/2/region`; it responds with HTTP `307 Temporary Redirect` to a region-recommendation host. Follow the redirect and read `region` from the final body. + + +**Auth across the redirect:** Most HTTP clients drop the `Authorization` header when a redirect crosses to a different host (standard, secure behavior). That is fine — only the initial `/2/region` request needs to be signed; the redirect target is public. Clients that follow redirects automatically work without special handling. + + +## Response + +The final response body: + +```json +{ + "region": "us-east-1" +} +``` + +Use the returned region when creating sources and broadcasts for optimal performance. The API recommends the best region based on the requester's location. diff --git a/livestream-api/sources/get-source.mdx b/livestream-api/sources/get-source.mdx new file mode 100644 index 000000000..3ef764a3d --- /dev/null +++ b/livestream-api/sources/get-source.mdx @@ -0,0 +1,24 @@ +--- +title: Get Stream Source +sidebarTitle: Get Stream Source +description: "GET /2/users/:user_id/sources/:source_id returns a single stream source owned by the authenticated user." +keywords: ["get stream source", "GET source", "source lookup"] +--- + +Returns a single source (must be owned by the authenticated user). + +**Endpoint:** `GET /2/users/:user_id/sources/:source_id` + +## Response + +Same shape as an item inside the [list response](/livestream-api/sources/list-sources), wrapped: + +```json +{ + "source": { ... } +} +``` + + +Poll this endpoint after starting your encoder and wait for `is_stream_active: true` before [creating a broadcast](/livestream-api/broadcasts/create-broadcast) — creating a broadcast against a source that is not yet ingesting returns a 404. + diff --git a/livestream-api/sources/list-sources.mdx b/livestream-api/sources/list-sources.mdx new file mode 100644 index 000000000..4f72b8ba6 --- /dev/null +++ b/livestream-api/sources/list-sources.mdx @@ -0,0 +1,21 @@ +--- +title: List Stream Sources +sidebarTitle: List Stream Sources +description: "GET /2/users/:user_id/sources returns all stream sources owned by the user." +keywords: ["list stream sources", "GET sources", "stream sources"] +--- + +Returns all sources owned by the user (empty array if none). + +**Endpoint:** `GET /2/users/:user_id/sources` + +## Response shape + +```json +{ + "sources": [ { ...source object... }, ... ] +} +``` + +- A source that has **never received a stream** will only have `recommended_configuration`. +- A source that **has received a stream** will additionally include `stream_attributes` (last/actual detected params) and `compatibility_info` (`errors` for hard violations, `warnings` for suboptimal values). diff --git a/livestream-api/sources/overview.mdx b/livestream-api/sources/overview.mdx new file mode 100644 index 000000000..36a181f71 --- /dev/null +++ b/livestream-api/sources/overview.mdx @@ -0,0 +1,25 @@ +--- +title: Stream Source Management +sidebarTitle: Overview +description: "Stream sources are persistent RTMP/RTMPS ingest endpoints (URL + unique stream key). Create them once per region/encoder setup and reuse them across broadcasts." +keywords: ["stream source", "RTMP ingest", "RTMPS", "stream key", "ingest endpoint"] +--- + +A **stream source** is a persistent RTMP/RTMPS ingest endpoint (URL + unique stream key). Create sources once per region/encoder setup and reuse them across many broadcasts. + + +A per-user limit on the number of sources may apply. Create sources sparingly and reuse them across broadcasts rather than creating a new source per broadcast. Sources belong to the creating user. + + +## Endpoints + +| Endpoint | Method & Path | +|:---------|:--------------| +| [Get Recommended Region](/livestream-api/sources/get-recommended-region) | `GET /2/region` | +| [Create Stream Source](/livestream-api/sources/create-source) | `POST /2/users/:user_id/sources` | +| [List Stream Sources](/livestream-api/sources/list-sources) | `GET /2/users/:user_id/sources` | +| [Get Stream Source](/livestream-api/sources/get-source) | `GET /2/users/:user_id/sources/:source_id` | +| [Update Stream Source](/livestream-api/sources/update-source) | `PUT /2/users/:user_id/sources/:source_id` | +| [Delete Stream Source](/livestream-api/sources/delete-source) | `DELETE /2/users/:user_id/sources/:source_id` | + +All of these require [user context authentication](/livestream-api/authentication). OAuth 2.0 (with `broadcast.read` and `broadcast.write` scopes) is recommended; OAuth 1.0a is also supported. diff --git a/livestream-api/sources/update-source.mdx b/livestream-api/sources/update-source.mdx new file mode 100644 index 000000000..f84b7cc10 --- /dev/null +++ b/livestream-api/sources/update-source.mdx @@ -0,0 +1,22 @@ +--- +title: Update Stream Source +sidebarTitle: Update Stream Source +description: "PUT /2/users/:user_id/sources/:source_id renames a stream source. Only the name can be updated." +keywords: ["update stream source", "rename source", "PUT source"] +--- + +Only the `name` can be updated. + +**Endpoint:** `PUT /2/users/:user_id/sources/:source_id` + +## Request body + +```json +{ + "name": "Updated Encoder Name" +} +``` + +## Response + +The updated source object (wrapped in `"source"`). diff --git a/livestream-api/typical-workflow.mdx b/livestream-api/typical-workflow.mdx new file mode 100644 index 000000000..3d6883e2a --- /dev/null +++ b/livestream-api/typical-workflow.mdx @@ -0,0 +1,45 @@ +--- +title: Typical Workflow +sidebarTitle: Typical Workflow +description: "The end-to-end Livestream API workflow: get a region, create a source, start your encoder, create and publish a broadcast, consume chat, and end the session." +keywords: ["Livestream workflow", "go live", "broadcast lifecycle", "stream source", "publish broadcast"] +--- + +The complete flow from nothing to a live broadcast on X: + + + + `GET /2/region` → obtain the recommended region (follow the 307 redirect). See [Get Recommended Region](/livestream-api/sources/get-recommended-region). + + + `POST /2/users/:user_id/sources` → create a source for that region. Record `rtmps_url` + `rtmp_stream_key`. See [Create Stream Source](/livestream-api/sources/create-source). + + + Configure and start your encoder, pushing to the RTMPS URL + key. Wait until `is_stream_active` is `true` (poll [List](/livestream-api/sources/list-sources)/[Get Source](/livestream-api/sources/get-source) if desired). + + + `POST /2/users/:user_id/broadcasts` with the `source_id`, `region`, and `is_low_latency`. Note the `media_key`, `id`, `share_url`, and `video_access` URLs. The broadcast starts in `NOT_STARTED`. See [Create Broadcast](/livestream-api/broadcasts/create-broadcast). + + + Subscribe to the user's `broadcast.chat` events in the X Activity API. If you have already done this previously, you don't need to resubscribe — the subscription covers all broadcasts for the user. See [Accessing Live Chat](/livestream-api/chat/accessing-live-chat). + + + `PUT …/broadcasts/:id/state` with `state: "PUBLISH"` (+ `title`, `chat_option`, etc.). The broadcast becomes `RUNNING` and publicly visible. See [Publish or End a Broadcast](/livestream-api/broadcasts/publish-or-end-broadcast). + + + Poll `GET /2/broadcasts/:id` for viewer counts, thumbnails, and state; read chat via the X Activity API; optionally post messages with [`POST /2/broadcasts/:id/chat`](/livestream-api/chat/send-chat-message). + + + When done: stop your encoder, then `PUT …/state` with `{ "state": "END" }`. The broadcast transitions to `ENDED`. + + + `GET /2/broadcasts/:id/chat` returns the broadcast's chat history. This endpoint is only valid after the broadcast has ended. See [Get Chat History](/livestream-api/chat/get-chat-history). + + + Delete the broadcast record or source if no longer needed. + + + + +Sources are reusable. Create a new source only when you need a different region or want to isolate encoder configs. + From cb388ee704d8bfefaa3a41e553edf80f95b0c5db Mon Sep 17 00:00:00 2001 From: Taylor Caldwell Date: Tue, 15 Sep 2026 13:49:35 -0700 Subject: [PATCH 2/2] docs: mark Livestream API as Enterprise-only and clean up for production Add Enterprise-only callouts pointing to the Enterprise interest form on the Introduction, Getting Started, and Scheduling overview pages, and replace "contact your partner manager" / allow-list wording on the Authentication page. Also fix content that was stale or not customer-ready: - Remove the Scheduling overview note claiming the management endpoints accept only OAuth 1.0a; it contradicted the Authentication page. - Replace the "WebSocket flow" reference on Send a Chat Message with the X Activity API broadcast.chat event the chat page now documents. - Reword the Broadcasts overview deprecation note to drop references to backend work and Periscope. - Replace "observed as" phrasing for the chat_option default and the is_high_latency field with plain statements. --- livestream-api/authentication.mdx | 4 ++-- livestream-api/broadcasts/create-broadcast.mdx | 4 ++-- livestream-api/broadcasts/overview.mdx | 2 +- livestream-api/broadcasts/publish-or-end-broadcast.mdx | 4 ++-- livestream-api/chat/send-chat-message.mdx | 2 +- livestream-api/getting-started.mdx | 10 +++++----- livestream-api/introduction.mdx | 6 +++++- livestream-api/scheduled-broadcasts/overview.mdx | 10 +++++----- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/livestream-api/authentication.mdx b/livestream-api/authentication.mdx index bb87df941..e27a4d2cf 100644 --- a/livestream-api/authentication.mdx +++ b/livestream-api/authentication.mdx @@ -13,7 +13,7 @@ Use **OAuth 2.0 Authorization Code Flow with PKCE** to obtain a user-context acc ### Requirements -- Your application must be allow-listed for the Livestream API (contact your X partner manager). +- Your application must be enabled for the Livestream API under an Enterprise plan. See [Getting Started](/livestream-api/getting-started) for how to request access. - Request the `broadcast.read` and `broadcast.write` scopes at minimum — these cover most operations. Add `offline.access` if you need a refresh token. - The authorizing user's numeric ID must exactly match the `:user_id` in the path. Mismatches are rejected with `400 Bad Request`. @@ -58,7 +58,7 @@ OAuth 1.0a 3-legged user context is still accepted for existing integrations. - Use OAuth 1.0a with **HMAC-SHA1** signatures (3-legged user context). - Include a properly constructed `Authorization: OAuth ...` header on every request. -- Your application must be allow-listed for the Livestream API. +- Your application must be enabled for the Livestream API under an Enterprise plan. - The application must have **Read + Write** (or ReadWriteDm) access level. - The numeric user ID from the OAuth 1.0a access token must exactly match the `:user_id` in the path. Mismatches are rejected with `400 Bad Request`. diff --git a/livestream-api/broadcasts/create-broadcast.mdx b/livestream-api/broadcasts/create-broadcast.mdx index c680cdfde..2d491070b 100644 --- a/livestream-api/broadcasts/create-broadcast.mdx +++ b/livestream-api/broadcasts/create-broadcast.mdx @@ -27,7 +27,7 @@ The source must already be actively receiving an RTMP feed (`is_stream_active: t |:------|:-----|:---------|:------------| | `source_id` | string | Yes | ID of an existing source owned by this user. | | `region` | string | Yes | Must match the source's region. | -| `is_low_latency` | boolean | No | Requests low-latency mode (lower delay for interactive/chatty broadcasts, trading some quality/stability). If omitted, defaults to `false` (standard latency); recommended to set explicitly. Note: the returned `is_high_latency` field is not a reliable inverse of this value (it has been observed to stay `true` regardless), so treat `is_high_latency` as informational only. | +| `is_low_latency` | boolean | No | Requests low-latency mode (lower delay for interactive/chatty broadcasts, trading some quality/stability). If omitted, defaults to `false` (standard latency); we recommend setting it explicitly. The `is_high_latency` field in the response is informational only and does not reflect this value. | ## Example request @@ -86,7 +86,7 @@ curl -X POST "https://api.x.com/2/users/172483972/broadcasts" \ - `video_access`: Playback URLs (HLS variants). `lhls*` and `replay_url` may appear later or for certain modes. - `share_url`: Public viewer link (`https://x.com/i/broadcasts/{id}`). - `state`: Always starts `"NOT_STARTED"`. -- `is_high_latency`: Informational; does not reliably reflect the `is_low_latency` you sent (see the note on the `is_low_latency` field above). +- `is_high_latency`: Informational only; it does not reflect the `is_low_latency` value you sent. - `twitter_user_id` vs `user_id` vs `owner_id`: `twitter_user_id` is your numeric X user id (returned as a string) — use this for the `:user_id` path parameter in every endpoint. `user_id` (e.g. `"1DYEXPNqZwqEg"`) is a separate alphanumeric livestream-platform id; do not use it in paths. Note also that the Source object's `owner_id` is delivered as a JSON number and will lose precision for real (19-digit) user IDs — never rely on it; use `twitter_user_id` or your authenticated id instead. After creation, start pushing your RTMP feed to the source's `rtmps_url` + `rtmp_stream_key` if you haven't already, then [publish](/livestream-api/broadcasts/publish-or-end-broadcast). diff --git a/livestream-api/broadcasts/overview.mdx b/livestream-api/broadcasts/overview.mdx index e65537642..c87c64421 100644 --- a/livestream-api/broadcasts/overview.mdx +++ b/livestream-api/broadcasts/overview.mdx @@ -37,7 +37,7 @@ The only transitions you trigger directly are `NOT_STARTED` → `RUNNING` (via ` All routes accept both OAuth 2.0 and OAuth 1.0a [user context authentication](/livestream-api/authentication). The new routes (those without `user_id` in the path) are the recommended routes and return additional fields (for example, field selection with `broadcast.fields`). -The original routes are being replaced as backend work completes and we drift away from the legacy Periscope endpoints. New routes will be added to the table above as they ship — use them in place of the original routes wherever a replacement is available. +The original routes are being replaced over time. Where a new route is listed, use it in place of the original; the original route is deprecated and may be removed in a future release. Additional replacement routes will be added to the table above as they become available. diff --git a/livestream-api/broadcasts/publish-or-end-broadcast.mdx b/livestream-api/broadcasts/publish-or-end-broadcast.mdx index bafcf762c..fd075c5f8 100644 --- a/livestream-api/broadcasts/publish-or-end-broadcast.mdx +++ b/livestream-api/broadcasts/publish-or-end-broadcast.mdx @@ -31,7 +31,7 @@ Transitions `NOT_STARTED` → `RUNNING`. The broadcast becomes publicly visible | `title` | string | No | Broadcast title / status text (also used for the announcement post if sent). Subject to X post length limits. | | `should_not_tweet` | boolean | No | Set `true` to suppress the automatic announcement post. Default `false` (a post is sent). | | `locale` | string | No | BCP 47 tag (e.g. `"en"`, `"en_US"`). Affects discovery/recommendation. | -| `chat_option` | integer | No | Controls who can chat — see the values table below. If omitted, the platform applies its default (observed as `3`, verified accounts). | +| `chat_option` | integer | No | Controls who can chat — see the values table below. If omitted, defaults to `3` (verified accounts). | ### Chat option values @@ -46,7 +46,7 @@ Transitions `NOT_STARTED` → `RUNNING`. The broadcast becomes publicly visible | `4` | Accounts the broadcaster follows | | `5` | The broadcaster's subscribers | -If you omit `chat_option` when publishing, the platform applies its default (observed as `3`, verified accounts). +If you omit `chat_option` when publishing, it defaults to `3` (verified accounts). Set it explicitly if you want a different audience. ### Example request diff --git a/livestream-api/chat/send-chat-message.mdx b/livestream-api/chat/send-chat-message.mdx index b528c2951..d10ff09d2 100644 --- a/livestream-api/chat/send-chat-message.mdx +++ b/livestream-api/chat/send-chat-message.mdx @@ -18,5 +18,5 @@ The response's `timestamp` is the server timestamp of the message in nanoseconds - It returns `403 Forbidden` if the authenticated user is not permitted to chat in the broadcast. -This endpoint sends messages only. To receive chat messages, use the read-access WebSocket flow described in [Accessing Live Chat](/livestream-api/chat/accessing-live-chat). +This endpoint sends messages only. To receive chat messages in real time, subscribe to the `broadcast.chat` event on the X Activity API as described in [Accessing Live Chat](/livestream-api/chat/accessing-live-chat). diff --git a/livestream-api/getting-started.mdx b/livestream-api/getting-started.mdx index 52bfee10c..ceee96006 100644 --- a/livestream-api/getting-started.mdx +++ b/livestream-api/getting-started.mdx @@ -1,15 +1,15 @@ --- title: Getting Started sidebarTitle: Getting Started -description: "Prerequisites for the X Livestream API: app allow-listing, OAuth credentials (OAuth 2.0 recommended, OAuth 1.0a also supported), user permissions, and an RTMP encoder." -keywords: ["Livestream API access", "allow-list", "OAuth 2.0", "OAuth 1.0a", "prerequisites", "RTMP encoder", "getting started"] +description: "Prerequisites for the X Livestream API: Enterprise access, OAuth credentials (OAuth 2.0 recommended, OAuth 1.0a also supported), user permissions, and an RTMP encoder." +keywords: ["Livestream API access", "Enterprise", "OAuth 2.0", "OAuth 1.0a", "prerequisites", "RTMP encoder", "getting started"] --- -To use the X Livestream API, you need the following: +The Livestream API is available exclusively under the Enterprise plan. To use it, you need the following: - - Your X application (OAuth client) must be explicitly added to the Livestream API allow-list. Contact your X partner manager or account team to request access. + + Your X application (OAuth client) must be enabled for the Livestream API, which is only available to Enterprise customers. If you are interested in access, fill out the [Enterprise interest form](/forms/enterprise-api-interest). If you already have an Enterprise agreement, ask your X account team to enable the Livestream API for your app. All Livestream endpoints support both OAuth 2.0 and OAuth 1.0a. OAuth 2.0 is the recommended method: request the `broadcast.read` and `broadcast.write` scopes at minimum, which cover most operations. OAuth 1.0a is still accepted for existing integrations. Store your credentials securely. diff --git a/livestream-api/introduction.mdx b/livestream-api/introduction.mdx index 6a261d3a4..65670cf78 100644 --- a/livestream-api/introduction.mdx +++ b/livestream-api/introduction.mdx @@ -7,6 +7,10 @@ keywords: ["Livestream API", "live video", "broadcast", "RTMP", "RTMPS", "live s The X Livestream API enables authorized third-party applications and services to create and manage live video broadcasts on X. + +The Livestream API is only available under the Enterprise plan. If you are interested in access, fill out the [Enterprise interest form](/forms/enterprise-api-interest). + + It provides a complete set of endpoints for the broadcast lifecycle: - Managing persistent RTMP/RTMPS **stream sources** (ingest points) @@ -20,7 +24,7 @@ Endpoints are hosted under `https://api.x.com`. - Prerequisites, allow-listing, and credentials. + Prerequisites, Enterprise access, and credentials. From stream source to live broadcast, step by step. diff --git a/livestream-api/scheduled-broadcasts/overview.mdx b/livestream-api/scheduled-broadcasts/overview.mdx index 10eecc68c..7baf1fe6a 100644 --- a/livestream-api/scheduled-broadcasts/overview.mdx +++ b/livestream-api/scheduled-broadcasts/overview.mdx @@ -11,6 +11,10 @@ The API supports one-time and recurring schedules. A scheduled broadcast is asso ## Before you begin +### Access + +The Livestream API, including the scheduling endpoints, is only available under the Enterprise plan. If you are interested in access, fill out the [Enterprise interest form](/forms/enterprise-api-interest). See [Getting Started](/livestream-api/getting-started) for the full list of prerequisites. + ### Authentication All requests require user-context authentication. The authenticated user owns the broadcasts accessed through these endpoints. @@ -20,11 +24,7 @@ Supported authentication methods: - **OAuth 2.0 Authorization Code Flow with PKCE.** Read routes require `broadcast.read`. Create, update, delete, and go-live routes require both `broadcast.read` and `broadcast.write`. - **OAuth 1.0a user context.** List and get routes accept apps with Read, Read and write, or Read, write, and Direct Messages permissions. Create, update, delete, and go-live routes require Read and write or Read, write, and Direct Messages permissions. -OAuth 2.0 is recommended for new integrations. - - -This differs from the Livestream [management endpoints](/livestream-api/authentication) (sources and broadcasts), which currently accept only OAuth 1.0a. - +OAuth 2.0 is recommended for new integrations. See [Authentication](/livestream-api/authentication) for details on both flows. ### Base URL