Writing a Tweet Deletion Script with the X API: Tokens, Scopes, and Refresh
If you want to bulk-delete years of old tweets, the first search result is usually a "deletion script," and the first thing people get stuck on is how to log that script into their X account. API auth looks intimidating, but it really comes down to three questions: where does the token come from, how long does it last, and what can it delete. Once those are clear, the deletion logic is the easy part.
What the API can and cannot delete
Let me set the limits up front so you do not waste effort. X's API has hard gates on deletion. The Free tier basically cannot get delete permission, and calling the delete endpoint returns 403. To remove tweets you usually need Pro or higher, with user-context authorization. The API only deletes tweets under your own account - not other people's, and not quoted snippets someone else wrote.
There is also the old limit people forget: through the API you can only reach tweets within your most recent 3,200. For anything older, download the full archive first, then operate on the tweet IDs inside it. The archive and the API are two tools that work together: the archive pulls out the old data, the API deletes by ID. Not an either/or.
Here is the practical catch: if your account has tens of thousands of tweets and you want to clear 2015 posts, API pagination alone will never reach that far. You need the archive first. A lot of people assume a script will just sweep everything, then get stuck because it literally cannot read data that old.
Three kinds of tokens, do not grab the wrong one
Auth centers on the token. Different flows issue different tokens, and the wrong one always returns 401.
| Token type | How you get it | Can it delete? | Who it fits |
|---|---|---|---|
| App-only Bearer | Generated in the developer console | No | Read-only analytics |
| User Context (OAuth 1.0a) | Exchanged after user authorization | Yes | Deleting your own tweets |
| OAuth 2.0 user context | Authorization-code flow | Yes (with the right scope) | New apps, web clients |
If you just want to delete your own old tweets, the user-context path is the most reliable. The app-only token looks simpler, but it carries the app's identity, not yours, so the delete endpoint rejects it. The difference is easy to spot: an app token has a fixed prefix and lives only in app config, while a user token is bound to a specific account after authorization.
Scopes: how to avoid a 401
OAuth 2.0 asks you to declare a scope at authorization. Deleting tweets needs both tweet.read and tweet.write. Leave either out and you will authorize fine but fail the moment you call delete. A common slip is writing read as readonly, or dropping write entirely. It is an annoying error: the consent screen said yes, yet the code says no permission.
One practical habit: after authorization, print the granted scope and confirm read and write are both there before writing any deletion code. It takes 30 seconds and saves an hour of debugging later. If the response has read but no write, go back through the auth flow again - do not hack around it in code.
Tokens expire, and refresh beats re-authorizing
User tokens are not forever. OAuth 1.0a tokens last a long time; OAuth 2.0 access tokens often expire in a few hours and need a refresh token to renew. Baking the refresh step into the script is far less annoying than re-authorizing by hand every time.
Two caveats. First, store the refresh token safely - it is just as sensitive as the access token. Second, in some setups a refresh invalidates the old access token immediately. In the script, swap to the new token, verify it works, then write it back locally. Do not crash mid-refresh and lose both.
A minimal working example
Here is the shape of it (pseudocode); watch the structure rather than any one line. Real endpoint names follow the official docs.
- Exchange the stored refresh token for a fresh access token;
- Read the target tweet-ID list from your archive or database;
- Call the delete endpoint within the hourly rate limit;
- Log successes, retry failures with backoff;
- Emit a deletion report when done.
Rate limits are where beginners trip. Each endpoint has its own quota, and delete and read are counted separately. The table below shows common tiers; your actual numbers depend on your account level:
| Operation | Rough limit | Over-limit result |
|---|---|---|
| Read one tweet | Hundreds per 15 min | 429, must wait |
| Delete a tweet | Tens per 15 min | 429, must back off |
| Bulk read timeline | Even lower | Likely throttled |
Sleep briefly after each deletion, or adjust speed from the remaining-quota header, instead of firing requests in parallel. Higher concurrency does not mean faster; it tends to trip throttling and slow the whole run down.
Keep the secrets on your own machine
The most important point: tokens, refresh tokens, and API keys live only on your local disk. Do not paste them into a script you plan to share, and never commit them to a public repo. A frequent mistake is pushing a keyed script to GitHub, where bots scrape it within hours and the account gets banned.
If you would rather not wire up the whole auth flow yourself, a local tool works too: upload your X archive, parse it on your machine, mark the tweets you want gone, then decide whether to delete through the API. The keys never leave your computer, which cuts the risk down a lot. Even if the last step uses the API, the parsing and filtering happen locally, so sensitive data is never uploaded.
Common errors and how to handle them
Even with auth sorted, you can still hit a few fixed errors when deleting. A 401 means the token expired or the scope is wrong: swap the token, then re-check the scope. A 403 usually means the tier is too low; the Free tier always fails on delete, so you need Pro or higher. A 429 means you hit the rate limit; back off a few seconds using the remaining-quota header instead of pushing through. Log these three errors and their handling, and you can see at a glance where the run got stuck.
One more easily missed point: the delete endpoint sometimes returns success while the tweet is still there. When that happens, do not immediately retry the same tweet; query its status on its own to confirm whether it was really deleted or just a blip. Adding a verify-after-delete step to the script is steadier than blind retries, and it avoids hammering the same tweet and burning the whole rate budget.
digital-footprint-health.shop is a privacy-check tool that runs on your own machine. It parses your X archive, builds a risk list, and lets you delete risky tweets one by one, with the data never leaving your computer. See the homepage for how it works, or the blog for more data-safety practices.
Frequently Asked Questions
Can a free X account delete tweets through the API?
Almost never. The delete endpoint requires Pro or a higher tier; the Free tier is mostly read-only and returns 403 on delete.
Why can an app token not delete my tweets?
An app token carries the app identity, not yours. Deletion requires a user-context token issued after you authorize.
Why do I still get 401 after a successful authorization?
Usually the scope is missing tweet.write, or read/write is misspelled versus the official names. Print the scope after auth to check.
Do I have to re-authorize by hand when a token expires?
OAuth 2.0 can renew automatically with a refresh token if you put the logic in the script. OAuth 1.0a tokens last much longer.
How should I store secrets used by the script?
Keep them in a local file only, never in version control or shared snippets, and do not print raw tokens in logs.
Check your own X/Twitter footprint
Free on-device scan. Your archive never leaves your computer.
Start Free CheckRelated Reads
Parsing a 200MB X Archive in the Browser: Streaming, Memory and Threads
No Node install, no upload, just the browser: can it parse a 200MB X archive? Yes, provided you work around three hard limits: main-thread blocking, peak memory, and how the file gets read. Here is what causes each one and how to engineer around it.
Designing a Resumable Deletion Job: Checkpoints, Idempotency and Safe Restarts
Any deletion job handling tens of thousands of posts will get interrupted, whether by a dropped connection, a sleeping laptop or a reclaimed process. What separates a job that resumes from one that starts over is checkpointing, idempotency and state recovery. This covers the design and a minimal implementation skeleton.
Why X Rate Limits Slow Down Bulk Deletion: Quotas and Queueing
Bulk deletion is slow for one reason: how write quotas are counted inside a time window, not network speed or tool quality. Once you understand window length, per-endpoint quotas and what a 429 actually means, deletion becomes a controllable queue instead of one long sprint that restarts from zero.