Skip to content
Delete TweetsBulk DeleteX APITweet DeletionPythonAccount Cleanup

GUIDE

Delete All Your Tweets With the X API (2026): Runnable Code, the 3,200 Ceiling, and Real Costs

How to bulk delete tweets through an API instead of a subscription tool: the enumerate-then-delete loop in Python, X's 3,200 history ceiling, the archive-export route past it, and what 1,000 deletions actually cost.

TwitterAPIs·
Deleting every tweet on an X account through an API, showing the per-call delete price, the cost per thousand deletions, and X's three thousand two hundred post history ceiling

Deleting every post on an X account is a loop, not a button. X exposes deletion one post ID at a time and nothing wider, so every product that advertises bulk deletion, paid or free, is running the same two steps underneath: collect the IDs, then call delete on each one. This guide writes those two steps out in full, prices them per call, and is specific about the two places the job goes wrong, which are the 3,200-post history ceiling on the timeline endpoints and the fact that replies and retweets are not on the timeline you are reading.

TL;DR: There is no single call that empties an account. Enumerate the post IDs, then delete them one at a time. POST /twitter/tweet/delete costs approximately $0.0008 per call, so 1,000 deletions is roughly $0.80 and the full 3,200 the timeline can reach is roughly $2.56. X's own API bills the same operation at $0.010 per request. Anything older than the most recent 3,200 posts is unreachable from any timeline endpoint, including X's own, so pull those IDs from your downloaded archive. Replies need a second pass on a different endpoint, retweets need an unretweet rather than a delete, and none of it is reversible.

Cost and ceiling panel showing eight hundredths of a cent per delete call, eighty cents per thousand deletions, and X's three thousand two hundred post history ceiling

The two numbers that decide the job: what a delete costs, and how far back you can see

Who actually wants this

The demand here is not niche and it is not developers being tidy. delete all tweets runs at approximately 880 searches a month and how to bulk delete tweets at approximately 590, measured through DataForSEO on 2 August 2026. The people behind those queries are mostly not looking for a script. They are looking for a way out of a decade of posts before a job search, a funding round, or a rebrand.

That post carries roughly 1,726 likes against 101,181 views, which is a high like rate for a request for technical help and a fair read on how many people share the problem. What the search results offer them is a row of subscription tools that want their account credentials. What a developer wants instead is the list of steps and the bill, which is what follows.

What our API actually does, and what it does not

Before any code, the honest inventory, read from the router rather than from a marketing page.

We serve POST /twitter/tweet/delete. It takes an id or a full status url, in the query string or a JSON body, and it acts as your own account through a registered session. We also serve the reads you need to build the ID list: GET /twitter/user/tweets, GET /twitter/user/tweets_and_replies, and GET /twitter/user/tweets/complete, which paginates internally and hands back a large slice of history in one call.

What we do not serve is a bulk endpoint. There is no delete_all, no ID array, no job queue you hand a username to. Anyone who tells you otherwise about any provider is describing a loop with a nicer wrapper on it.

We also do not raise X's history ceiling, and neither does anyone else. That is the next section, and it is the part most guides skip.

Numbered list of five things that bite when deleting a real archive: deletion is permanent, the timeline stops at 3,200 posts, replies live on a separate timeline, a 200 response is not a receipt, and retweets need an unretweet

Read this list before you write the loop, not after

The 3,200 ceiling, which is the real problem

X serves at most the 3,200 most recent posts for any account through its timeline endpoints. Page past roughly thirty-two pages of one hundred and the cursor stops returning anything, whatever the account's actual post count is. This is X's behaviour, not a limit of any API sitting in front of it, and it applies identically to X's own web client.

The size of the gap is easy to see. A live GET /twitter/user/info?username=nasa on 2 August 2026 reports tweet_count: 74283. The timeline endpoints will hand you 3,200 of those. The other 71,000 exist, they are visible on individual permalinks, and they are unreachable by paging a timeline.

For most personal accounts the ceiling is generous enough to finish the job. For a decade-old account that posted daily, it is not. That is precisely the case the person searching this usually has, so a guide that stops at the timeline loop leaves them with the newest 3,200 gone and twelve years of older posts still live, which is arguably worse than not starting.

The route past the ceiling is your data archive. X's help documentation states that downloading your archive "allows you to browse a snapshot of your X information, starting with your first post," and walks through requesting it from Settings, Account, Your X data, Request data. The archive arrives as a file containing every post with its ID. Those IDs feed the same delete loop. The enumeration source changes; the deletion code does not.

That last sentence is the whole architecture, and it is worth seeing as a shape rather than a list, because the branch is the part a linear set of steps cannot show:

Connector map of the deletion pipeline, showing the script authenticating once through customer session, paging the timeline for post IDs until the 3,200 ceiling, routing around that ceiling through the X data archive, and both ID sources feeding the same delete call which then re-reads the timeline to verify

Two ways in, one way out. Only the archive edge reaches past 3,200

Read the edges rather than the boxes. customer/session is a one-time authentication that everything else runs under. The timeline and the archive are two independent sources of the same thing, post IDs, and they converge on one delete call. The archive edge is the only one that crosses the ceiling. And the edge from tweet/delete back to user/tweets is the verification pass, which is a loop rather than a final step, and is the edge most implementations leave out.

The most-upvoted community answer to this problem reached the same conclusion independently. The author of the script that ranks first for this query on Google added an update to their own post after shipping it:

the r/Twitter thread where the author of a free delete-all-tweets script, at roughly 460 upvotes and 263 comments, adds that the archive-export route is much faster and more reliable than paging the timeline from r/Twitter

Two independent implementations arriving at the same architecture is usually a sign the architecture is forced. It is.

Comparison grid of three routes to deleting tweets, covering whether each reaches past 3,200 posts, whether it runs without handing over account access, cost at five thousand tweets, and whether the code is readable

Three routes, and the row that usually decides it is the first one

Start building with TwitterAPIs

$0.04 per 1,000 tweets. $0.50 free credits. No credit card required.

Step 1: register your session once

Deletion acts as your account, so the API needs your account. POST /twitter/customer/session stores your X auth_token and ct0 cookies against your API key, and it is billed at zero. After that, every write runs as you rather than through a shared pool, which is the only sane design for an operation this destructive.

curl -X POST "https://api.twitterapis.com/twitter/customer/session" \
  -H "Authorization: Bearer $TWITTERAPIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"auth_token": "YOUR_AUTH_TOKEN", "ct0": "YOUR_CT0"}'

You can also pass the same two values per request as x-auth-token and x-ct0 headers if you would rather not store them. Both cookies come from a logged-in x.com session in your browser's developer tools.

There is no developer application to file and no project to create. On X's own API you do need a developer account with a funded credit balance before the first request, because pay-per-usage billing will not run against an empty balance.

Step 2: get the IDs

Two ways in, depending on how far back you need to reach.

Under 3,200 posts, or you only care about recent history. Use user/tweets/complete. It pages internally and returns the batch in one response.

import requests

BASE = "https://api.twitterapis.com/twitter"
H = {"Authorization": f"Bearer {API_KEY}"}

def own_user_id(username):
    r = requests.get(f"{BASE}/user/info", params={"username": username}, headers=H, timeout=30)
    r.raise_for_status()
    return r.json()["user"]["id"]

def recent_history(user_id, want=3200):
    """Walk user/tweets/complete to the ceiling, returning post IDs newest first."""
    ids, cursor = [], None
    while len(ids) < want:
        params = {"user_id": user_id, "max": 800}
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{BASE}/user/tweets/complete", params=params, headers=H, timeout=120)
        r.raise_for_status()
        page = r.json()
        ids.extend(t["id"] for t in page.get("tweets", []))
        cursor = page.get("next_cursor")
        if not cursor:            # history exhausted, or the ceiling was reached
            break
    return ids

One behaviour worth knowing before it surprises you: max is a floor, not a cap. A live call with max=40 against NASA on 2 August 2026 returned count: 59. Pages arrive in whole chunks and the walk stops after the chunk that crosses your target, so you get at most one page more than you asked for. That is deliberate. Slicing back to max while returning the cursor of the last page fetched would drop the overshoot into a gap the resume point skips over, which loses posts silently. Ask for a number, budget for a bit more.

Past 3,200 posts. Request the archive, wait for the email, download it, and read the IDs out of data/tweets.js. That file is not JSON. It is JavaScript: a single assignment of a JSON array to a window.YTD global, so the prefix has to come off before anything will parse.

import json, pathlib

def archive_ids(path="data/tweets.js"):
    raw = pathlib.Path(path).read_text(encoding="utf-8")
    body = raw[raw.index("=") + 1:]              # drop the "window.YTD... =" prefix
    return [row["tweet"]["id_str"] for row in json.loads(body)]

Cutting at the first = rather than matching the literal prefix is deliberate, because the prefix itself is not stable. Exports in circulation use window.YTD.tweet.part0 and window.YTD.tweets.part0, and the deleted-posts file uses window.YTD.deleted_tweets.part0. The x-archive-parser project documents the shape it handles, which is a list of { tweet: { id_str, full_text, created_at, ... } } objects. Print one row before you trust the parse on your own export. The IDs themselves are stable, and they are the only thing this step needs to produce.

Step 3: the delete loop

The loop itself is short. Everything that makes it survivable is the handling around it.

import json, pathlib, time
import requests

BASE = "https://api.twitterapis.com/twitter"
H = {"Authorization": f"Bearer {API_KEY}"}
STATE = pathlib.Path("delete-state.json")

def load_state():
    return json.loads(STATE.read_text()) if STATE.exists() else {"done": [], "failed": []}

def delete_all(ids, pause=1.0):
    state = load_state()
    done = set(state["done"])
    for tweet_id in ids:
        if tweet_id in done:
            continue
        r = requests.post(f"{BASE}/tweet/delete", params={"id": tweet_id},
                          headers=H, timeout=60)
        if r.status_code == 200 and r.json().get("deleted"):
            state["done"].append(tweet_id)
        elif r.status_code == 429:
            time.sleep(60)                        # backed off, retry on the next run
            break
        else:
            state["failed"].append({"id": tweet_id, "status": r.status_code,
                                    "body": r.text[:200]})
        STATE.write_text(json.dumps(state))       # checkpoint every call
        time.sleep(pause)
    return state

Four decisions in there are the difference between a job that finishes and a job you restart from zero:

Checkpoint after every call, not every batch. The state file is the only thing standing between an interrupted run and re-walking the whole archive. Writing it per call is cheap next to the request you just made.

Treat 429 as a stop, not a retry. Back off and let the next run pick up from the checkpoint. Grinding against a throttle on your own account is how a cleanup job turns into an account problem.

Record failures with their status code. A 422 means the delete did not apply, which usually means the post is not yours or is already gone. A 400 means the ID never parsed. Those are different problems and you want to tell them apart afterwards.

Pace it. Our delete route imposes no per-day cap of its own. The limiter you will meet is X's, on your own account, and X does not publish that threshold. A one-second pause is a starting point rather than a tuned value, and a few thousand posts is comfortably an overnight job.

Flow diagram of the deletion pipeline in four steps: register a session, enumerate the post IDs, delete each one while checking status codes, then verify by re-reading the timeline

The loop is four steps, and the fourth one is the one people skip

Step 4: verify, because a response body is not a receipt

Re-read the timeline when the run finishes and confirm the IDs are gone. This is not paranoia about any particular API, it is what the response can and cannot tell you.

A 200 from a delete call means the request reached X and X did not return an error. That is a weaker statement than "the post no longer exists." We tested this directly: POST /twitter/tweet/delete?id=1 returns 200 with deleted: true, for an ID that is not a real post and is not ours. A well-formed ID that does not exist behaves correctly and comes back 422 with ok: false, which is the case you will actually hit, but the lesson holds either way. Validate that your IDs are real 18-to-19-digit post IDs before the loop, and confirm the outcome by reading rather than by trusting.

def verify(user_id, expect_gone):
    r = requests.get(f"{BASE}/user/tweets/complete",
                     params={"user_id": user_id, "max": 800},
                     headers=H, timeout=120)
    r.raise_for_status()
    still_live = {t["id"] for t in r.json().get("tweets", [])} & set(expect_gone)
    return sorted(still_live)

An empty return is your finish condition. A non-empty one is a re-run list, not a failure.

The cheapest pay-as-you-go Twitter API. Try it free.

$0.04 per 1,000 tweets. $0.50 free credits. No credit card required.

The passes most cleanups forget

Running the loop above on user/tweets and calling it done leaves two categories live.

Replies. They are not on the plain user timeline. GET /twitter/user/tweets and GET /twitter/user/tweets_and_replies are separate endpoints returning separate sets, and user/tweets/complete walks the originals timeline. On a conversational account the replies outnumber the originals by a wide margin, and they are the posts most likely to be the ones you wanted gone. Run a second enumeration pass against user/tweets_and_replies and feed those IDs through the same delete loop.

Retweets. A retweet is not a post you can delete, because the underlying post is not yours. Undo it with POST /twitter/tweet/unretweet against the original ID. A delete call there will fail, and it will fail in a way that looks like a permissions problem rather than a category error, which wastes an afternoon if you have not read this paragraph.

Likes are a third category, and worth a decision rather than an assumption. They are visible on your profile and are removed with POST /twitter/tweet/unfavorite, one at a time, at the same per-call rate.

What it costs

Deletion is billed as a simple write action at approximately $0.0008 per call, the same rate as a standard read. The arithmetic is unusually easy because there is no plan and no minimum.

Archive sizeDelete callsCost at approximately $0.0008
500 posts500approximately $0.40
1,000 posts1,000approximately $0.80
3,200 posts (the timeline ceiling)3,200approximately $2.56
10,000 posts (archive route)10,000approximately $8.00

Bar chart of the total cost to clear an X archive at four sizes, from forty cents for five hundred posts to eight dollars for ten thousand posts

Total spend to clear an archive, by size

Enumeration barely registers next to the deletes. user/tweets/complete is a premium read at approximately $0.0024 per call and returns hundreds of posts per call, so building the ID list for a 3,200-post account costs a couple of cents. Signup includes approximately $0.50 in credit with no card, which covers roughly 625 deletions before you pay anything at all.

The comparison that matters is X's own rate card, which is published per request and confirms pay-per-usage with no subscriptions. Its Interaction: Delete row is $0.010 per request. Clearing 3,200 posts on X's own API is therefore $32.00 against approximately $2.56 here, before counting the developer account and the funded balance you need to make the first call. Per call, and with no monthly commitment on either side, that is a twelve-and-a-half times difference on the identical operation.

Against the subscription tools that currently occupy this search result, the comparison is different in kind rather than degree. They charge monthly whether you delete one post or forty thousand, and they ask for account access to do it. If you are deleting once and never again, a per-call bill of a few dollars against a recurring plan is not a close call. If you want a standing rule that deletes anything older than thirty days, the loop above is the same code with a date filter, and it runs on a cron for cents a month.

The parts that are genuinely annoying

Written out plainly, because a guide that pretends this is easy gets abandoned at step three.

It is not reversible. X has no undo and neither do we. Request the archive first, download it, and keep it. Do not start the loop until that file is on disk.

The ceiling will probably bite you. If your account has more than 3,200 posts, and the accounts whose owners search this usually do, the timeline route cannot finish the job. Plan for the archive route from the start rather than discovering it at post 3,199.

It takes real time. Not because the API is slow but because you are pacing against a throttle nobody publishes. Build for unattended overnight running with a checkpoint, and stop thinking about wall-clock.

Protected and suspended states change the answer. A locked or restricted account may not accept writes at all, and no client-side loop fixes that.

Search engines and archives are a separate problem. A deleted post can survive in third-party archives, in screenshots, and in caches you do not control. Deleting your posts removes them from X. It does not remove them from the internet, and any product that suggests otherwise is selling something.

Where to go next

If the enumeration half is the part you care about, the complete tweet history walkthrough covers cursor handling in more depth, and the pagination guide covers the cursor contract across every endpoint that has one. The inverse problem, recovering a post that is already gone, is in recovering deleted tweets. Every endpoint named here is listed with its parameters and per-call price in the API reference, and the write actions are on the docs site with copy-paste examples in three languages.

// sources

Where these numbers come from

Each row is a figure in this post and the artefact it was read from. Prices and limits on this platform move, so check the date on the source before you plan against it.

x-archive-parser project
Documents the archive object shape the parsing step targets, a list of tweet records carrying id_str, full_text, and created_at, and backs the warning that the file prefix varies across exports.
X API pricing rate card
The published per-request rate card confirming pay-per-usage with no subscriptions, and the source of the Interaction Delete row at $0.010 per request behind the $32.00 figure for clearing 3,200 posts.

Frequently Asked Questions

There is no single call that empties an account. Every route, whether a paid tool or your own script, deletes one post at a time, because X exposes deletion per post ID and nothing else. What differs between routes is how they get the list of IDs and how much they charge you for the loop. A per-call API deletes at approximately $0.0008 per post on TwitterAPIs, so 1,000 deletions costs roughly $0.80 with no subscription. The harder problem is not the deleting, it is the enumerating: X's own timeline endpoints stop at the most recent 3,200 posts per account, so anything older has to come from your downloaded archive.

That is X's per-account history ceiling on the timeline endpoints, and it applies to every client that reads a user timeline, including X's own web app. Page past roughly thirty-two pages of one hundred posts and the cursor runs out, no matter how many posts the account actually has. It is not a limit any third-party API can raise, because no further history is served. The way past it is your data archive, which X states covers a snapshot of your information starting with your first post. Read the post IDs out of the archive file and feed those into the delete loop.

No. X provides no undo for a deleted post and neither does any API in front of it. A deleted post ID stops resolving, and the text, media, and engagement counts are not recoverable from us or from X. Request and download your data archive before the first delete call runs, not after, and keep it. That archive is the only copy you will have, and it is also the source of the post IDs you need to reach anything older than the most recent 3,200 posts.

Slower than the arithmetic suggests, and the limiter is X rather than the API in front of it. Each deletion is one round trip against your own account, and X throttles account-level write activity without publishing the threshold, so a loop that fires as fast as the network allows will start collecting errors. Build the job to run unattended instead of fast: persist the cursor and the last deleted ID after every batch, back off when a call fails, and resume from the checkpoint. A few thousand posts is comfortably an overnight job rather than a one-minute one.

On TwitterAPIs, tweet/delete is billed at approximately $0.0008 per call, which works out to about $0.80 per 1,000 deletions and about $2.56 to clear the full 3,200 posts the timeline API can reach. Enumeration is close to free by comparison: user/tweets/complete is approximately $0.0024 per call and pulls hundreds of posts at once. Signup includes approximately $0.50 in credit with no card, enough for roughly 625 deletions before you pay anything. X's own API bills the same operation at $0.010 per request under the Interaction: Delete row of its published rate card.

No, and treating them as one set is the most common way a cleanup job leaves half the account behind. Replies do not appear on the plain user timeline; they are on the tweets-and-replies timeline, which is a separate endpoint and a separate pass. Retweets are not deletions at all: a retweet is undone with an unretweet call, and issuing a delete against the original post ID will fail because you do not own it. A complete cleanup runs three passes rather than one.

Not on a per-call API. TwitterAPIs works by registering your own X session once through customer/session, after which write actions run as your account rather than through a shared pool. There is no developer application to file, no project to create, and no monthly plan. On X's own API you do need a developer account with a funded credit balance, because pay-per-usage billing requires a positive balance before the first request goes out.

Check out similar blogs

More guides on the Twitter/X API, scraping, and pricing.

Four routes for Twitter scraping in Python compared in 2026: the official X API v2 pay-per-use rate, a cookie-authenticated account pool, a headless browser fleet, and a managed read API, weighed on unit cost, maintenance burden, terms-of-service exposure, and data completeness
Twitter scrapingPython

Twitter Scraping in Python (2026): Pick a Route Before You Write Code

Four ways to collect X data in Python, priced honestly: the official X API v2 pay-per-use rate, an account-pool scraper, a headless browser, and a managed read API. What each one actually costs to run for a year.

TwitterAPIs·
How to fetch a full Twitter thread through an API in one call in 2026, pulling the root tweet plus every connected tweet in the chain instead of paginating replies by hand
twitter thread apitweet thread

Fetch a Full Twitter Thread via API in One Call (2026)

How to pull an entire Twitter thread, the root tweet plus every connected tweet in the chain, in a single API call with the tweet/thread endpoint, instead of walking replies by hand. Live-tested code in curl, Python, and Node.js, with the real per-call cost.

TwitterAPIs·
How to search tweets by date on X in 2026, showing the search-by-date operators and the historical reach limits across three API routes
Twitter Search APIDate Search

How to Search Tweets by Date on X in 2026 (Operators, API, and the Limits Nobody Documents)

How to search tweets by date on X in 2026: the since:/until: operators, the API parameters, and the exact historical limits X's own docs bury, tested live against a real endpoint.

TwitterAPIs·
What rate limited means on X in 2026, covering the consumer account action limits and the developer API 429, with the current published numbers for both
Rate Limits429

What "Rate Limited" Actually Means on X (Every Limit, Measured)

"Sorry, you are rate limited" is one message covering two different systems: a consumer action ceiling and a developer API window. Here is what the term means, the current numbers for both, where it came from, and how long it actually lasts.

TwitterAPIs·
Auto-posting to Twitter (X) in 2026, comparing seven social media scheduler tools against posting directly through an API on real cost per tweet, with break-even volumes and automation policy limits
Auto Post TwitterTwitter Scheduler

Auto-Posting to Twitter (X) in 2026: 7 Scheduler Tools vs the API, Priced Per Tweet

Every way to auto post to Twitter (X) in 2026, priced in one honest unit: cost per tweet. Seven scheduler tools with live pricing, X's own per-post API rate, the volume where each route wins, and what X's automation rules actually forbid.

TwitterAPIs·
Twitter (X) API authentication in 2026, covering OAuth 1.0a and OAuth 2.0 bearer tokens, the four credential types, and how to fix 401 Unauthorized and 403 errors in Python and Node.js
Twitter API AuthenticationOAuth 2.0

Twitter API Authentication in 2026: OAuth, Bearer Tokens, and Fixing 401

How Twitter (X) API authentication works in 2026: the four credential types, OAuth 1.0a versus OAuth 2.0, generating and using a bearer token, runnable Python and Node.js, and a fix for every 401 Unauthorized and 403 error, plus the one-header alternative.

TwitterAPIs·
The 2026 Twitter/X API developer reference: an indexed catalog of endpoints, authentication, rate limits, error codes, and cursor pagination, with the per-call cost of each request
Twitter APIX API

The Twitter API Developer Reference (2026): Endpoints, Rate Limits, Error Codes and Pagination

A single indexed reference for the Twitter/X API in 2026: the endpoint catalog, how authentication and bearer tokens work, the rate limits behind every 429, what error codes 401, 403, and 429 mean, cursor pagination, response shapes, and the real per-call cost of each call.

TwitterAPIs·
How to choose a Twitter/X API in 2026: a buyer's-guide framework weighing pricing model, data coverage, rate limits, authentication, reliability, compliance, and migration cost across the official X API and third-party providers
Twitter APIX API

How to Choose a Twitter/X API in 2026: The Complete Buyer's Guide

A decision framework for choosing a Twitter/X API in 2026: the seven criteria that actually matter (pricing model, data coverage, rate limits, auth, reliability, compliance, migration cost), a use-case decision tree, and where each path wins.

TwitterAPIs·