Skip to content
SocialsForAI
Menu

How to make a Twitter (X) bot

An X bot in 2026 is a script, an API key and a budget.

The code is the easy part. The parts that bite are OAuth, X's per-post pricing, rate limits, and not getting the account limited for duplicates or bursts.

The short answer · Updated Nov 1, 2026

To make a Twitter (X) bot you need an X account for the bot, a way to authorize posting (your own X developer app with OAuth 2.0, or a posting API that handles OAuth for you), and code that creates posts on a schedule or in response to events. X charges $0.015 per post created and $0.200 per post containing a link.

X: live. Posting to X works on the API today.

X API fees for a bot, per 30-day month
Posts per dayPosts per monthWithout links ($0.015)Every post has a link ($0.200)
130$0.45$6.00
390$1.35$18.00
5150$2.25$30.00
24 (hourly)720$10.80$144.00
100 (our per-account cap)3,000$45.00$600.00

X's fees only, computed from docs.x.com pricing, read 2026-11-01. X notes prices are subject to change.

What do you need to build a Twitter bot?

You need four things: an X account for the bot, permission to post as it, code that decides what to post, and something that runs that code on time.

Permission is the hard part. Posting as an account through X's API v2 means an OAuth 2.0 flow with PKCE, token storage, and refreshing tokens before they expire, or the bot stops working. Our X connector requests the scopes tweet.read tweet.write users.read media.write offline.access, stores the tokens encrypted (AES-256-GCM) and refreshes them automatically, so your code only ever sees an account id.

The code that decides what to post can be anything: a script that reads an RSS feed, a build pipeline that announces releases, or an AI agent. If the agent runs in Claude Code or Cursor, our X MCP server gives it a create_post tool and you write no HTTP code at all. For no-code workflows, see n8n.

How much does it cost to run an X bot?

X bills API usage per request. Its developer pricing page lists Post: Create at $0.015 per request and Post: Create with a URL at $0.200 per request, with no subscription or minimum spend. A bot that posts links pays about 13 times more per post than one that doesn't.

The table below is that math over a 30-day month. It covers X's fees only. On our side, the Free plan includes 100 posts a month and 3 active accounts, which covers a bot posting three times a day; see pricing for the paid plans and the X credit they include. Our X API pricing page has the full breakdown.

One more line item: X charges $0.005 per post returned by a read. We read each X post back once after publishing to confirm it exists, and if your bot reads mentions or timelines, those reads are billed by X at the same rate.

A worked example: a build-status bot

Here is the full round trip for a bot that announces passing builds. First the request. The Idempotency-Key is the build number, so if your CI job retries the step, the second call returns the first post instead of creating another.

bash
curl https://socialsforai.vercel.app/v1/posts \
  -H "Authorization: Bearer $SOCIALS_API_KEY" \
  -H "Idempotency-Key: build-1482" \
  -d '{
    "accounts": ["acc_x_bot"],
    "text": "Build 1482 passed. 412 tests, 0 failures. Deploying to production now.",
    "wait": true
  }'

What does the response look like?

With wait: true the call returns after X has answered. The post is published, the target has X's post id and URL, and verified is true because we read the post back. The text is 70 weighted characters, well under 280, and has no link, so X's fee for it is $0.015.

If the same request is sent again with the same key, you get this same body back with the header Idempotent-Replayed: true. Reusing the key with different text returns 409 idempotency_key_reused.

json
{
  "id": "post_Qm3v9XcR1aZ0bL7e",
  "object": "post",
  "status": "published",
  "text": "Build 1482 passed. 412 tests, 0 failures. Deploying to production now.",
  "media": [],
  "platform_options": {},
  "scheduled_at": null,
  "created_at": "2026-11-01T15:02:11.204Z",
  "completed_at": "2026-11-01T15:02:12.918Z",
  "metadata": {},
  "targets": [
    {
      "id": "tgt_k2Hs8PZq0nV4wYcT",
      "account_id": "acc_x_bot",
      "platform": "x",
      "status": "published",
      "url": "https://x.com/buildbot/status/1852039912345678901",
      "external_post_id": "1852039912345678901",
      "attempts": 1,
      "next_attempt_at": null,
      "published_at": "2026-11-01T15:02:12.611Z",
      "verified": true,
      "error": null
    }
  ]
}

How do I post from TypeScript or Python?

The same call in TypeScript with fetch, run from a cron job, a serverless function or a CI step. It throws on any non-2xx status so your scheduler marks the run as failed and you see the error code in the logs.

typescript
const res = await fetch("https://socialsforai.vercel.app/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SOCIALS_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `build-${buildNumber}`,
  },
  body: JSON.stringify({
    accounts: [process.env.X_BOT_ACCOUNT_ID],
    text: `Build ${buildNumber} passed. ${tests} tests, 0 failures.`,
    wait: true,
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.fix ?? body.error.message}`);
console.log(body.targets[0].url);

And the same bot in Python?

The Python version with requests. On a 422 it prints each preflight issue's code and fix, which is usually all you need to correct a template.

python
import os, requests

API = "https://socialsforai.vercel.app"
r = requests.post(f"{API}/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['SOCIALS_API_KEY']}",
             "Idempotency-Key": f"build-{build_number}"},
    json={"accounts": [os.environ["X_BOT_ACCOUNT_ID"]],
          "text": f"Build {build_number} passed. {tests} tests, 0 failures.",
          "wait": True})
if r.status_code == 422:
    for issue in r.json()["error"]["issues"]:
        print(issue["code"], "->", issue.get("fix"))
r.raise_for_status()
print(r.json()["targets"][0]["url"])

What gets an X bot limited?

Repeating yourself and posting in bursts are the two patterns that get automated accounts in trouble, and both are checked before anything reaches X.

  • Duplicate text. If the same account already has the same text published or queued in the last 24 hours, preflight returns duplicate_content. Add a variable part (a build number, a date, a count) to every template.
  • Bursts. X's rate-limit docs list POST /2/tweets at 100 requests per 15 minutes per user and 10,000 per 24 hours per app. Our fair-use cap is stricter: 100 posts per X account per rolling 24 hours, returned as daily_cap_reached. Spread posts out with scheduled_at instead of firing them together.
  • Links in every post. Each one costs $0.200 at X's rates, and our preflight attaches an x_link_cost warning (it doesn't block). Moving the link into a reply doesn't lower the bill: the reply is itself a post with a URL, so the pair costs $0.215. If cost matters, post without the link or give X its own link-free text in platform_options.x.text.
  • X's own automation rules. X publishes rules for automated accounts in its Help Center. Read the current version before launch; we don't paraphrase them here because they change.

Common mistakes when building a Twitter bot

These are the common failures, with the fix for each.

  • Counting characters with text.length. X counts URLs as 23, emoji as 2 and CJK characters as 2. A 260-character JavaScript string can be over the limit. Use our X character counter or dry_run, which apply X's weights. Details on the X character limit page.
  • Posting the same template every hour. The second identical post within 24 hours fails with duplicate_content.
  • Retrying without an idempotency key. A timeout on your side doesn't mean the post failed. Without a key, the retry can publish a second copy.
  • Scheduling with a local timestamp. scheduled_at must include Z or an offset like -05:00; 2026-11-10T14:00:00 without one is rejected with a 400 validation_error.
  • Sending video. We don't post video to any network yet; preflight returns video_not_supported. Images work: up to 4 per X post, 5 MB each.

How do I fix X bot errors?

Every error carries a stable code, a message and a fix. Preflight errors come back as a 422 preflight_failed with an issues array, and nothing is published. Errors from X itself land on the target after publishing starts.

  • text_too_long: the message gives the weighted count, for example "Text is 283 characters; X allows 280." Shorten it, or set platform_options.x.text to a shorter version.
  • duplicate_content: same text on the same account within 24 hours. Vary the text.
  • daily_cap_reached: 100 posts on this account in 24 hours. Schedule the rest later.
  • account_needs_reauth: the token couldn't be refreshed. Create a new connect link for the bot account and approve it again. An account.needs_reauth webhook fires when this happens.
  • platform_rate_limited or platform_unavailable: X returned 429 or a 5xx. These retry on their own after 30s, 2m, 10m, 30m and 2h (6 attempts), then the target is failed.
  • platform_rejected: X refused the post for another reason. The message includes X's response; adjust the post and send it with a new idempotency key.

How to make an X bot, step by step

This is the path through our API. The same steps apply if you build on X's API directly; you take on OAuth, token refresh and retries yourself.

  1. 01

    Create a separate X account for the bot

    Use a dedicated account, not your personal one. If the bot misbehaves, the limits land on that account, and you can revoke its access without touching anything else.

  2. 02

    Get an API key

    Send POST https://socialsforai.vercel.app/v1/workspaces with a name. The response contains an api_key starting with sk_. It is shown once, so store it in your secrets manager or .env file right away.

  3. 03

    Connect the bot's X account

    Send POST /v1/connect-links with platforms set to x. Open the returned url in a browser where you are signed in as the bot account and approve. The account id (acc_...) appears in GET /v1/accounts and on the account.connected webhook.

  4. 04

    Dry-run the first post

    Send POST /v1/posts with dry_run: true. Nothing is published. The response lists every issue, such as text_too_long or duplicate_content, with the exact fix.

  5. 05

    Publish from a cron job or event

    Drop dry_run and call the same endpoint from your scheduler, a GitHub Action, an n8n workflow or an agent. Add an Idempotency-Key header so a retried request never posts twice.

  6. 06

    Listen for results

    Register a webhook for post.published and post.failed, or poll GET /v1/posts/:id. Failed targets carry an error code, a message and a fix.

Questions

What developers ask

Are bots allowed on X?

X permits automated accounts under its automation rules, which it publishes in the X Help Center. Read the current rules before you launch, because they decide what gets an account limited. Separately, our API blocks two common problems before they reach X: identical text on the same account within 24 hours, and more than 100 posts per account in 24 hours.

How much does an X bot cost to run?

At X's published rates, a bot posting 5 times a day without links pays about $2.25 a month in X fees (150 posts at $0.015). If every post has a link, the same bot pays $30 a month, because X charges $0.200 per post with a URL. Our Free plan covers 100 posts a month on top of that.

Do I need my own X developer account?

Not through our API. We handle the OAuth 2.0 flow, token refresh and the X developer app, and your code only needs our API key and the bot's account id. If you build directly on X's API instead, you need a developer account, an app with the tweet.write scope, and your own token storage and refresh logic.

Can an X bot post images?

Yes. Pass up to 4 images in the media array, each a public https URL of at most 5 MB, with optional alt text. We download each file and upload it to X's media endpoint before creating the post. Video isn't supported on any network yet; a video item fails preflight with video_not_supported.

Can I test an X bot without posting publicly?

Yes, two ways. Send dry_run: true and you get every preflight issue without publishing anything. Or connect the sandbox platform, a fake account that accepts posts; putting [fail:retryable], [fail:permanent] or [fail:auth] in the text simulates each failure type so you can test your error handling.

Is X posting live on SocialsForAI today?

Yes. The X connector is live and handles OAuth, media upload, retries and read-back verification as described here. Bluesky, LinkedIn, Mastodon and Telegram are live too, and the same bot code posts there by adding their account ids to the accounts list.

Get an API key and send your first post in five minutes.