- Python 100%
|
|
||
|---|---|---|
| .github/workflows | ||
| directives | ||
| execution | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| GEMINI.md | ||
| README.md | ||
| requirements.txt | ||
| runtime.txt | ||
X Automation Service
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_tokenandct0). - Browser Fingerprinting: Uses
curl_cffito 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
queryIdandfeatureSwitches. - 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-idand maintains a stablex-client-uuidper 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) oralt(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 thex-api-keyheader when making POST requests so random bots can't tweet from your server.
To generate a secure key, runpython -c "import secrets; print(secrets.token_hex(32))"in your terminal, or simply type a long random string.
How to get your X Cookies:
- Log in to x.com in your browser.
- Open DevTools (F12) → Application → Cookies →
https://x.com. - Copy the values for
auth_tokenandct0. (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):
- Create a new "Web Service" pointing to your fork.
- Build Command:
pip install -r requirements.txt - Start Command:
uvicorn execution.main:app --host 0.0.0.0 --port $PORT - 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 to2 attemptsspaced5000msapart.
(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_DETECTEDerrors, the hardcodedBROWSER = "chrome136"inmain.pymay need to be incremented to match the latest typical browser version supported bycurl_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.comJS bundles may time out. The service automatically falls back to hardcodedFALLBACK_QUERY_IDandFALLBACK_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.