Mass Delete Tweets — Bulk API Workflow + Free Tool Options
Mass deleting your own tweets — clearing an old account before re-use, removing pre-rebrand content, post-incident cleanup, or a full 'delete all my tweets' account reset — is one of the most common dev-adjacent workflows on X. The honest answer in 2026: there is no truly free programmatic mass-delete option that scales. X's API is consumption-based at $0.010 per delete; the third-party 'free tweet deleters' (Redact, TweetDeleter, TweetEraser, Circleboom) work only in small batches (typically ~3K/month) before paid tiers kick in.
This guide walks the API workflow with runnable Python: fetch tweet IDs (via advanced_search or user timeline), loop deletes with rate-limit pacing, dry-run + audit log per attempt. The free / cheap tier reality is at the bottom — what 'free' actually means, and when a mass delete crosses from 'small one-off' into 'API-only territory'.
The straight answer — no truly free path at scale
Three options for bulk delete in 2026, ranked by cost + reliability:
1. X official API — DELETE /2/tweets/{id} at $0.010 per delete per docs.x.com pricing. Programmatic, reliable, requires OAuth user-context auth. Best for any meaningful volume.
2. Third-party 'free' deleters (TweetDeleter, TweetEraser, similar) — usually free for small batches (a few hundred tweets) then paid. Web-UI based, may use X auth on your behalf. Useful for one-off small cleanups; doesn't scale.
3. Roll-your-own via OAuth + DELETE — same X official endpoint, but you write the code. No different cost — you're paying X per delete regardless of who's calling the endpoint.
The myth of 'free bulk delete' usually points at option 2 with a hidden batch cap. The honest, scalable answer is option 1 with budget.
Step 1 — list the tweet IDs to delete
Most bulk-delete workflows have a target set: 'all tweets before 2024', 'tweets containing certain keywords', 'every tweet'. Fetch the IDs via the user timeline + filter client-side.
Via X official: /2/users/{id}/tweets with paginated cursor. Via twitterapi.io: /twitter/user/last_tweets or /twitter/tweet/advanced_search?query=from:.
import os, requests
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def list_my_tweet_ids(handle: str, before_date: str = None) -> list[str]:
"""Get tweet IDs to delete via advanced_search (cheap read)."""
ids = []
cursor = None
while True:
query = f"from:{handle}"
if before_date:
query += f" until:{before_date}"
params = {"query": query}
if cursor:
params["cursor"] = cursor
r = requests.get(
f"{BASE}/twitter/tweet/advanced_search",
headers=HEADERS, params=params, timeout=15,
)
r.raise_for_status()
resp = r.json()
for t in resp.get("tweets", []):
ids.append(t["id"])
cursor = resp.get("next_cursor")
if not cursor: break
return ids
old_tweets = list_my_tweet_ids("your_handle", before_date="2024-01-01")
print(f"found {len(old_tweets)} tweets to delete")
# Cost: ~$0.00015 × len(old_tweets) — read is cheap; delete is the bill
Step 2 — execute the deletes with rate-limit safety
Once you have the ID list, loop the DELETE calls. X publishes rate limits; pacing 1-2 seconds between deletes is the operational baseline.
Per docs.x.com/x-api/getting-started/pricing: $0.010 per delete. 1,000-tweet cleanup = $10. 10,000-tweet cleanup = $100.
# pip install tweepy
import tweepy, time, json, random
from datetime import datetime, timezone
client = tweepy.Client(
consumer_key="YOUR_KEY",
consumer_secret="YOUR_SECRET",
access_token="USER_TOKEN",
access_token_secret="USER_TOKEN_SECRET",
)
def bulk_delete(tweet_ids: list[str], dry_run: bool = True):
"""Delete tweets with audit log + rate-limit safety."""
log_path = f"delete_log_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.jsonl"
success, fail = 0, 0
with open(log_path, "a") as log:
for tid in tweet_ids:
entry = {"tweet_id": tid, "at": datetime.now(timezone.utc).isoformat()}
if dry_run:
entry["action"] = "dry_run"
log.write(json.dumps(entry) + "\n")
continue
try:
client.delete_tweet(id=tid)
entry["action"] = "deleted"
success += 1
except tweepy.TooManyRequests:
entry["action"] = "rate_limited"
log.write(json.dumps(entry) + "\n")
time.sleep(60 + random.uniform(0, 5))
continue
except Exception as e:
entry["action"] = f"failed: {e}"
fail += 1
log.write(json.dumps(entry) + "\n")
time.sleep(1.0 + random.uniform(0.1, 0.5)) # safe pacing
return {"success": success, "fail": fail, "log": log_path}
# Always run dry_run=True first to verify the target set
# result = bulk_delete(old_tweets, dry_run=False)
Free / cheap alternatives — what 'free' actually means
Third-party tools advertised as 'free tweet deleter':
TweetDeleter, TweetEraser, Circleboom, Redact (formerly TweetDelete) — web UIs that bulk-delete via the user's X auth. Free tier typically caps at a small monthly batch (e.g. 3,000 tweets/mo); past that, paid plans apply.
Browser extensions — DIY scripts that programmatically scroll your timeline and click delete in the UI. Slow + fragile (UI changes break them). Some violate X's terms of service depending on implementation.
'Permanent free' deleters — usually have hidden batch caps or sustainable-volume limits. Try one for a small test before relying on it for a 10K cleanup.
The honest tradeoff: free options work for small one-time cleanups (< 1,000 tweets); paid / API options are the path for any meaningful scale or recurring workflow.
Side-by-side comparison — 3 paths to bulk delete
Two practical patterns: (a) the API path is the only programmatic + reliable path at scale; (b) free options are good for one-off small jobs, not for recurring workflows.
Cost framing + scaling
Math from docs.x.com pricing at $0.010 per delete:
100 tweet cleanup = $1.00
1,000 tweet cleanup = $10.00
10,000 tweet cleanup = $100.00
Plus the cost of listing target IDs — at twitterapi.io's $0.00015/tweet read, that's negligible (1,000 IDs to read = $0.15).
Rate-limit pacing means a 10K-tweet cleanup takes ~6 hours of wall-clock at 1.5 sec/delete. Plan accordingly.
Archive before you delete — the safe mass-delete workflow
Deletes are permanent. There is no undo API, no soft-delete recovery, no 30-day trash bin. Before a mass delete of any meaningful volume — especially a full 'delete all tweets' account reset — export the target set first. Storage is cheap; regret is not.
The 3-stage safe pattern — export → verify → delete:
Stage 1 · Export to local JSONL. Loop /twitter/tweet/advanced_search?query=from: with the next_cursor and write each tweet object to a file. At twitterapi.io's $0.00015/tweet read pricing, exporting 10,000 tweets costs $1.50 — one-off, worth it. See /blog/twitter-history-api-export-guide for the full export walk.
Stage 2 · Verify the export locally. Open the JSONL, scan for anything you don't want to lose (media URLs, quote-tweet contexts, reply threads). Media URLs in the export point at pbs.twimg.com CDN — download the ones you care about now, because a deleted tweet's media eventually 404s.
Stage 3 · Delete via the loop from Step 2 above. Same bulk_delete() code — the archive is your safety net if you regret anything.
For account-reset scale (10K+ tweets, 'delete all my tweets'): export first, keep the JSONL for at least 90 days, THEN delete. This is the same pattern the reputable web tools use internally (Redact and Circleboom both offer 'export before delete'); the API path just gives you full control over the archive format + storage location.
# Stage 1 — export to JSONL BEFORE running bulk_delete()
import os, json, requests
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def export_all_tweets(handle: str, out_path: str = "tweets_archive.jsonl") -> int:
"""Full-account export before mass-delete. Cost ~$0.00015 per tweet."""
cursor = None
count = 0
with open(out_path, "w") as f:
while True:
params = {"query": f"from:{handle}"}
if cursor:
params["cursor"] = cursor
r = requests.get(
f"{BASE}/twitter/tweet/advanced_search",
headers=HEADERS, params=params, timeout=15,
)
r.raise_for_status()
resp = r.json()
for t in resp.get("tweets", []):
f.write(json.dumps(t) + "\n")
count += 1
cursor = resp.get("next_cursor")
if not cursor:
break
return count
count = export_all_tweets("your_handle")
print(f"exported {count} tweets to tweets_archive.jsonl")
print(f"estimated cost: ${count * 0.00015:.4f} (read-only)")
# NOW verify tweets_archive.jsonl locally before running bulk_delete() from Step 2
# Practical example: end-to-end old-tweet cleanup with dry-run + audit log.
import os, time, random, json
import tweepy, requests
from datetime import datetime, timezone
TAPI_HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
client = tweepy.Client(
consumer_key=os.environ["X_CONSUMER_KEY"],
consumer_secret=os.environ["X_CONSUMER_SECRET"],
access_token=os.environ["X_USER_TOKEN"],
access_token_secret=os.environ["X_USER_SECRET"],
)
HANDLE = os.environ["X_HANDLE"]
DRY_RUN = True # always start safe
def list_targets(before_date: str):
ids = []
cursor = None
while True:
params = {"query": f"from:{HANDLE} until:{before_date}"}
if cursor: params["cursor"] = cursor
r = requests.get(
"https://api.twitterapi.io/twitter/tweet/advanced_search",
headers=TAPI_HEADERS, params=params, timeout=15,
)
r.raise_for_status()
resp = r.json()
for t in resp.get("tweets", []):
ids.append(t["id"])
cursor = resp.get("next_cursor")
if not cursor: break
return ids
def bulk_delete(ids):
log_path = f"delete_log_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.jsonl"
success, fail = 0, 0
with open(log_path, "a") as log:
for tid in ids:
entry = {"tweet_id": tid, "at": datetime.now(timezone.utc).isoformat()}
if DRY_RUN:
entry["action"] = "dry_run"
else:
try:
client.delete_tweet(id=tid)
entry["action"] = "deleted"
success += 1
except tweepy.TooManyRequests:
entry["action"] = "rate_limited"
log.write(json.dumps(entry) + "\n")
time.sleep(60 + random.uniform(0, 5))
continue
except Exception as e:
entry["action"] = f"failed: {e}"
fail += 1
log.write(json.dumps(entry) + "\n")
time.sleep(1.0 + random.uniform(0.1, 0.5))
return success, fail, log_path
targets = list_targets("2024-01-01")
print(f"found {len(targets)} tweets to delete")
print(f"Estimated cost (X official): ${len(targets) * 0.010:.2f}")
print(f"Time at 1.5s pace: ~{len(targets) * 1.5 / 60:.0f} minutes")
if not DRY_RUN:
s, f, p = bulk_delete(targets)
print(f"Done: {s} deleted, {f} failed. Log: {p}")
Questions readers ask
How do I delete all my tweets programmatically?
The 'delete all tweets' workflow is a special case of mass-delete without a date filter: (1) fetch every tweet ID via /twitter/tweet/advanced_search?query=from: looping the next_cursor until exhaustion; (2) run the bulk_delete() loop from the code sample above with DRY_RUN=False; (3) monitor the audit log. At 10K tweets that's ~$100 (X API pricing) + ~4 hours wall-clock at 1.5s pacing. Export first (see FAQ 'What about archiving before delete?') — the delete is permanent.
Is there really no free mass-delete API in 2026?
Not at scale. X's API is consumption-based with no free tier in 2026. Third-party 'free' tools (Redact / TweetDeleter / TweetEraser / Circleboom) work for small one-off cleanups (typically ~3,000 tweets/month cap on free tier) but don't scale to recurring or large workflows. The honest tradeoff: pay for the API or accept the batch caps.
Can I undo a bulk delete?
No — tweet deletes are permanent. There is no undelete API. Run dry_run=True first to verify your target set, and consider exporting the tweets you're about to delete first (see /blog/twitter-history-api-export-guide for the export workflow).
Will X suspend my account for bulk deleting?
Bulk-deleting your own tweets via the API is a standard supported workflow — not against X's terms. The risk is rate-limit violations if you go too fast. Pace 1-2 seconds between deletes; don't burst.
Can I delete other users' tweets?
No — you can only delete your own tweets. OAuth user-context auth scopes the call to the authenticated account. Reporting other users' content is a separate moderation workflow, not the delete API.
What about archiving before delete?
Strongly recommended for any meaningful cleanup. Export the tweets first via twitterapi.io advanced_search to JSONL (single-digit-dollar cost), then run the deletion. Archive lives outside X regardless of what happens to your account.
How long does a 10K-tweet bulk delete take?
At 1.5 second pacing per delete: 10,000 × 1.5s = 15,000 seconds = ~4.2 hours of wall-clock time. Plus any rate-limit pauses. Run overnight, monitor for completion.
Continue
- X API — pricing (docs.x.com, 2026 verified)
- Tweepy documentation
- twitterapi.io — pricing (read-only API)
- Twitter (X) API — cluster hub
- Mass unfollow Twitter (X) API tutorial
- Twitter (X) history API — export timeline
- Rate Limit Exceeded on Twitter (X) — Fixes
- twitterapi.io pricing
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key