pywipex
A command-line tool for cleaning up an X (Twitter) account through the official X API, built on the
xdk SDK. It can convert an X archive to JSON Lines, filter tweets with
expressions, and bulk-delete them.
Warning: deletions are real and irreversible.
Prerequisites
- Python 3.10+ and
uv - An X developer app with OAuth 2.0 enabled and a funded (paid) developer account; see Setting up the X app.
Setting up the X app
Warning: the X API is paid. X uses pay-per-use pricing: you buy credits upfront in the Developer Console (with a payment method such as a credit card) and each API request draws them down. Deleting posts through the API is a billable write request, so you must top up the developer account before
pywipex deletewill work. The pricing page lists "Interaction: Delete" at about $0.01 per request, but does not nameDELETE /2/tweets/:idexplicitly, so check the current rates at https://docs.x.com/x-api/getting-started/pricing and estimate the cost for your number of tweets first. Without credits the API returns an error and the run aborts (the--remaining-fileis still written).
- Sign in at https://developer.x.com and sign up for a developer account.
- In the Developer Console, create an app (inside a Project if prompted).
- Add credits under the billing section of the Developer Console.
- Open the app's settings, find User authentication settings and click Set up:
- App permissions: Read and write
- Type of App: Native App (public client, PKCE; no client secret is needed). Choosing "Web App" can cause an
unauthorized_clienterror. - Callback URI / Redirect URL:
http://localhost:8080/callback(orhttp://localhost:PORT/callbackif you use--port) - Website URL: any valid URL (the form requires one)
- Save. The scopes pywipex requests are
tweet.read tweet.write users.read offline.access.
- Open the Keys and tokens tab and copy the OAuth 2.0 Client ID (it appears only after step 4). Ignore the API Key/Secret, Bearer Token and Client Secret; pywipex does not use them.
- Pass the ID with
--client-id, or export it:export PYWIPEX_CLIENT_ID=YOUR_ID. To keep it out of your shell history, store it in a file (outside version control) and read it from there:pywipex delete tweets_to_delete.jsonl --client-id "$(cat path/to/client_id)".
The portal UI changes from time to time, so field names may differ slightly.
Installation
uv sync
uv sync creates .venv from the committed uv.lock and installs the project in editable mode. Then either activate the venv (source .venv/bin/activate) or call .venv/bin/pywipex directly.
Usage
pywipex --help
pywipex convert TWEETS_JS OUTPUT_JSONL
pywipex filter INPUT_JSONL OUTPUT_JSONL -e EXPRESSION
pywipex delete INPUT_FILE [--remaining-file PATH] --client-id ID [--port PORT]
Typical workflow: request your X archive,
convert its data/tweets.js, filter the result, then delete the filtered file.
convert
pywipex convert data/tweets.js tweets.jsonl turns the JavaScript file of the archive into JSON Lines (one {"tweet": {...}} record per line).
filter
Keeps the records for which a Python-style expression is true:
pywipex filter tweets.jsonl filtered.jsonl -e "created_time >= '2022-01-01 00:00:00' and created_time < '2022-02-01 00:00:00'"
pywipex filter tweets.jsonl filtered.jsonl -e "favorite_count + retweet_count * 20 > 100"
pywipex filter tweets.jsonl filtered.jsonl -e "contains(lower(full_text), 'giveaway')"
- Variables:
id(string),created_time(UTC stringYYYY-MM-DD HH:MM:SS),favorite_count,retweet_count(ints),full_text. - Operators: arithmetic, comparisons,
and/or/not(not&&/||),in. - Functions:
len,lower,contains(text, sub),re_search(pattern, text). - Expressions are evaluated with
simpleeval, a sandboxed evaluator, not Python'seval.
delete
Deletes every tweet listed in INPUT_FILE: one tweet per line, either a bare tweet ID or a JSON Lines record as produced by filter
(no header; blank lines ignored).
- A browser window opens for the OAuth 2.0 PKCE authorization. A temporary local server on
localhost:8080(configurable with--port) receives the callback automatically; nothing needs to be pasted. - Tweets are deleted one by one. Connection errors are retried every 5 seconds.
- The X API allows 50 deletions per 15 minutes, so the tool sleeps 15 minutes after every 50 deletions.
Progress is logged to stderr with a date and time on every line.
Options:
--remaining-file PATH– if the run stops early (Ctrl+C, API error, ...), the not-yet-processed IDs (including the one that was in flight) are written to this file in the same format as the input. Re-run with that file asINPUT_FILEto resume. The file is not created when everything was deleted.--client-id ID(envPYWIPEX_CLIENT_ID) – required. OAuth 2.0 client ID of your X app.--port PORT– local port for the callback server (default 8080). Use it if 8080 is busy; the matching callback URL must be registered in the X app.
Example:
pywipex delete tweets_to_delete.csv --client-id YOUR_ID --remaining-file remaining.txt
Development
Source lives in src/pywipex/:
cli.py– theclickgroup; commands are registered withmain.add_command(...).auth.py– OAuth PKCE flow with a temporary Flask callback server.commands/– one module per command (convert,filter,delete).
To add a command, create a module in commands/ exposing a click.command and register it in cli.py.
Metadata
Release files for pywipex 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pywipex-1.0.0.tar.gz | 21.9 kB | Without provenance | Details for pywipex-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pywipex-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for pywipex-1.0.0-py3-none-any.whl |
Total release size: 44.7 kB
Release files / pywipex-1.0.0.tar.gz
| Download URL | pywipex-1.0.0.tar.gz |
|---|---|
| Size | 21.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
99e9c29011f30f996c27b498f5487ea052a0a7c801169d72d9b2ea6d47fbff5b
|
|
BLAKE2b-256 checksum How to use checksums |
895e6f9d656b2dcda781971a25f4540d41e4824ee6896fa9d88329add0ee8fa7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"45","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pywipex-1.0.0-py3-none-any.whl
| Download URL | pywipex-1.0.0-py3-none-any.whl |
|---|---|
| Size | 22.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f096db5637429a20652b7c6e0891d7a4fbead94a993de3713f6ffbf7e007f2f
|
|
BLAKE2b-256 checksum How to use checksums |
d9c747597d2cf2aefd548737c45b4d89d1c8f658d9c64d9abef5f421fd6652a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"45","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for pywipex 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.