Skip to main content

auto-x

Open-source X (Twitter) publisher that drives x.com with Patchright and your own imported cookies. Prepare content in a folder, configure your account once, publish.

Status: Alpha. Personal-account use only. Browser-driven automation of X is restricted by X's ToS — use a scratch account, conservative pacing, and a residential IP that matches where you got the cookies.

Why this project

X's official developer API gates writes behind a paid tier and an app-review process. For one operator who already has a real logged-in browser tab, the most durable workflow is to drive that same browser. auto-x does exactly that.

Supported post types in v1:

  • tweet — single post, up to 4 images or 1 video, with optional per-image alt text
  • thread — a chain of 2+ tweets, each segment can carry its own media

X Premium accounts unlock 25,000-character posts; toggle premium: true in the account YAML to lift the 280-char cap.

Not supported in v1: replies, quote-tweets, polls, Spaces, long-form Articles. They will follow once the v1 surface is stable.

Requirements

  • Python 3.11+
  • macOS or Linux (tested on macOS)
  • A real Chrome/Chromium you can log in from on the same network you'll run the bot on
  • (Recommended) a residential proxy if you plan to run this on a different machine from where you logged in

Install

One command with pipx (recommended):

pipx install auto-tt
auto-tt init --account demo

Or with uv:

uv tool install auto-tt
auto-tt init --account demo

auto-tt init installs the patched Chrome channel Patchright needs and scaffolds a working directory in .:

./config/demo.yaml            # account config from the shipped template
./content/example-post/       # sample post descriptor
./sessions/                   # session files (gitignored)
./.gitignore                  # appended with auto-x entries

It is safe to re-run — existing files are preserved.

From source (contributors)

git clone https://github.com/xtea/auto-x
cd auto-x
uv sync
uv run auto-tt init --account demo

Configure an account

Edit config/<account>.yaml. The important fields:

  • handle — your X username without @ (display only)
  • premium — true if your account has X Premium (raises the per-tweet cap to 25,000 chars)
  • user_agent, viewport, locale, timezone — match the browser you'll log in from. Drift between these and the cookie's origin is the #1 cause of re-challenges on x.com.
  • pacing.max_posts_per_day — start at 25 or lower. X's rolling-window soft caps fire well before the documented daily limit.

Authenticate

Two paths; pick whichever you prefer.

Option A — Headed manual login (simplest)

auto-tt login --account demo

A Chrome window opens at https://x.com/i/flow/login regardless of your headless setting. Log in by hand (handle Arkose / 2FA yourself). When the home timeline appears, the session is saved to sessions/demo.json.

Option B — Import cookies from your real browser

X's most important cookies (auth_token, ct0, kdt) are HttpOnly — they will not appear in document.cookie. Use a tool that reads the browser's cookie store directly:

  1. Install Cookie-Editor (browser extension).

  2. Open x.com in your real browser, click the extension, Export → Export as JSON, save to x-cookies.json.

  3. Run:

    auto-tt import-cookies ./x-cookies.json --account demo
    

The tool rejects the import if auth_token or ct0 is missing. Cookies on the legacy .twitter.com domain are auto-mirrored to .x.com, so a session migrated mid-rebrand still works.

Verify

auto-tt doctor --account demo

Should print OK: <handle> session is valid.

Publish content

Layout

content/
└── my-post/
    ├── post.yaml
    └── media/
        ├── 1.jpg
        └── 2.jpg

post.yaml schema (single tweet)

type: tweet
caption: |
  Your caption. 280 chars on free, 25,000 on Premium.
  #opensource
media:
  - ./media/1.jpg          # 1–4 images, OR exactly one video
alt_texts:                 # optional, aligned by index, ≤1000 chars each
  - "Description of 1.jpg"
schedule: 2026-04-25T14:00:00Z   # optional, UTC or with offset

post.yaml schema (thread)

type: thread
segments:
  - caption: "First tweet in the chain."
    media: [./media/1.jpg]
  - caption: "Second tweet — every segment can have its own media."
    media: [./media/2.mp4]

Validation runs before any browser work:

Surface Rule
Caption (free) ≤ 280 chars
Caption (Premium) ≤ 25,000 chars
Images per segment 1–4, .jpg/.jpeg/.png/.webp, ≤5 MB (15 MB for .gif)
Video per segment 1 only, .mp4/.mov, ≤512 MB and ≤140 s on free tier
Mixing media images xor video — never both
Alt text ≤ 1000 chars per image
Thread length 2–25 segments

One-shot publish

auto-tt publish content/my-post --account demo --dry-run   # safe first run
auto-tt publish content/my-post --account demo             # actually posts

--dry-run walks the full upload flow and stops before clicking Post — useful when patching selectors.

Scheduled / queued publish

Drop posts with schedule: set in the future, then run auto-tt queue from cron / launchd / systemd-timer:

*/5 * * * * cd /path/to/auto-x && auto-tt queue --account demo >> sessions/queue.log 2>&1

The queue stores state in sessions/queue.db (SQLite) with statuses: queued | running | succeeded | failed | paused. Use auto-tt list to inspect.

How the Playwright flow works

  1. Launch Chrome via Patchright (Chromium with CDP/webdriver leaks patched at the binary level). Vanilla playwright is detectable by X's fingerprinting stack — don't use it.
  2. Load sessions/<account>.json as the Playwright storage_state.
  3. Navigate to https://x.com/home, confirm the Post side-nav button is visible (signal of authenticated state).
  4. Click Post → setInputFiles into the hidden <input data-testid="fileInput">.
  5. Type the caption into [data-testid="tweetTextarea_0"] (contenteditable, must use keyboard.type not fill).
  6. For threads, click the addButton and repeat for tweetTextarea_1..N.
  7. Click tweetButtonInline. Watch for the toast [data-testid="toast"] and read its View link to capture the new tweet's numeric ID.

All selectors are in src/auto_x/publisher/selectors.py — when X changes the UI, that is the file to patch.

Pacing & safety

Built-in guardrails, tunable in config/<account>.yaml:

  • max_posts_per_day daily cap (enforced by the queue)
  • min/max_step_delay_seconds randomized delays between UI actions (30–180 s recommended)
  • pre_run_idle_seconds_* scroll/dwell on the home timeline before the first click

On any redirect to /i/flow/login, /account/access, /i/flow/consent_flow, /i/flow/login/check, or /account/suspended, the runner pauses the job and records the reason. Re-authenticate with auto-tt login and retry — there is no auto-retry.

headless: true is the shipped default. The auto-tt login command always forces a headed window regardless, so cookie capture and 2FA still work.

Known limitations

  • v1 surface only. Reply, quote, poll, Spaces, and long-form Article composition are not supported yet.
  • Selectors rot. X ships UI changes frequently. Expect periodic patches to selectors.py.
  • Arkose / Cloudflare-style challenges. If X drops a FunCaptcha mid-run, the tool pauses; manual re-login is required.
  • Shared IP. Using cookies captured from residence A while running the bot on residence B's IP is the single most reliable way to get challenged.
  • ToS risk. Browser-driven automation of personal X accounts is against X's terms. Ban risk is real. Use a scratch account.

Non-goals (for now)

  • Web UI / dashboard (CLI + YAML only)
  • Long-running daemon (cron-friendly invocation instead)
  • Engagement automation (likes, follows, DMs, retweets-without-quote)
  • Account registration

License

MIT.

Metadata

Release files for auto-tt 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for auto-tt 0.1.0
File Size Uploaded
auto_tt-0.1.0.tar.gz 31.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for auto-tt 0.1.0
File Interpreter ABI Platform
auto_tt-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 67.8 kB

Release files / auto_tt-0.1.0.tar.gz

Download URL auto_tt-0.1.0.tar.gz
Size 31.6 kB
Tags Source
SHA-256 checksum
How to use checksums
92f6b7d6decd92b6ac8d6b0655dd638232523abd2cf7dff800686ea79be75af0
BLAKE2b-256 checksum
How to use checksums
e10231080f363b96221449bb7b395177806bbc85feb2f0f6b724c4ad2e6c4819
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / auto_tt-0.1.0-py3-none-any.whl

Download URL auto_tt-0.1.0-py3-none-any.whl
Size 36.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e337e0f18d8e49c03413c9748bbfa16f267c6eae5f6c4f4866763d57a788a383
BLAKE2b-256 checksum
How to use checksums
9adc2c7882f814654a8ec8b35bf1e82bfd27f622b7b765f0c8153b85b586a6bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page