X Bots API: create a bot account with no login
X now mints bot accounts from one API call: a real @handle with no password, a single bearer token, and an Automated by label you cannot take off. The full flow.
The short version
- ▸POST /2/bots creates a real X account from one API call. It gets a user ID, an @handle and a display name, but no password and no login screen. The only way to act as it is the bearer token the call returns, which starts with xcbot_ and is shown exactly once.
- ▸The bot belongs to your project, not to you. Six endpoints cover its whole life, and DELETE /2/bots/:id permanently destroys the X account rather than just unhooking it from your app.
- ▸The Automated by @owner label is applied for you and points at the X account that owns the client app. That is the opposite of the older automated-account label, which you had to switch on yourself from the bot's settings and confirm with a human account's password.
- ▸Default allowance is one bot per project. The list response reports your real ceiling in meta.max_bots, and creating past it errors out.
- ▸The hard part is not creating the bot, it is receiving the first message. A DM from someone who does not follow the bot lands in Message requests, GET /2/chat/conversations returns an empty list with has_message_requests set to true, and the request inbox lives at /2/chat/message_requests rather than under /2/chat/conversations/.
Quick answer
POST /2/bots creates a working X account in one call. It is a real account with a user ID, an @handle and a display name, but it has no password and no login, and the only way to act as it is the bearer token the call hands back. The token starts with xcbot_, is returned exactly once, and arrives with dm.read, dm.write, tweet.read, users.read and media.write already granted. X applies an "Automated by @owner" label to the profile automatically. Default allowance is one bot per project.
Last updated: October 2026
Every guide to building a bot on X still describes the 2019 route: register a second account by hand, add a phone number, apply for API access, wire up OAuth 1.0a, then go and toggle the automated-account label in settings. None of that is how this works. The bot account is now an object your app creates and owns, and the interesting problems have moved somewhere else entirely.
The create call, and the token you get once
Authentication is the first surprise. All six bot endpoints use OAuth 2.0 app-only auth, so you call them with your app's bearer token. No user context, no scopes, no consent screen.
POST /2/bots
Authorization: Bearer <app bearer token>
{"handle": "my_support_bot", "display_name": "Support Bot"}
201
{
"data": {
"id": "2075014963136012288",
"username": "my_support_bot",
"name": "Support Bot",
"token": "xcbot_...",
"expires_at": 1787761273000,
"scopes": ["dm.read", "dm.write", "tweet.read", "users.read", "media.write"]
}
}
handle is required and runs 1 to 15 characters of letters, digits and underscores. display_name is optional, caps at 50 characters, and defaults to the handle. scopes is optional and may only narrow that default set; ask for anything outside it and you get a 400.
That token field is the whole account. It is returned on creation and on rotation and can never be fetched again, so a bot whose token you failed to persist is a bot you now have to rotate. A bot holds one active token at a time, and every mint revokes whatever was outstanding.
Create is idempotent on the handle, which is a nicer design than it first looks. Repeat the call for a handle that already names one of your project's bots and you get that bot back with a fresh token, the old one revoked. A lost response is therefore safe to retry, because the token you never saw is the token that just got invalidated.
Six endpoints, one of which is irreversible
| Endpoint | What it does |
|---|---|
POST /2/bots | Create a bot, return its token |
GET /2/bots | List the project's bots, plus meta.max_bots |
POST /2/bots/:id/token | Rotate: mint a new token, revoke the old one |
PUT /2/bots/:id | Update handle, display_name or dm_permission |
DELETE /2/bots/:id/token | Revoke tokens, keep the account |
DELETE /2/bots/:id | Revoke tokens and permanently destroy the X account |
Read those last two rows carefully, because they are one word apart in the URL and very far apart in consequence. DELETE /2/bots/:id/token is a kill switch: the account survives, posts stay up, and you can rotate a new token later to bring it back. DELETE /2/bots/:id destroys the account and removes it from the project. The handle, the history and the conversations go with it.
dm_permission on the update endpoint takes everyone, premium or no_one. It is the one field the idempotent create will not touch, so the only way to change who may message your bot is an explicit PUT.
The label is applied to you, not by you
Bot profiles carry an "Automated by @owner" label pointing at the X account that owns the client app. You do not ask for it and there is no parameter to turn it off.
This is a real change in posture. The older automated account label is a self-selected setting: you open the bot account's own settings, find the Automation menu, nominate a human managing account, and confirm with that account's password. It is also the single most broken feature on X's developer forum, because the menu is simply absent on a lot of accounts and X's own help page still describes the feature as in testing.
A bot created through POST /2/bots sidesteps all of that. It has no settings screen to visit and no password to confirm with, so X does the labelling at creation time from the project that owns it. If you have ever spent an afternoon hunting for an Automation menu that was never going to appear, that is the afternoon this endpoint gives back.
Worth being plain about the trade: you cannot build a bot here that does not announce itself. X's Developer Guidelines already require automated accounts to identify themselves and name their operator, so the label enforces an obligation rather than adding one. The API route is the sanctioned route, which is exactly why its rules are written into the response.
One bot, probably
Default allowance is one bot per project. Your plan sets the real number and GET /2/bots reports it in meta.max_bots; creating past the cap errors. Rate limits are per-app in 15-minute windows.
That default kills one architecture outright. If your plan was a bot per customer, a bot per campaign or a bot per inbox, read meta.max_bots before you write the provisioning code, because the shape you want may need an enterprise conversation rather than a loop.
Getting the first message is the actual hard part
Creating the bot takes a minute. Getting a human's first message into it is where people lose days, and the cause is a feature of the DM product rather than a bug in the API.
GET /2/chat/conversations returns the primary inbox only. A message from somebody who does not follow the bot does not go there. It goes to Message requests, and the conversations call answers a cheerful 200 with result_count: 0 and has_message_requests: true. Nothing is broken. Your bot simply cannot see the thing it is being told exists.
The request inbox is at GET /2/chat/message_requests, and this is the detail that costs people the most time: the path has no /conversations/ segment in it. A developer reported exactly this on 27 August 2026 against a bot created with POST /2/bots, hitting /2/chat/conversations/message_requests and getting a 503 while has_message_requests stayed true. X staff acknowledged it on 27 and 28 August and no resolution was posted in that thread. The documented paths today are:
GET /2/chat/message_requestsfor the pending list, which can return an empty page with anext_tokenstill setGET /2/chat/message_requests/{id}for one request and its newest events, which marks those messages delivered just like opening the request in the appPOST /2/chat/message_requests/{id}/acceptto move the conversation into the inbox, returning 404 when there is nothing pending, including after a successful accept
Replying to a request does not accept it. That one sentence in X's reference explains most "the bot answered and then went silent" reports.
A second trap shows up in that same forum thread: GET /2/activity/stream returned 403 Unsupported Authentication when called with the bot's own token. X's own starter bot matches that, wanting the bot token for Chat calls and a separate app-only bearer purely so the activity stream can discover threads. Plan for two credentials in the worker, not one.
What it costs
The Chat API was announced as free for a limited amount of time on 30 July 2026, with no end date given. Treat that as a window rather than a price.
Everything around it meters normally under the pay-per-use model we broke down in X API pricing. If you wire the bot up to real-time delivery, each chat.received event costs $0.010 and chat.sent is not billed, which is one of the cheerier rows in the X Activity API price table. Rate limits are tighter than the pricing: 25 message sends per 15 minutes and 500 per 24 hours, per app and per user, which caps a support bot at roughly 500 replies a day before anything else does.
Encryption is not optional either. Chat is end-to-end encrypted, so the bot needs its own keypairs registered and the Chat XDK doing encrypt, decrypt and signing in your process. If you want the user-facing side of that system first, XChat explained covers what it is and how people turn it on.
Is this the bot you want
It fits one job well: a labelled, project-owned account that answers encrypted DMs on behalf of a business. Order status, booking confirmations, support triage, the Barista Bar taking a coffee order in X's own example. No password to rotate, no human account to borrow, no settings menu to find.
It is the wrong tool for growth work, and the limits say so. The default scope set is DM-shaped. One bot per project is the starting allowance. The label is permanent and points at you. And the account has no login, so nobody can step in and drive it by hand when the automation says something strange, which is usually the moment you most want to.
If what you actually want is your own account posting and replying with judgment behind it, that is a different problem, and X-Autopilot solves it from your own Mac with your own session rather than by minting a second identity. If you want to know the difference between a useful bot and a flagged one before you ship either, X comment bots is the honest version of that question, and how to send a DM on X covers the limits the API inherits from the product.
Frequently asked
Answers indexed by Google + AI assistants.
How do I create a bot account on X?+
Send POST /2/bots with your app's app-only bearer token and a JSON body containing a handle of 1 to 15 characters. X returns 201 with the bot's id, username, name, a bearer token starting with xcbot_, an expires_at in epoch milliseconds, and the granted scopes. There is no signup form, no phone number and no password, because the account has no login.
Do I need the user's permission to call the bots endpoints?+
No. All six bot endpoints use OAuth 2.0 app-only authentication, so your app's bearer token is enough and no OAuth scopes are required to call them. Every operation is scoped to the calling app's project, which means you can only see and manage bots that belong to that project.
Can I hide that the account is a bot?+
No. X applies an 'Automated by @owner' label to bot profiles, pointing at the X account that owns the client app. There is no parameter to suppress it. X's Developer Guidelines also require automated accounts to identify themselves, so the label is doing something you would be obliged to do anyway.
What happens if I call POST /2/bots twice with the same handle?+
It converges rather than erroring. Repeating the call for a handle that already names one of your project's bots returns that same bot with a freshly minted token and revokes the previous one, which makes a retry safe after a lost response. It re-applies display_name if you pass it, but it never changes the bot's DM permission.
How many bots can one project have?+
The default is one. Your plan sets the real allowance and the GET /2/bots list response reports it in meta.max_bots. Creating past the cap returns an error, so read that field before you design anything that spins bots up per customer.
Why does my bot never receive the first DM?+
Because the first inbound message from a non-follower sits in Message requests, and GET /2/chat/conversations only returns the primary inbox. It answers 200 with result_count 0 and has_message_requests true. The pending requests are at GET /2/chat/message_requests, with POST /2/chat/message_requests/{id}/accept to move one into the inbox. Replying to a request does not accept it.
- X developer docs - 'Manage bot accounts with the Chat API': the definition of a bot as a programmatic project-owned account with no password or login, app-only auth on all six endpoints, the xcbot_ token format and one-time return, the default scope set, handle and display_name limits, dm_permission values, idempotent create behaviour, meta.max_bots with a default of 1, and the delete semantics (accessed 12 October 2026)
- X developer docs - 'Accept a Chat message request' and 'Get Chat Message Requests': the /2/chat/message_requests paths, the 404 on accepting with no pending request, that replying to a request does not accept it, and the pagination caveat (accessed 12 October 2026, X API v2 version 2.170)
- X Developers forum - '[Announcement] X Chat API and XDKs are now available' by @taycaldwell: public availability of the Chat API and Chat XDK, the OAuth 2.0 scope set, the per-endpoint rate limits, and the statement that the X Chat API would be free for a limited amount of time (posted 30 July 2026)
- X Developers forum - 'X Chat bot first inbound: GET /2/chat/conversations/message_requests returns 503': a developer reproducing the parked-first-message problem on a bot created with POST /2/bots, including a 403 Unsupported Authentication from the activity stream on a bot token, with X staff replies on 27 and 28 August 2026 and no resolution posted since (accessed 12 October 2026)
- Social Media Today - 'X launches API for business chatbot accounts' by Andrew Hutchinson: the business framing of the bots endpoints, the Barista Bar order-taking example, and X's quoted definition of a bot as a real account with no password and no login (published 27 August 2026)
- xdevplatform/xchat-agent-skeleton on GitHub - X's own starter bot: the environment variables a working Chat bot needs, and the README note that GET /2/chat/conversations returns only the primary inbox so non-follower threads need the activity stream to be discovered (repository created 19 August 2026, accessed 12 October 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 →