X Livestream API: your source id was the stream key
The X Livestream API moved to /2/sources and /2/broadcasts on 5 November 2026. What the new routes change, what returns 404 on 1 January 2027, and what the SDK still lacks.
On this page · 10 sections
- Quick answer
- What shipped on 5 November 2026
- The old source id was the stream key
- One call to go live, and two ways it succeeds
- Strict bodies, a data wrapper, renamed fields
- The errors changed, including the one you will hit most
- The spec never says deprecated, and the SDK has neither route set
- No price, no rate-limit row
- What to do before 1 January 2027
- Who this API is for
The short version
- ▸On the old routes a stream source's id was its stream key. The same secret identified the source and let anyone publish video to it, and it came back in every source response. The new /2/sources routes split them: a 19-digit public source id, and a stream key returned only twice.
- ▸POST /2/broadcasts replaces create-then-publish with one call. It returns 201 for a new broadcast and 200 with the existing one if the source is already live, so a retry after a lost response cannot double-publish you.
- ▸The deprecated /2/users/:user_id/sources and /2/users/:user_id/broadcasts routes return 404 from 1 January 2027. Passing a stream key as source_id to the scheduled routes stops working the same day.
- ▸The live API description at version 2.174 does not mark a single one of those routes deprecated, so code generators and type checkers will not warn you. The deadline exists only in prose.
- ▸The official Python SDK at 0.10.6 has no /2/sources methods and no go-live, list, end or delete for broadcasts. Neither the old routes nor the new ones are in it, and nothing on the rate card prices any of this.
Quick answer
The X Livestream API now lives at /2/sources and /2/broadcasts, shipped 5 November 2026. The headline change is not the shorter paths: on the original routes a stream source's id was its stream key, so the value you used to identify a source was the credential that let anyone publish video to it. The new routes split them into a public 19-digit source id and a stream key returned only twice. The old /2/users/:user_id/... routes keep working until 1 January 2027, then return 404.
Last updated: November 2026
Everything below is checked against the live API description at api.x.com/2/openapi.json, version 2.174, and the official Python SDK at 0.10.6, both read on 9 November 2026.
What shipped on 5 November 2026
Six source routes and five broadcast routes, mapping one-to-one onto the old set with one addition and one merge.
| Old route | New route |
|---|---|
POST /2/users/:user_id/sources | POST /2/sources |
GET /2/users/:user_id/sources | GET /2/sources |
GET /2/users/:user_id/sources/:source_id | GET /2/sources/:id |
PUT /2/users/:user_id/sources/:source_id | PUT /2/sources/:id |
DELETE /2/users/:user_id/sources/:source_id | DELETE /2/sources/:id |
the source's id | GET /2/sources/:id/stream_key (new) |
create, then PUT .../state with PUBLISH | POST /2/broadcasts (one call) |
GET /2/users/:user_id/broadcasts | GET /2/broadcasts |
GET /2/users/:user_id/broadcasts/:broadcast_id | GET /2/broadcasts/:id |
PUT .../state with END | PUT /2/broadcasts/:id/state |
DELETE /2/users/:user_id/broadcasts/:broadcast_id | DELETE /2/broadcasts/:id |
GET /2/region is untouched. Authentication is untouched too: OAuth 2.0 user context with broadcast.read and broadcast.write, or OAuth 1.0a. Which scope belongs on which call is the usual X puzzle, and the pattern across the v2 surface is in our notes on X API OAuth. One asymmetry here is worth flagging: GET /2/sources/:id/stream_key is a GET that requires broadcast.write, so a read-only token cannot fetch a key.
This whole namespace is also oddly hidden. It is absent from docs.x.com/llms.txt and from the X API v2 index at docs.x.com/x-api/llms.txt, the two files X publishes so that agents can discover its documentation. The changelog entry and a direct URL are the only ways in. There is no developer-forum announcement either, which puts this launch in the same quiet category as the Articles endpoints.
The old source id was the stream key
This is the part worth acting on today rather than in December.
X's own API description, read live, says it in two places. On the old source object, id is "Source id; also the RTMP stream key", and rtmp_stream_key is "Stream key; equal to id". One value, two jobs: the handle you pass around in paths, logs and dashboards, and the secret that authorises publishing video to your account's ingest point.
New routes separate them:
| Value | Old routes | New routes |
|---|---|---|
| Source id | id, equal to the stream key | id, 19 decimal digits, not a secret |
| Stream key | id and rtmp_stream_key, in every response | stream_key, only from POST /2/sources and GET /2/sources/:id/stream_key |
Two consequences follow, and only one is in the migration guide.
The documented one: new routes reject a stream key where a source id belongs, with 400 and the detail "The id value is not a source id." Your value is not echoed back, which is the right call for a credential.
Then the one you have to work out yourself. Existing sources keep their stream keys, which X says plainly as reassurance that configured encoders will not break. Read it the other way and it is a rotation problem: if an old source id reached a log aggregator, an error tracker, a support ticket or a browser URL, that key is still live. Splitting the fields protects what you create from now on and does nothing for what is already sitting in a search index you pay for. Call GET /2/sources, work out which sources had their old id handled carelessly, and replace those rather than assuming the change was retroactive.
One call to go live, and two ways it succeeds
Until last week the flow created a broadcast in NOT_STARTED, then flipped it with PUT .../state and {"state":"PUBLISH"}. POST /2/broadcasts does both:
{
"source_id": "9123456789012345678",
"title": "Launch stream",
"locale": "en",
"chat_option": 2,
"should_not_tweet": false
}
What comes back is the broadcast, already RUNNING. Its status code carries the meaning:
201means a new broadcast exists.200means the source was already live, and you are getting that broadcast back unchanged. Yourtitle,locale,chat_optionandshould_not_tweetare ignored.
That makes the call safe to retry, which matters because the old pair was not. Lose the create response to a timeout and you could not tell whether a draft existed. Here a retry either creates or returns.
Three timing rules sit around it. The broadcast goes public before your encoder sends anything, and X ends it with state TIMED_OUT if no video arrives within five minutes, so start the encoder next to the call rather than before the kettle boils. If a source cannot accept video until its encoder connects, the call answers 409 conflict, and the fix is to wait for is_stream_active to turn true on GET /2/sources/:id. If another broadcast is already live on the source, you also get 409, and that one does not clear on its own.
There is no NOT_STARTED state any more, so the old seven-day cleanup of never-published broadcasts has nothing to clean. Ending is PUT /2/broadcasts/:id/state with {"state":"END"}, and PUBLISH is no longer an accepted value there at all.
Strict bodies, a data wrapper, renamed fields
Everything about the new routes is tighter, which is good engineering and a migration tax.
POST /2/broadcasts sets additionalProperties: false in the live spec. chat_option must be a JSON integer from 0 to 6, where the old PUBLISH accepted "2", and should_not_tweet must be a real boolean, where the old one took "true" and 1. region and is_low_latency, which the old create route accepted and ignored, now produce a 400.
Responses move into data, as in the rest of v2. Lists gain meta.result_count and a flat meta.next_token that you feed back as pagination_token, replacing a next_token object that held an offset. GET /2/broadcasts also takes max_results up to 100 and quietly accepts an ids parameter the migration guide never mentions.
Renames are few and awkwardly placed:
| Old field | New field |
|---|---|
status (the title) | title |
rtmp_region | region |
creation_time (number) | created_at_ms (string) |
media_id | gone: take the digits after the last _ in media_key |
owner_id, is360, video_service, creation_origin | not returned |
| not present | source_id, via broadcast.fields=source_id |
Losing owner_id is a mercy. X's own description of it warns that the value "exceeds the exact integer range of many JSON parsers", which is a polite way of saying JavaScript has been silently corrupting it since the day it shipped.
Watch the source_id row. It is new, it is not returned by default, and broadcast.fields only exists on the two GET routes. Go-live has no broadcast.fields parameter in the spec, so an audit trail built from the create response alone will never record which source went live.
The errors changed, including the one you will hit most
Old routes answered with { "error_type", "message" }. New routes use the standard problem format, keyed on type.
| Case | Old | New |
|---|---|---|
| Unknown or someone else's source | 404 notFound | 404 resource-not-found |
| Stream key where a source id goes | did not exist | 400 invalid-request |
| Account cannot go live | 412 preconditionFailed | 403 about:blank |
| Source busy or not yet ingesting | did not exist | 409 conflict |
| Temporary failure | 503 serviceUnavailable | 503 about:blank |
Row three is the regression. "This account is not allowed to go live" is the most common first-run failure on any broadcast API, and the old route at least gave it a name you could grep. Now it is a 403 with type: about:blank, and X's own table says that same 403 also covers "the broadcast was moderated": two unrelated causes, one blank response. A separate 403 with missing-scopes does exist, so a token problem stays distinguishable from an account problem.
Check the account in the product before spending an afternoon on it. Live video on X has been gated behind a paid subscription in the app since mid-2024, and these docs only say the account "must have permission to go live" without naming a tier. Our walkthrough of how to go live on X covers which plans unlock the button. Protected accounts are excluded outright.
The spec never says deprecated, and the SDK has neither route set
Two gaps make this migration quieter than it should be.
First, the live API description does not flag any of it. Read api.x.com/2/openapi.json at version 2.174 and every one of the ten /2/users/:id/... source and broadcast operations has no deprecated flag. OpenAPI has a field for exactly this, and generated clients surface it as a compiler or linter warning. Here they will not. The deadline lives in a changelog entry, a migration page and a callout, which is to say it lives in prose your build never reads.
Second, the official Python SDK cannot help either way. At 0.10.6, uploaded 25 July 2026 and still current, xdk.broadcasts exposes the scheduled-broadcast methods and send_broadcast_chat, and nothing else: no sources module, and no go-live, list, get, end or delete for live broadcasts. Searching the published wheel for /2/sources returns no route at all, only schema text that still defines a scheduled broadcast's source_id as "same as sources rtmp_stream_key", the deprecated meaning. The SDK documents an expiring semantic for a field with seven weeks left. That pattern of generated client against hand-written page is in our notes on the official X API Python SDK.
Practically: write these calls by hand. Eleven routes, one auth header, standard problem errors.
No price, no rate-limit row
The pay-per-use rate card is specific down to $0.001 per block event. It names twelve read resources and fifteen write actions. Not one of them is a broadcast, a source or a livestream.
Rate limits tell the same story. Fourteen per-namespace tables, including two-endpoint namespaces such as Trends and Communities, plus an "Other endpoints" catch-all holding eleven leftovers. No Livestream section, and no /2/sources or /2/broadcasts row in the catch-all either. The only published number is in the prose of the docs pages: 450 requests per 15 minutes per user on each source route and on POST /2/broadcasts.
So going live has a documented frequency ceiling and an undocumented price. If you are modelling the cost of anything that touches this API, start from how the rest of the card bills in X API pricing, then measure your credit balance across one real broadcast, because inference from neighbouring rows will not get you there. The namespaces that do publish ceilings are collected in X API rate limits.
One disambiguation, since the names collide. This is video ingest. The Filtered Stream endpoint, which delivers Posts over a long-lived connection and bills per Post read, is a different product with a different budget, covered in X API stream.
What to do before 1 January 2027
A short list, in the order that limits damage:
- Call
GET /2/sourcesand store each source's new 19-digitid. Use it everywhere you previously used the stream key as an identifier. - Decide which old source ids have leaked into logs, URLs or third-party tools, and recreate those sources. Keys you already exposed are still valid.
- Move source management to
/2/sources, sendingregionon create, since the old route defaulted to US West when you omitted it and the new one requires it. - Replace create-plus-publish with
POST /2/broadcasts, handling201,200and both409cases, and switch reads to/2/broadcastswithdata,titleandmeta.next_token. - Pass the source id, not the stream key, as
source_idwhen you create or update scheduled broadcasts, and branch on problemtypeinstead oferror_type.
Both route sets act on the same objects until the deadline, so this is a gradual migration rather than a cutover. You can end a broadcast created the old way through the new route, and every source you made the old way appears in GET /2/sources with a fresh id.
Who this API is for
Anyone running real live video on X from their own stack: a studio pushing a weekly show, a sports feed, an events platform. Eleven routes, an RTMPS URL and an encoder, all fully sanctioned by X. If that is the job, this is the right door and it got meaningfully better this month.
If what you actually want is an account that stays active while you are away from the keyboard, live video is a strange place to start, because a broadcast needs a human, a camera and an audience that turns up at a set hour. X-Autopilot works the opposite problem: posting and replying from your own Mac, in your own browser session, so there is no shared cloud IP pool and no delegate account in the path. That is a smaller detection surface than a hosted scheduler, not an absent one, and driving engagement through a browser sits in a gray area under X's automation rules rather than being something X endorses.
Frequently asked
Answers indexed by Google + AI assistants.
What is the X Livestream API?+
A set of X API v2 routes for running live video on X from your own software. You create a persistent RTMPS ingest point called a stream source, push video to it from an encoder such as ffmpeg or OBS, then take that source live as a broadcast. As of 5 November 2026 the current routes are /2/sources for ingest points and /2/broadcasts for live sessions, plus /2/broadcasts/scheduled for scheduling and /2/broadcasts/:id/chat for live chat. It is a different thing from the Filtered Stream data endpoint, which streams Posts rather than video.
What changed in the X Livestream API on 5 November 2026?+
X added the /2/sources and /2/broadcasts routes and deprecated the original /2/users/:user_id/sources and /2/users/:user_id/broadcasts routes. The new routes take the owner from the access token instead of the URL, wrap every result in data, return errors in the standard X API problem format, and separate a source's public id from its secret stream key. POST /2/broadcasts also collapses the old create-then-publish pair into one call that returns a broadcast already RUNNING.
When do the old X broadcast routes stop working?+
1 January 2027. After that date requests to /2/users/:user_id/sources, /2/users/:user_id/broadcasts and their sub-routes return 404. The scheduled broadcast routes are not being removed, but they stop accepting a stream key in place of a source id on the same date. Both route sets act on the same sources and broadcasts until then, so you can migrate one call at a time rather than in one cutover.
Why was the old X stream source id a security problem?+
Because it was the credential. X's own API description says the old source id is also the RTMP stream key, and that rtmp_stream_key is equal to id. Anyone holding it can publish video to that source, and it was returned in every list and get response, which is exactly the kind of value that ends up in logs, analytics events and URLs. The new routes return the stream key only from POST /2/sources and GET /2/sources/:id/stream_key. Existing sources keep their keys, so a key you logged before the change is still live until you rotate the source.
Does the X Livestream API require X Premium?+
X's developer docs only say the broadcasting account must have permission to go live, and that protected accounts cannot create broadcasts. They do not name a subscription tier. Live video on X has been a paid product in the app since July 2024, and POST /2/broadcasts answers an ineligible account with a bare 403 that carries no explanation, so confirm the account can go live in the app before you debug your request.
How much does the X Livestream API cost per broadcast?+
Nothing published says. The pay-per-use rate card lists twelve read resources and fifteen write actions by name, and none of them is a broadcast, a source or a livestream. The rate-limit reference has fourteen per-namespace tables plus a catch-all, and Livestream appears in none of them. The only published limit is in prose: 450 requests per 15 minutes per user on the source routes and on POST /2/broadcasts.
- X developer changelog, 'Livestream API: new /2/sources and /2/broadcasts routes; /2/users/:user_id routes deprecated', 5 November 2026, including the 1 January 2027 removal date (accessed 9 November 2026)
- X developer docs, 'Migrating to the /2/sources and /2/broadcasts routes': the full route mapping, the source id versus stream key table, the field renames, the data wrapper table and the old-to-new error mapping including 412 preconditionFailed becoming 403 about:blank (accessed 9 November 2026)
- X live OpenAPI description at api.x.com/2/openapi.json, version 2.174: both route sets present with no deprecated flag on any of them, CreateUserSourceResponseDataSource.id described as 'also the RTMP stream key' and rtmp_stream_key as 'equal to id', CreateBroadcastRequest with additionalProperties false and chat_option typed integer 0 to 6, the 26-value broadcast.fields enum carrying source_id, and GET /2/sources/:id/stream_key requiring broadcast.write (accessed 9 November 2026)
- X developer docs, 'Stream Source Management' and 'Go Live': the source object fields, the 450 per 15 minutes per user source rate limit, the five-minute TIMED_OUT rule, the 201 versus 200 behaviour and the complete error table (accessed 9 November 2026)
- X developer docs, 'Pricing': the complete pay-per-use read and write tables, which contain no broadcast, source or livestream row (accessed 9 November 2026)
- X developer docs, 'X API Rate Limits': fourteen per-namespace tables plus 'Other endpoints', none of which lists a /2/sources or /2/broadcasts route (accessed 9 November 2026)
- Official X Python SDK, xdk 0.10.6 on PyPI, uploaded 25 July 2026: the xdk.broadcasts client exposes only the scheduled-broadcast methods and send_broadcast_chat, there is no sources module and no /2/sources path anywhere in the wheel (downloaded and inspected 9 November 2026)
Product designer and indie hacker. Runs the agent on his own X account every day and writes up what the data shows, including when it's inconvenient.
Follow on X →