No description
Find a file
Philip Nelson 18d3c380c8
Some checks failed
Deploy to Render / deploy (push) Has been cancelled
pass cookies to session
2026-07-23 21:36:24 -04:00
.github/workflows Initial public release: X Automation Service 2026-04-09 23:03:30 +05:30
directives feat: add media upload and alt text support 2026-05-22 22:26:32 -04:00
execution pass cookies to session 2026-07-23 21:36:24 -04:00
.env.example Initial public release: X Automation Service 2026-04-09 23:03:30 +05:30
.gitattributes Add .gitattributes for consistent line endings 2026-04-09 23:09:36 +05:30
.gitignore Initial public release: X Automation Service 2026-04-09 23:03:30 +05:30
GEMINI.md Initial public release: X Automation Service 2026-04-09 23:03:30 +05:30
README.md feat: add media upload and alt text support 2026-05-22 22:26:32 -04:00
requirements.txt feat: add media upload and alt text support 2026-05-22 22:26:32 -04:00
runtime.txt Initial public release: X Automation Service 2026-04-09 23:03:30 +05:30

X Automation Service

Product Siddha Python 3.11+ FastAPI

An open-source, resilient FastAPI service that allows you to post tweets to X (Twitter) without using the expensive official API. Instead, it interacts directly with X's internal GraphQL API using browser-grade TLS fingerprinting (curl_cffi) and dynamic session management.

🏢 Brought to you by Product Siddha
We built this internal tool and decided to open-source it to help the automation community. While we try to give you as much guidance as possible here in the repo, running browser fingerprinting APIs can be technically challenging.
Love the idea but don't want to manage the code? Hire our agency to replicate, host, or customize this workflow for your business.


🛑 The Problem: Why Does This Exist?

In early 2023, X (Twitter) severely restricted its API access, eliminating the standard free tier that developers used for simple automation and bot posting. Today, the "Basic" official API tier costs a staggering $100 per month—which is prohibitively expensive if you just want to post automated tweets, schedule content, or integrate a simple n8n/Make webhook.

We needed a way to automate our agency's tweets without paying $1,200 a year for the privilege.

This repository solves that by mapping a Python backend directly to X's internal Web API (the exact same API your browser uses when you click "Tweet" on x.com). By spoofing a real browser's identity and using your active session cookies, this service completely bypasses the official developer API paywalls.


🌟 Features

  • No Official API Required: Runs entirely on session cookies (auth_token and ct0).
  • Browser Fingerprinting: Uses curl_cffi to mimic real Chrome (Chrome 136+) TLS patterns to bypass JA3/JA4 checks.
  • Dynamic Session Extraction: Auto-scrapes X's JavaScript bundles on startup to find the latest GraphQL queryId and featureSwitches.
  • Resilient Scrape Retry Logic: Failed bundle scrapes back off for 60 seconds before retrying — prevents retry storms on restricted networks (e.g. Render free tier).
  • Advanced Header Management: Dynamically generates x-client-transaction-id and maintains a stable x-client-uuid per session.
  • Media Uploads: Attach images by URL (/tweet) or by uploading local files directly (/tweet-file). Up to 4 images per tweet.
  • Alt Text Support: Set accessibility alt text on each image via mediaAlt (JSON) or alt (multipart) fields.
  • Actionable Error Handling: Cleans up ambiguous X API errors into readable flags (AUTH_EXPIRED, RATE_LIMIT, DUPLICATE_TWEET, AUTOMATION_DETECTED).
  • n8n / Make Friendly: Perfect for triggering from any workflow automation tool via a simple POST request.

🚀 Setup & Installation

1. Prerequisites

  • Python 3.11+
  • Residential Proxy (Datacenter IPs from Render, AWS, GCP, etc. are typically blocked by X)

2. Clone and Install

git clone https://github.com/elnino-hub/x-automation.git
cd x-automation
pip install -r requirements.txt

3. Environment Variables

Copy .env.example to a new .env file:

cp .env.example .env

Fill in the variables:

Variable Purpose
X_AUTH_TOKEN auth_token cookie from a logged-in X browser session
X_CT0 ct0 cookie from a logged-in X browser session
API_KEY Secret key sent in the x-api-key header to authenticate requests securely
PROXY_URL (Required in cloud) Residential proxy URL — format: http://user:pass@host:port

🔑 Important Note on API_KEY:
This is NOT an official X Developer API Key! Since this service bypasses X's API, this variable is simply a custom "password" you create right now to protect your own deployment from unauthorized access. You must send this exact string via the x-api-key header when making POST requests so random bots can't tweet from your server.
To generate a secure key, run python -c "import secrets; print(secrets.token_hex(32))" in your terminal, or simply type a long random string.

How to get your X Cookies:

  1. Log in to x.com in your browser.
  2. Open DevTools (F12) → Application → Cookies → https://x.com.
  3. Copy the values for auth_token and ct0. (Note: Cookies generally last ~12 months before needing rotation).

4. Run the Service

uvicorn execution.main:app --host 0.0.0.0 --port 8000

📡 Endpoints

All mutating endpoints require your API_KEY to be passed in the x-api-key header.

POST /tweet

Post a tweet with optional image attachments by URL.

Body fields:

Field Type Required Description
text string yes Tweet content, max 280 chars
mediaUrls list of strings no Public image URLs to attach (up to 4)
mediaAlt list of strings no Alt text for each image, positionally matched to mediaUrls

Text only:

curl -X POST http://localhost:8000/tweet \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world from the unofficial API!"}'

With images and alt text:

curl -X POST http://localhost:8000/tweet \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Check this out!",
    "mediaUrls": ["https://example.com/photo.jpg"],
    "mediaAlt": ["A description of the photo"]
  }'

Response:

{
  "success": true,
  "tweet_id": "184719247192847120"
}

POST /tweet-file

Post a tweet with local file uploads via multipart/form-data. Useful when calling from scripts that already have image bytes in memory.

Form fields:

Field Type Required Description
text string yes Tweet content, max 280 chars
files file no Image file(s) to attach (repeat for multiple, up to 4)
alt string no Alt text per image, repeat in the same order as files
# Single image with alt text
curl -X POST http://localhost:8000/tweet-file \
  -H "x-api-key: YOUR_API_KEY" \
  -F "text=Hello with a local file!" \
  -F "files=@/path/to/photo.jpg" \
  -F "alt=A description of the photo"

# Multiple images
curl -X POST http://localhost:8000/tweet-file \
  -H "x-api-key: YOUR_API_KEY" \
  -F "text=Two photos!" \
  -F "files=@photo1.jpg" \
  -F "files=@photo2.png" \
  -F "alt=Description of first photo" \
  -F "alt=Description of second photo"

GET /health

Returns the current cache state (queryId source, features, transaction context). No authentication required. Useful for Keep-Alive pings. Does not trigger a bundle scrape — reads from cache only, so pings are instant even when x.com is unreachable.

GET /ip

Returns the current outbound IP of the service. Highly recommended to verify your PROXY_URL is configured correctly.

GET /debug-tweet

Fires a test tweet and returns the absolute raw response from X. Useful if something is breaking and you need to see exactly what X is returning.


☁️ Deployment

This service is container-ready and runs on any Python hosting provider (Render, Railway, Fly.io).

Render Deployment (Recommended):

  1. Create a new "Web Service" pointing to your fork.
  2. Build Command: pip install -r requirements.txt
  3. Start Command: uvicorn execution.main:app --host 0.0.0.0 --port $PORT
  4. Add all required secrets (X_AUTH_TOKEN, X_CT0, API_KEY, PROXY_URL) directly in the Render dashboard.

🤖 n8n Workflow Integration

To use this with n8n:

  • Node: HTTP Request
  • URL: POST https://your-service-url.com/tweet
  • Header: key: x-api-key, value: <YOUR_API_KEY>
  • Body: Send JSON with { "text": "Your tweet here" }
  • Settings: Set a timeout of 60 seconds (to handle cold starts). Set retries to 2 attempts spaced 5000ms apart.

(Pro-Tip: Set up a Cron trigger to hit GET /health every 14 minutes to prevent your cloud container from spinning down).


⚠️ Limitations & Caveats

  • Rate Limits: Keep it under ~50 tweets/day. Pushing this library too hard will result in X locking your account.
  • Browser Impersonation Ages Out: X constantly monitors TLS versions. If you suddenly get AUTOMATION_DETECTED errors, the hardcoded BROWSER = "chrome136" in main.py may need to be incremented to match the latest typical browser version supported by curl_cffi.
  • "Duplicate Tweet" Error During Retry: If a request pauses during transmission and retries, X might accept the first and reject the second as a duplicate. The API handles this gracefully (success: true, tweet_id: null).
  • Bundle Scrape Timeouts on Free Hosting: On Render's free tier, outbound requests to x.com JS bundles may time out. The service automatically falls back to hardcoded FALLBACK_QUERY_ID and FALLBACK_FEATURES — tweets will still post. Failed scrapes back off for 60 seconds before retrying to avoid log spam.

🤝 Need Help?

As mentioned, this toolkit is a little complex underneath the hood! If you're running a business and love this automation but:

  • Don't know how to deploy it
  • Keep getting flagged or proxy-banned
  • Need custom functionality (DMs, thread scheduling, advanced workflows)

Reach out to us. Product Siddha specializes in building robust, un-breakable automation infrastructure for growing businesses.