Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Post tweets, build threads, upload media via the X API.
.claude/skills/notque-x-api/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 105% | 0% |
This skill orchestrates OAuth-authenticated, rate-limit-aware X/Twitter API interactions through a deterministic Python script (scripts/x-api-poster.py). The workflow implements a 4-phase pipeline with an explicit confirmation gate (Phase 2) to prevent accidental public posts.
Core principles:
--confirmed)Goal: Confirm credentials, content, and dependencies before any network call.
Step 1: Check credentials
Test credential presence by running a dry-run credential check:
bashpython3 $HOME/.claude/scripts/x-api-poster.py post --dry-run --text "ping"
This confirms all required environment variables are set: X_API_KEY, X_API_SECRET, X_ACCESS_TOKEN, X_ACCESS_SECRET, X_BEARER_TOKEN.
X_BEARER_TOKEN is requiredStep 2: Validate content length
The script enforces a 280-character limit per tweet. Before posting, validate your content length:
For a single tweet:
bashpython3 $HOME/.claude/scripts/x-api-poster.py post --dry-run --text "your tweet text here"
For a thread:
bashpython3 $HOME/.claude/scripts/x-api-poster.py thread --dry-run --texts "part 1" "part 2" "part 3"
If --dry-run reports a length error, ask the user to shorten the text or approve auto-segmentation into a thread.
Gate: Dry run exits 0, content length validates, credentials confirmed present. Proceed only when gate passes.
Goal: Show the user exactly what will be posted and require explicit approval before writing.
This gate is mandatory because X posts are public and irreversible. Present a content preview in this format:
CONTENT PREVIEW
================
Tweet 1/1:
"Your tweet text here"
Characters: 42/280
Action: POST single tweet
Approve? [yes/no]For a thread:
CONTENT PREVIEW
================
Tweet 1/3:
"First part text"
Tweet 2/3:
"Second part text"
Tweet 3/3:
"Third part text"
Action: POST thread (3 tweets, chained replies)
Approve? [yes/no]Wait for explicit user approval. The words "yes", "approve", "go ahead", "post it", or equivalent typed in the current conversation turn constitute approval. Do not infer approval from context or prior conversation turns. Do not pass --confirmed before the user provides explicit typed approval.
Gate: User has typed an explicit approval in this conversation turn. Proceed only when gate passes.
Goal: Execute the write operation and capture tweet IDs.
Only proceed once Phase 2 approval is confirmed. Pass the --confirmed flag when the user approves in this turn.
Single tweet:
bashpython3 $HOME/.claude/scripts/x-api-poster.py post \ --confirmed \ --text "your tweet text here"
Thread:
bashpython3 $HOME/.claude/scripts/x-api-poster.py thread \ --confirmed \ --texts "part 1" "part 2" "part 3"
Tweet with media:
bashpython3 $HOME/.claude/scripts/x-api-poster.py post \ --confirmed \ --text "your tweet text here" \ --media /absolute/path/to/image.jpg
Media constraints: Images must be <= 5 MB (JPG, PNG, GIF); videos must be <= 512 MB (MP4). Media upload is a two-step process; if either step fails, no orphaned media is left behind. Confirm the file exists and is in a supported format before posting.
Watch output for:
[tweet-posted] id=... url=... — success line per tweet; contains canonical URL (https://x.com/i/web/status/{id})[rate-limit-warning] remaining=N reset=EPOCH — surface to user immediately if presentERROR: line — surface verbatim and stopOAuth mode is automatic: Read operations use Bearer token only; write operations require full OAuth 1.0a. The script selects the mode based on operation type — do not override it.
Gate: Script exits 0, at least one [tweet-posted] line in output. Proceed only when gate passes.
Goal: Return tweet URLs, IDs, and engagement baseline to the user.
Step 1: Collect tweet IDs from Phase 3 output
Parse all [tweet-posted] id=... url=... lines from the script output.
Step 2: Read engagement baseline (optional)
For each posted tweet, you may optionally read initial engagement metrics via:
bashpython3 $HOME/.claude/scripts/x-api-poster.py read-timeline --user-id me --max-results 5
Engagement metrics have propagation delay: X API metrics take time to populate. Reading public_metrics immediately after posting with 0 impressions is expected behavior, not failure. Report metrics as baseline at post time and note they will grow asynchronously.
Step 3: Report to user
Provide:
Cause: One or more credential env vars not set in the shell Solution:
Cause: A single tweet segment is too long Solution:
Cause: Script invoked without confirmation (should not happen if Phase 2 gate was followed) Solution: Return to Phase 2, present the confirm gate, and obtain explicit user approval
Cause: Credentials are invalid, expired, or lack the required permissions Solution:
Cause: API rate limit window exhausted Solution:
Cause: Media upload is a two-step process; failure at either step leaves no orphaned media Solution:
$HOME/.claude/scripts/x-api-poster.py: Backing script (exit codes: 0=success, 1=missing credentials, 2=content validation failed, 3=API error, 4=write attempted without --confirmed)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | 7,971 | 4,291 | -46% | 1 | 1 | 0% | 1,327 | 2,520 | +90% | 0 | 0 | — |
case-01 | fail→fail | 8,476 | 9,583 | +13% | 1 | 1 | 0% | 1,411 | 2,538 | +80% | 0 | 0 | — |
case-03 | fail→fail | 7,090 | 5,778 | -19% | 1 | 1 | 0% | 1,156 | 2,341 | +103% | 0 | 0 | — |
case-04 | fail→pass | 11,354 | 4,231 | -63% | 1 | 1 | 0% | 1,768 | 2,814 | +59% | 0 | 0 | — |
case-05 | fail→fail | 6,587 | 2,335 | -65% | 1 | 1 | 0% | 1,104 | 2,364 | +114% | 0 | 0 | — |
case-06 | pass→pass | 9,232 | 3,167 | -66% | 1 | 1 | 0% | 1,476 | 2,565 | +74% | 0 | 0 | — |
case-07 | fail→pass | 10,799 | 6,024 | -44% | 1 | 1 | 0% | 1,701 | 3,149 | +85% | 0 | 0 | — |
case-08 | fail→pass | 8,517 | 3,238 | -62% | 1 | 1 | 0% | 1,326 | 2,612 | +97% | 0 | 0 | — |
case-09 | pass→pass | 6,740 | 3,175 | -53% | 1 | 1 | 0% | 1,053 | 2,604 | +147% | 0 | 0 | — |
case-10 | pass→pass | 11,930 | 5,499 | -54% | 1 | 1 | 0% | 1,925 | 2,909 | +51% | 0 | 0 | — |
case-11 | fail→pass | 12,083 | 2,604 | -78% | 1 | 1 | 0% | 2,214 | 2,495 | +13% | 0 | 0 | — |
case-12 | fail→pass | 9,387 | 4,127 | -56% | 1 | 1 | 0% | 1,324 | 2,720 | +105% | 0 | 0 | — |
case-13 | fail→pass | 12,973 | 2,312 | -82% | 1 | 1 | 0% | 2,162 | 2,443 | +13% | 0 | 0 | — |
case-14 | fail→pass | 12,001 | 7,187 | -40% | 1 | 1 | 0% | 1,960 | 3,235 | +65% | 0 | 0 | — |
case-15 | pass→pass | 14,340 | 6,619 | -54% | 1 | 1 | 0% | 2,666 | 3,210 | +20% | 0 | 0 | — |
case-16 | pass→pass | 8,528 | 2,764 | -68% | 1 | 1 | 0% | 1,272 | 2,498 | +96% | 0 | 0 | — |
case-17 | fail→pass | 8,259 | 2,311 | -72% | 1 | 1 | 0% | 1,200 | 2,439 | +103% | 0 | 0 | — |
case-18 | fail→pass | 11,088 | 2,675 | -76% | 1 | 1 | 0% | 1,792 | 2,395 | +34% | 0 | 0 | — |
case-19 | fail→pass | 11,370 | 2,483 | -78% | 1 | 1 | 0% | 1,778 | 2,401 | +35% | 0 | 0 | — |
case-20 | fail→pass | 12,805 | 3,059 | -76% | 1 | 1 | 0% | 1,846 | 2,524 | +37% | 0 | 0 | — |
case-21 | pass→pass | 14,839 | 9,889 | -33% | 1 | 1 | 0% | 2,437 | 3,682 | +51% | 0 | 0 | — |
case-22 | pass→pass | 11,222 | 7,966 | -29% | 1 | 1 | 0% | 1,984 | 3,418 | +72% | 0 | 0 | — |
case-23 | pass→pass | 10,242 | 7,374 | -28% | 1 | 1 | 0% | 1,607 | 3,177 | +98% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +48 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.