Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
101 changes: 101 additions & 0 deletions livestream-api/authentication.mdx
Original file line number Diff line number Diff line change
@@ -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)

<Warning>
**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`.
</Warning>

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).
92 changes: 92 additions & 0 deletions livestream-api/broadcasts/create-broadcast.mdx
Original file line number Diff line number Diff line change
@@ -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`

<Warning>
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).
</Warning>

## 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).
18 changes: 18 additions & 0 deletions livestream-api/broadcasts/delete-broadcast.mdx
Original file line number Diff line number Diff line change
@@ -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`.

<Note>
Don't depend on the exact body shape of delete responses — treat any 2xx as success.
</Note>
15 changes: 15 additions & 0 deletions livestream-api/broadcasts/get-broadcast-by-id.mdx
Original file line number Diff line number Diff line change
@@ -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.

<Tip>
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.
</Tip>
21 changes: 21 additions & 0 deletions livestream-api/broadcasts/get-broadcast.mdx
Original file line number Diff line number Diff line change
@@ -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`

<Warning>
**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.
</Warning>

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.

<Tip>
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.
</Tip>
20 changes: 20 additions & 0 deletions livestream-api/broadcasts/get-broadcasts.mdx
Original file line number Diff line number Diff line change
@@ -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.

<Tip>
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.
</Tip>
35 changes: 35 additions & 0 deletions livestream-api/broadcasts/list-broadcasts.mdx
Original file line number Diff line number Diff line change
@@ -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`

<Warning>
**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.
</Warning>

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