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..e27a4d2cf --- /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 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`. + +### 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 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`. + +### 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..2d491070b --- /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); we recommend setting it explicitly. The `is_high_latency` field in the response is informational only and does not reflect this value. | + +## 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 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/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..c87c64421 --- /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 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. + + + +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..fd075c5f8 --- /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, defaults to `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, it defaults to `3` (verified accounts). Set it explicitly if you want a different audience. + +### 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..d10ff09d2 --- /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 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/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..ceee96006 --- /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: 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"] +--- + +The Livestream API is available exclusively under the Enterprise plan. To use it, you need the following: + + + + 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. + + + 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..65670cf78 --- /dev/null +++ b/livestream-api/introduction.mdx @@ -0,0 +1,55 @@ +--- +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. + + +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) +- 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, Enterprise access, 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..7baf1fe6a --- /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 + +### 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. + +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. See [Authentication](/livestream-api/authentication) for details on both flows. + +### 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. +