X Calls API: the 10 endpoints X never announced
The X Calls API is live: 10 endpoints, PCM16 audio over a WebSocket, and three OAuth scopes that appear nowhere in X's documented scope list.
The short version
- ▸There are 10 Calls operations in the live X API spec: create a call, read one, join, leave, and six for scheduled calls. Version 2.174 of the spec defines all of them, and the X API changelog has never carried an entry announcing any of it.
- ▸This is a voice-agent API in everything but name. POST /2/calls/:id/join returns a media_url and a single-use media_token; you open a WebSocket to that URL and exchange PCM16 little-endian mono audio at 8, 16, 24 or 48 kHz. X's own description of POST /2/calls says the host is 'typically a bot'.
- ▸The three scopes it needs (calls.read, calls.write, calls.join) appear zero times in X's 4.8 MB documentation bundle, including the 24-row scope table on the OAuth 2.0 page. The spec is the only place they are written down.
- ▸A call id is also its join secret. X's own field description for Call.id reads 'Unique identifier of the call; also its join secret', so logging a call id is logging a way in. The join_policy you set at create time (open, closed or trusted) is the only thing standing in front of it.
- ▸Nothing about it is priced or rate-limited in public. The pay-per-use pricing table has no Call row and the rate-limit page has no /2/calls row, so the Developer Console is the only place to find what a call actually costs.
Quick answer
The X Calls API is a live, unannounced set of 10 endpoints under /2/calls that lets code start an X call, hand out its join link, schedule it on a calendar, and put a software participant inside it over a WebSocket carrying PCM16 audio. It is defined in spec version 2.174 and it is not in the changelog, not in the machine-readable docs index, and the three OAuth scopes it requires are absent from every scope list X publishes. The reference pages exist but they are generated spec dumps: the socket protocol you need to actually build against it has never been written down.
Last updated: November 2026
Everything below was measured on 12 November 2026 against the live OpenAPI description at api.x.com/2/openapi.json, cross-checked against X's pricing page, rate-limit page, the three llms.txt indexes and the 4.8 MB llms-full.txt bundle.
The 10 operations
| Method and path | What it does | Scope |
|---|---|---|
POST /2/calls | Start a call hosted by the authenticated user | calls.write |
GET /2/calls/{id} | Title, join policy, host, participant count | calls.read |
POST /2/calls/{id}/join | Start a media session in the call | calls.join |
POST /2/calls/{id}/leave | End that media session | calls.join |
POST /2/calls/scheduled | Put a call on the organiser's calendar | calls.write |
GET /2/calls/scheduled | List calls on the user's calendar | calls.read |
GET /2/calls/scheduled/{id} | Read one scheduled call | calls.read |
PUT /2/calls/scheduled/{id} | Move, rename or re-invite | calls.write |
DELETE /2/calls/scheduled/{id} | Cancel it | calls.write |
POST /2/calls/scheduled/{id}/rsvp | Accept, decline or tentative | calls.write |
Eight of the 10 accept OAuth 1.0a user tokens as an alternative to OAuth 2.0. The two that do not are join and leave, the pair that moves audio. Those are OAuth 2.0 user context only, with no fallback, which matters if you were planning to drive this from an older twurl-shaped stack. The split is worth reading alongside which auth each X API endpoint actually takes, because this is the first endpoint group on the platform that quietly drops OAuth 1.0a entirely.
The scopes do not exist on paper
X's OAuth 2.0 guide carries a scope table. It runs 24 rows, from tweet.read at the top to broadcast.write at the bottom, and it is the canonical list every tutorial copies.
calls.read, calls.write and calls.join are not in it.
That is not a reading error. llms-full.txt is X's own single-file dump of the entire documentation set, 4,860,318 bytes on 12 November 2026. Each of those three strings appears in it zero times. The spec is the only published artefact that names them, and it names them only inside the security block of each operation.
Practically, you request them in the authorize URL like any other scope and find out whether the consent screen accepts them. There is no documented display string, no description row, and no entry in the authentication mapping guide that tells you which endpoints a scope unlocks. For a surface that can put a microphone-shaped thing into a conversation, that is a thin paper trail.
Join is the interesting one
POST /2/calls/{id}/join is not a "mark me as present" call. It starts a media session, and the 200 response is a handshake:
| Field | What it carries |
|---|---|
session_id | The media session id |
media_type | websocket |
media_url | Where to open the socket |
media_token | Bearer credential for that socket, single use, short-lived |
expires_at | When the token dies (RFC 3339) |
admission | immediate, or requires_approval when the host must let you in |
You open a WebSocket to media_url, send the media_token as a Bearer credential, and from there you exchange audio and events. The request body lets you choose the format: media.type is websocket (the only value), media.audio.encoding is pcm16 (the only value, little-endian mono), and media.audio.sample_rate is 8000, 16000, 24000 or 48000, defaulting to 24000.
Raw PCM16 at 24 kHz is the native input format of every realtime speech model shipping right now. That is not a coincidence, and X is not being coy about the intent: the description on POST /2/calls says the call is "hosted by the authenticated user (typically a bot)", and the description on POST /2/calls/scheduled tells you to "put an agent in it at start time with POST /2/calls/:id/join".
Here is the gap. The event schema on that socket is undocumented. The strings media_token, pcm16, requires_approval and session.ended each appear exactly once in the entire documentation bundle, which is the one sentence of endpoint description they come from. There is no event list, no error taxonomy, no reconnect guidance, no sample code in any language. The single named event anywhere is session.ended with reason: left, mentioned in passing on the leave endpoint. Anyone building a voice agent on this is reverse-engineering a protocol by connecting to it.
Neither official SDK helps. The Python and TypeScript XDK indexes mention calls zero times, which matches the module inventory of the published Python wheel: no Calls module at all. Same story as the gaps catalogued in the X API OpenAPI spec, where the spec consistently runs ahead of both the docs and the generated clients.
The call id is the join secret
Read this field description twice, because it is doing a lot of work:
id- Unique identifier of the call; also its join secret.
A call id is not an opaque handle you can sprinkle through logs and error reports. It is the credential. The same note appears on ScheduledCall.call_id. The only barrier in front of a leaked id is join_policy, which you set once at create time and cannot change afterwards through any of the 10 endpoints:
open(the default): anyone with the link joinsclosed: the host admits each person individuallytrusted: members of the host's Verified Organization join, everyone else asks
Note which one is the default. If you build anything that creates calls programmatically and stores or forwards the ids, pass join_policy: "closed" or "trusted" explicitly on every create, scrub ids from your logs, and treat an id in a support ticket the way you would treat a session cookie. A joiner can also check viewer_may_join on GET /2/calls/{id} to learn whether they get in without asking.
One more asymmetry: the recipient's settings still win. X's help documentation gates calls behind a prior Direct Message, and the four permission toggles are exposed on PUT /2/chat/settings as accept_calls_from_everyone, accept_calls_from_following, accept_calls_from_verified and accept_calls_from_addressbook. Those belong to the person being called, not to your app. The consumer-side rules, including the privacy default that leaks IP addresses peer to peer, are in audio and video calls on X, and the container they live in is XChat.
The scheduling half is a calendar, not a queue
Six of the 10 endpoints are about scheduled calls, and they are closer to Outlook than to a job runner. POST /2/calls/scheduled takes start_time (RFC 3339, not in the past), up to 25 invitee_ids, a duration_minutes that defaults to 30 and tops out at 44,640, and an optional RFC 5545 recurrence RRULE such as FREQ=WEEKLY;BYDAY=MO with an IANA time_zone. Invitees get an email unless you pass notify_invitees: false.
A few edges that will bite:
- 44,640 minutes is 31 days. The maximum call length on paper is a month long.
- Recurring edits need two parameters.
scopetakesall,thisorthis_and_following, andthis-style scopes needoccurrence_start_timeto say which occurrence you mean.scopedefaults toallwithout it andthiswith it. - Linked calendars are read-only.
GET /2/calls/scheduledreturns X Calls events and events from Microsoft Outlook calendars the user linked to X. Outlook-sourced entries havecalendar: "outlook", noid, and noinvitees, so you cannot update, cancel or RSVP to them through the API. Those have to be changed in Outlook. - The list window is capped. It defaults to the next 24 hours and may span at most 7 days, with
max_resultsbetween 1 and 100 defaulting to 20. - Reads are field-gated.
call.fieldsoffers 8 values andhost_idis only reachable throughexpansions=host_id;scheduled_call.fieldsoffers 12, withcall_id,inviteesandorganizer_idbehind expansions. The pattern is the same one documented for X API expansions.
Cancelling something already cancelled succeeds, which is a sane idempotency choice and rarer in this API than it should be.
Nobody has priced it
X prices resource categories, not routes. Reads run from $0.001 to $0.010 per resource, writes from $0.005 to $0.200 per request, and webhook events get their own table. There is no Call row in any of them, and no /2/calls row on the rate-limit page either.
X's pricing page covers itself: different endpoints have different costs, and current rates live in the Developer Console. So the honest answer is that the Console is the only authority, and the first call you make is also your price discovery. If you are modelling spend, what pay-per-use actually costs is the arithmetic to start from, and a media session that bills per minute would behave unlike anything else on the platform.
Account shape is its own question. The endpoint description says the host is "typically a bot", and bot accounts are created and tokened through their own endpoint group, covered in the X Bots API. Whether a bot token can hold calls.join is not documented anywhere, which is the sort of thing you learn from a 403.
What this is for, and what it is not
The shape of the surface tells you the use cases X has in mind. A support agent that answers a call in an XChat conversation. A standing weekly call with an AI notetaker that joins on the recurrence. A voice interface to a product, reachable from a join link in a post. All three are now buildable against a documented request body and an undocumented socket.
What it is not is a growth tool. There is no discovery here, no way to reach someone who has not DMed you first, and no broadcast. Calls are a one-to-few surface with a hard prerequisite on the other person's prior action, and X built it that way on purpose. If what you want is reach rather than conversation, the API side of that story has been narrowing since the February 2026 reply restrictions, and what the X API is and how to use it is the current map.
That gap is where browser-driven tools like X-Autopilot live, and the trade is worth stating plainly rather than selling. Driving your own browser on your own machine is not the sanctioned path, and we do not claim it complies with X's terms: browser automation is a grey area and X's automation rules are the document to read before deciding anything. What it offers against cloud schedulers is a lower detection surface, not an absent one - residential IP, your own browser session, no delegate account, no shared cloud IP pool. Reduced exposure. If sanctioned access is a hard requirement, the API is the route, and /2/calls is now part of it whether or not X has said so.
The bottom line
Ten endpoints, three undocumented scopes, one undocumented WebSocket protocol, and no price. The Calls API is real, it is in the spec, and the reference pages are live at docs.x.com/x-api/calls/* even though the index that AI tools read has never heard of them.
If you want to build on it, start from the spec rather than the docs, set join_policy explicitly on every create, treat call ids as secrets, and expect to discover the socket protocol by connecting to it. And watch the changelog: when X finally announces this, the announcement is also likely to be the first time the scopes get a definition.
Frequently asked
Answers indexed by Google + AI assistants.
Does the X API let you make phone calls?+
It lets you create, join and schedule X calls, which are the audio and video calls inside XChat rather than telephone calls. POST /2/calls starts one and returns a join_url; POST /2/calls/:id/join starts a media session that streams PCM16 audio over a WebSocket. There is no PSTN dialling and no phone number anywhere in the surface.
What scopes does the X Calls API need?+
calls.read for the two GET endpoints, calls.write for create, schedule, update, cancel and RSVP, and calls.join for join and leave. None of the three appears in X's documented scope table, which lists 24 scopes ending at broadcast.write. The only published source for them is the OpenAPI spec at api.x.com/2/openapi.json.
Can an AI agent join an X call and talk?+
That is what the endpoint is built for. Join returns media_url plus a short-lived single-use media_token, and X's description says to open a WebSocket with the token as a Bearer credential to exchange PCM16 audio and events. The user enters the call when the WebSocket connects. What X has not published anywhere is the event schema on that socket, so the message format has to be discovered by connecting to it.
How much does the X Calls API cost?+
X has not published a figure. The pay-per-usage page prices resource categories such as Posts Read at $0.005 and Post Create at $0.015, and there is no Call category on it. X's pricing page says different endpoints have different costs and points you at the Developer Console for current rates, so treat the Console as the only authority and watch your credit balance on the first call you make.
Why can't I find the Calls endpoints in X's docs?+
The 10 reference pages exist at docs.x.com/x-api/calls/*, but they are missing from x-api/llms.txt, the machine index that AI tools and the docs MCP server read to discover pages. The string x-api/calls appears zero times in it. The pages themselves are generated spec dumps with no prose guide, no quickstart and no WebSocket protocol reference.
Can a bot call anyone on X?+
No. The recipient side is still governed by XChat call permissions, and X's help documentation requires that a person has sent you a Direct Message at least once before you can call them. The API exposes those permissions as accept_calls_from_everyone, accept_calls_from_following, accept_calls_from_verified and accept_calls_from_addressbook on PUT /2/chat/settings, which is the user's own setting rather than yours.
- X live OpenAPI description at api.x.com/2/openapi.json, spec version 2.174: the 10 operations tagged Calls (POST /2/calls, GET /2/calls/{id}, POST /2/calls/{id}/join, POST /2/calls/{id}/leave, GET and POST /2/calls/scheduled, GET PUT DELETE /2/calls/scheduled/{id}, POST /2/calls/scheduled/{id}/rsvp), their calls.read / calls.write / calls.join security entries, the CreateCallRequest join_policy enum of open, closed and trusted, the JoinCallMediaAudio pcm16 encoding with sample rates 8000, 16000, 24000 and 48000, the JoinCallResponseData media_url, media_token, expires_at and admission fields, the ScheduleCallRequest limits of 25 invitee_ids and 44,640 duration_minutes, and the field description 'Unique identifier of the call; also its join secret' (accessed 12 November 2026)
- X developer docs, 'Join a Call': the endpoint reference page, which states the response carries a media_url and a short-lived media_token to open a WebSocket with the token as a Bearer credential to exchange PCM16 audio and events, that the user enters the call when the WebSocket connects, and that closed or trusted calls return admission requires_approval (accessed 12 November 2026)
- X developer docs OAuth 2.0 user access token guide: the full documented scope table of 24 scopes, from tweet.read to broadcast.write, containing no calls.read, calls.write or calls.join entry. Verified against docs.x.com/llms-full.txt, the complete 4,860,318-byte documentation bundle, in which the strings calls.read, calls.write and calls.join each appear zero times (accessed 12 November 2026)
- X developer docs machine index docs.x.com/x-api/llms.txt, 384 pages across 29 sections, in which the string 'x-api/calls' appears zero times, while the 10 pages under docs.x.com/x-api/calls/ return 200 and are present in docs.x.com/llms-full.txt (accessed 12 November 2026)
- X developer docs, 'X API pay-per-usage pricing and credits': the read table (Posts Read $0.005 per resource, User Read $0.010), the write table (Post Create $0.015, DM Interaction Create $0.015), the webhook event table and the Owned Reads list, none of which contain a Call row, plus the statement that different endpoints have different costs and current rates live in the Developer Console (accessed 12 November 2026)
- X API changelog, read in full from the 10 November 2026 MCP read-only entry back through the February 2026 pay-per-use launch: no entry announcing the Calls endpoints, the calls scopes, or the media WebSocket (accessed 12 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 →