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.
+