diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0c2ebd2..1d80ad1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -115,8 +115,8 @@ }, { "name": "fiale-awesome-skills", - "description": "Curated Claude Code skills for developer security and productivity — supply chain hardening across npm, Python, Go, Rust, and Homebrew (macOS)", - "version": "0.1.0", + "description": "Curated Claude Code skills — supply chain hardening (macOS) and cost-optimized X/Twitter workflows via xurl CLI with local sandbox testing", + "version": "0.2.0", "author": { "name": "Pavel Fadeev", "email": "pavel.fadeev@gmail.com", @@ -131,7 +131,11 @@ "hardening", "npm", "pip", - "macos" + "macos", + "xurl", + "twitter", + "x-api", + "social-media" ] }, { diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index e2fe3d8..3e4278d 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -3,6 +3,10 @@ on: pull_request_target: types: [opened] +permissions: + issues: write + pull-requests: write + jobs: check-membership: runs-on: ubuntu-latest @@ -13,13 +17,32 @@ jobs: script: | const { owner, repo } = context.repo; const author = context.payload.pull_request.user.login; + + // Check if PR author is the repo owner (covers user-owned repos) + if (author === owner) { + console.log(`${author} is the repo owner — PR allowed`); + return; + } + + // Check org membership (for org-owned repos) try { await github.rest.orgs.checkMembershipForUser({ org: owner, username: author, }); console.log(`${author} is an org member — PR allowed`); - } catch { + } catch (e) { + // Check if it's an org API error (user account, not an org) vs actual non-member + if (e.status === 404) { + // Not an org — check if author is a collaborator instead + try { + await github.rest.repos.checkCollaborator({ owner, repo, username: author }); + console.log(`${author} is a collaborator — PR allowed`); + return; + } catch {} + } + + console.log(`${author} is not a member — closing PR`); await github.rest.issues.createComment({ owner, repo, issue_number: context.payload.pull_request.number, @@ -30,5 +53,4 @@ jobs: pull_number: context.payload.pull_request.number, state: 'closed', }); - console.log(`${author} is not an org member — PR closed`); } diff --git a/README.md b/README.md index 6be39bd..2cd392b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Plugins for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). | [mdbrowser](#mdbrowser) | Browse any URL as clean markdown | | [Vibes](#vibes) | Sentiment-driven musical phrases on task completion | | [Remote Layout](#remote-layout) | Mobile-friendly response formatting for remote sessions | +| [Awesome Skills](#awesome-skills) | Supply chain hardening, cost-optimized X/Twitter via xurl | --- @@ -177,6 +178,28 @@ Switch Claude's response format for small screens. Persists across sessions via --- +## Awesome Skills + +**Curated skills that turn Claude into a specialist.** + +Developer security and productivity skills — supply chain hardening across npm/Python/Go/Rust/Homebrew, and cost-optimized X/Twitter workflows via [xurl](https://github.com/xdevplatform/xurl) with local sandbox testing through [playground](https://github.com/xdevplatform/playground). + +| Skill | What it does | +|-------|-------------| +| `/supply-chain-hardening` | Audit and harden your macOS system against supply chain attacks | +| `/xurl` | Post, search, engage on X/Twitter with spending guardrails and free sandbox mode | + +**Install:** +```bash +/plugin marketplace add fiale-plus/fiale-claude-plugins +/plugin install fiale-awesome-skills +/xurl +``` + +→ [Full documentation](plugins/fiale-awesome-skills) + +--- + ## License MIT diff --git a/plugins/fiale-awesome-skills/.claude-plugin/plugin.json b/plugins/fiale-awesome-skills/.claude-plugin/plugin.json index 833ba7a..e1dd9b6 100644 --- a/plugins/fiale-awesome-skills/.claude-plugin/plugin.json +++ b/plugins/fiale-awesome-skills/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "fiale-awesome-skills", - "version": "0.1.0", - "description": "Curated Claude Code skills for developer security and productivity — supply chain hardening across npm, Python, Go, Rust, and Homebrew (macOS)", + "version": "0.2.0", + "description": "Curated Claude Code skills — supply chain hardening (macOS) and cost-optimized X/Twitter workflows via xurl CLI with local sandbox testing", "author": { "name": "Pavel Fadeev", "email": "pavel.fadeev@gmail.com", "url": "https://fiale.plus" }, "license": "MIT", - "keywords": ["claude-code", "security", "supply-chain", "hardening", "npm", "pip", "macos"] + "keywords": ["claude-code", "security", "supply-chain", "hardening", "npm", "pip", "macos", "xurl", "twitter", "x-api", "social-media"] } diff --git a/plugins/fiale-awesome-skills/README.md b/plugins/fiale-awesome-skills/README.md index cee026c..93bb6c2 100644 --- a/plugins/fiale-awesome-skills/README.md +++ b/plugins/fiale-awesome-skills/README.md @@ -4,13 +4,12 @@ Curated Claude Code skills for developer security and productivity. -**Platform: macOS only** (for now). - ## Skills | Skill | Command | What it does | |-------|---------|-------------| | supply-chain-hardening | `/supply-chain-hardening` | Audit and harden your system against supply chain attacks across npm, Python, Go, Rust, and Homebrew | +| xurl | `/xurl` | Cost-optimized X/Twitter workflows via xurl CLI with local sandbox testing | ## Supply Chain Hardening @@ -30,12 +29,47 @@ Prompted by the axios npm RAT and LiteLLM PyPI credential stealer (both March 20 All changes require user confirmation. Existing configs are backed up before modification. Rollback instructions included. +## xurl + +Post, reply, search, and engage on X/Twitter through [xurl](https://github.com/xdevplatform/xurl) — the official X CLI — with pay-per-use cost awareness baked in. + +**Two operating modes:** + +| Mode | Cost | How | +|------|------|-----| +| **Sandbox** | $0 | Local [playground](https://github.com/xdevplatform/playground) server simulates the full X API v2 | +| **Live** | Real credits | Compose → confirm → post. Spending guardrails with configurable limits | + +**Cost optimization features:** +- Defaults to sandbox when playground is running — unlimited free testing +- Cost preview: see what sandbox operations would cost before going live +- Configurable session spending limits (default $0.25) +- Estimated cost shown before every live write operation +- Local session spend tracking with estimated costs per operation +- 24h UTC deduplication awareness (same resource = 1 charge per day) + +**Pay-per-use pricing** (community-sourced estimates, check Developer Console for authoritative rates): + +| Operation | Cost | +|-----------|------| +| Post read | $0.005 | +| User lookup | $0.010 | +| Post write | $0.010 | +| DM send | $0.015 | +| Like / follow / repost | $0.015 | + +**Prerequisites:** +- [xurl](https://github.com/xdevplatform/xurl) CLI installed (`brew install --cask xdevplatform/tap/xurl`) +- X Developer account with app credentials (registered manually) +- Optional: [playground](https://github.com/xdevplatform/playground) for free sandbox testing + ## Install ```bash /plugin marketplace add fiale-plus/fiale-claude-plugins /plugin install fiale-awesome-skills /supply-chain-hardening +/xurl ``` ## License diff --git a/plugins/fiale-awesome-skills/commands/xurl.md b/plugins/fiale-awesome-skills/commands/xurl.md new file mode 100644 index 0000000..3f80f76 --- /dev/null +++ b/plugins/fiale-awesome-skills/commands/xurl.md @@ -0,0 +1,10 @@ +--- +name: xurl +description: Cost-optimized X/Twitter workflows via xurl CLI with local sandbox +--- + +# xurl + +Run the xurl skill. Post, reply, search, and engage on X/Twitter with pay-per-use cost awareness. Defaults to local sandbox (playground) when available — zero cost for testing. Switches to live API on explicit request with spending guardrails and confirmation gates. + +Requires xurl CLI installed. Optional: playground server for free sandbox testing. diff --git a/plugins/fiale-awesome-skills/skills/xurl.md b/plugins/fiale-awesome-skills/skills/xurl.md new file mode 100644 index 0000000..ace4e86 --- /dev/null +++ b/plugins/fiale-awesome-skills/skills/xurl.md @@ -0,0 +1,453 @@ +--- +name: xurl +description: Cost-optimized X/Twitter workflows via xurl CLI with local sandbox testing. Posts, replies, search, timeline, engagement — all wrapped with spending guardrails, sandbox-first defaults, and pay-per-use cost awareness. Triggers on "/xurl", "post to X", "tweet", "post on twitter", "search X", "check mentions", "xurl post", "send a tweet", "check my timeline", "search twitter", "search and engage", "monitor engagement", "like from search", "review timeline", "batch like", "post and watch". +--- + +# xurl — Cost-Optimized X/Twitter Operations + +Run the X API through xurl with aggressive cost optimization. Every penny counts — default to sandbox, confirm before spending, track costs in real time. + +**Announce at start:** "Using xurl for cost-optimized X/Twitter operations." + +## Security Rules (mandatory, from xurl SKILL.md) + +These are non-negotiable. Violating any of them leaks credentials. + +- **Never read, print, parse, or send `~/.xurl`** to the conversation. This file contains secrets. +- **Never use `--verbose` / `-v`** — it exposes auth headers/tokens in output. +- **Never use inline secret flags** in agent commands: `--bearer-token`, `--consumer-key`, `--consumer-secret`, `--access-token`, `--token-secret`, `--client-id`, `--client-secret`. +- **Credential registration is manual.** The user must run `xurl auth apps add ...` and `xurl auth oauth2` themselves, outside this session. Do not execute auth commands with secrets. +- To check auth state safely: `xurl auth status` (shows apps and token status without exposing secrets). + +## Step 1: Environment Detection and First-Run Setup + +Run detection silently: + +```bash +which xurl 2>/dev/null && echo "xurl: installed" || echo "xurl: NOT FOUND" +which playground 2>/dev/null && echo "playground: installed" || echo "playground: NOT FOUND" +curl -sf http://localhost:3080/health > /dev/null 2>&1 && echo "playground: running on 3080" || echo "playground: not running on 3080" +echo "API_BASE_URL=${API_BASE_URL:-'(not set)'}" +xurl auth status 2>&1 || true +cat /tmp/xurl-session-config.json 2>/dev/null || echo "no session config" +``` + +### Returning user (session config exists) + +Reset per-session fields (`trusted_actions` to `[]`, `spent` to `0.0`, `operations` to `0` — only `spending_limit` carries over). Show one-line header and proceed with the request. + +### First-run interview + +On first invocation, detect what's configured and present a status + guided setup: + +``` +Welcome to xurl. Checking your setup... + + xurl: ✓ installed / ✗ not found + OAuth2: ✓ configured / ✗ missing (needed for post, like, follow, timeline, bookmarks, DM) + Bearer: ✓ configured / ✗ missing (needed for usage tracking) + Playground: ✓ running on 3080 / ✗ not running / ✗ not installed + +What do you want to do? + 1. Post / engage / read timeline or bookmarks (needs OAuth2) + 2. Track my API usage and costs (needs bearer token) + 3. Test in sandbox first (needs playground + OAuth2) + 4. Full setup — all of the above +``` + +Only guide the user through what's missing for their chosen path. Don't dump all setup instructions at once. + +### Auth types explained + +Two auth types serve different purposes — both are recommended: + +| Auth type | What it unlocks | Setup command (user runs outside session) | +|-----------|----------------|------------------------------------------| +| **OAuth2 user context** | Post, reply, like, follow, DM, timeline, bookmarks, mentions | `xurl auth oauth2` | +| **Bearer token (app-only)** | Usage tracking (`/2/usage/tweets`), public search, public user lookup | `xurl auth app --bearer-token ` | + +### Setup instructions (user runs outside this session) + +**If xurl is not installed:** +```bash +brew install --cask xdevplatform/tap/xurl # macOS +npm install -g @xdevplatform/xurl # npm +go install github.com/xdevplatform/xurl@latest # Go +``` + +**OAuth2 setup** (required for most operations): +``` +xurl auth apps add my-app --client-id --client-secret +xurl auth default my-app +xurl auth oauth2 +``` +The `auth default` step is required — OAuth2 flow fails unless the app is set as default first. +Redirect URI must be `http://localhost:8080/callback` in the X Developer Console. + +**Bearer token setup** (required for usage tracking): +``` +xurl auth app --bearer-token +``` +Get the bearer token from X Developer Console → your app → "Keys and tokens" → Bearer Token → Generate. + +**Playground install** (recommended for free sandbox testing): +```bash +go install github.com/xdevplatform/playground/cmd/playground@latest +``` +Requires Go (`brew install go` on macOS). Or download a pre-built binary from https://github.com/xdevplatform/playground/releases. + +Start the sandbox in the background: +```bash +playground start -p 3080 &>/dev/null & +``` +Verify: `curl -sf http://localhost:3080/health && echo "playground: running"` + +Playground runs on port 3080 to avoid conflicting with the OAuth callback on port 8080. + +## Step 2: Sandbox Mode (playground) + +When playground is running AND xurl auth is configured, **default to sandbox** for all operations. This costs $0 — playground accepts any valid token without real billing. + +xurl auth is required even in sandbox mode — xurl needs credentials to construct requests. + +Prefix every xurl command with `API_BASE_URL=http://localhost:3080` to route it to the sandbox. This must be on the same line — a separate `export` will not persist between tool calls. + +**App-auth shortcuts work directly** (search, read): + +```bash +API_BASE_URL=http://localhost:3080 xurl search "query" -n 5 +API_BASE_URL=http://localhost:3080 xurl read POST_ID +``` + +**User-context shortcuts** (`whoami`, `post`, `reply`, `like`, `follow`, `dm`, etc.) may fail with a 403 "Unsupported Authentication" error because the playground validates the OAuth token type. For these, use raw path form instead: + +```bash +# User profile +API_BASE_URL=http://localhost:3080 xurl /2/users/me + +# Post +API_BASE_URL=http://localhost:3080 xurl -X POST /2/tweets -d '{"text":"Hello from sandbox!"}' + +# Like (requires your user ID — get it from /2/users/me first) +API_BASE_URL=http://localhost:3080 xurl -X POST /2/users/USER_ID/likes -d '{"tweet_id":"POST_ID"}' + +# Timeline +API_BASE_URL=http://localhost:3080 xurl /2/users/USER_ID/reverse_chronological + +# Mentions +API_BASE_URL=http://localhost:3080 xurl /2/users/USER_ID/mentions +``` + +Prefix all sandbox output with `[SANDBOX]`. + +### Cost preview + +After testing a workflow in sandbox, show what it would cost on the real API: + +```bash +# Current pricing rates (account-independent, always works) +curl -s http://localhost:3080/api/credits/pricing | jq '.' +``` + +Note: playground derives account ID from the auth token. The default `Bearer test` token maps to account `0`. If xurl sends a real OAuth token via `API_BASE_URL`, the account ID will differ. Check `/health` for active account info before querying per-account endpoints. + +Present the cost preview to the user before they switch to live mode. + +### State management and error simulation + +```bash +# Save current state +curl -s http://localhost:3080/state/export > /tmp/xurl-playground-state.json + +# Restore a saved state +curl -s -X POST -H "Content-Type: application/json" \ + -d @/tmp/xurl-playground-state.json http://localhost:3080/state/import + +# Reset to fresh defaults +curl -s -X POST http://localhost:3080/state/reset + +# Force-save state to disk +curl -s -X POST http://localhost:3080/state/save + +# Simulate rate limiting to test error handling +curl -X PUT http://localhost:3080/config/update \ + -H "Content-Type: application/json" \ + -d '{"errors": {"enabled": true, "error_rate": 0.3, "error_type": "rate_limit"}}' + +# Disable error simulation +curl -X PUT http://localhost:3080/config/update \ + -H "Content-Type: application/json" \ + -d '{"errors": {"enabled": false}}' +``` + +Web UI for browsing sandbox data: `http://localhost:3080/playground` + +### Switching to live + +When the user says "go live", "publish", "send it for real", or similar — simply stop prefixing commands with `API_BASE_URL=http://localhost:3080`. Plain `xurl ...` commands hit the real API. + +Show the mode banner: "[LIVE MODE] Switched. Session limit: $X.XX. Every write will show its cost inline." + +**For any ambiguous write command** when mode is not explicit: default to sandbox if playground is running. If playground is not running, ask before going live — never silently default to live. + +## Step 3: Live Mode + +Prefix all live output with `[LIVE $X.XX]` showing the cost of each operation. + +Use xurl shortcut commands against the real API: + +```bash +xurl post "text" # $0.01 +xurl reply POST_ID "text" # $0.01 +xurl quote POST_ID "text" # $0.01 +xurl read POST_ID # $0.005 +xurl search "query" -n 10 # ~$0.005 × results (see note) +xurl user @handle # $0.01 +xurl timeline -n 20 # ~$0.005 × results +xurl mentions -n 10 # ~$0.005 × results +xurl like POST_ID # $0.015 +xurl repost POST_ID # $0.015 +xurl follow @handle # $0.015 +xurl bookmark POST_ID # $0.015 +xurl dm @handle "message" # $0.015 +xurl dms -n 10 # ~$0.01 × results +xurl media upload file.jpg # $0.01 +``` + +Note: search/timeline/mentions billing per-post vs per-request is not publicly confirmed by X. The ~$0.005/result is community consensus. Use the lowest `-n` value that satisfies your need — it directly controls cost. + +### Batch reads + +When reading multiple posts, use the batch endpoint to save rate limit quota (up to 100 IDs in one request): + +```bash +# Individual: 10 separate requests (eats rate limit faster) +xurl read ID1 && xurl read ID2 && ... + +# Batch: one request, same billing (each post still counts separately) +xurl /2/tweets?ids=ID1,ID2,ID3,...,ID10 +``` + +Note: X bills each post in a batch separately — batching does NOT reduce credit cost. It saves rate limit quota and network round trips. + +### Write operations — compose + confirm + +**Single writes**: inline confirmation — "[LIVE] Post: «{text}» ($0.01) — confirm?" + +**Batch of same type** (user described the plan): one confirmation showing all items, total cost, and count — no per-item prompts. Example: "Like 5 posts ($0.075 total) — confirm?" + +**First write of session**: always individual confirmation, even in batch context. + +**Trust mode**: after the first confirmed write of a given type, offer "Skip confirmations for [likes/reposts/etc.] this session?" Track in `/tmp/xurl-session-config.json` under `trusted_actions`. Trust is **per-session only** — always reset `trusted_actions` to `[]` at the start of each new session (Step 1). The spending limit guardrail remains active regardless. + +**Before batch reads** (search, timeline, mentions with -n > 10), show estimated cost inline — no separate confirmation. + +Post IDs and full URLs both work: `xurl read https://x.com/user/status/123` extracts the ID automatically. + +## Step 4: Spending Guardrails + +Check for existing session config (already done in Step 1). If config exists with a spending limit, use it silently. + +If no config exists AND user is in live mode, ask once inline: +> "No session limit set. Suggestions: $0.25 (casual), $2.00 (dev testing), $10.00 (automation). Default $0.25 — proceed? (or say an amount)" + +Do not block sandbox operations for missing config — sandbox is always free. + +Store in `/tmp/xurl-session-config.json`: +```json +{"spending_limit": 0.25, "spent": 0.0, "operations": 0, "trusted_actions": []} +``` + +This file persists in `/tmp` (survives across sessions until reboot). Only `spending_limit` carries over — `spent`, `operations`, and `trusted_actions` are reset at each session start (Step 1). + +Track spending locally using approximate costs: + +| Operation | Approximate Cost | +|-----------|-----------------| +| Post read (single or batch) | $0.005 per post | +| User lookup | $0.010 | +| Post write | $0.010 | +| DM read | $0.010 | +| DM send | $0.015 | +| Engagement (like/follow/repost) | $0.015 | + +These are community-sourced estimates. X does not publish per-endpoint rates publicly — the Developer Console is the only authoritative source, and prices can change without notice. + +Update the tracker after every live API call. Guardrails: +- **80% of limit** → yellow warning: "Approaching spending limit ($X.XX / $Y.YY)" +- **100% of limit** → hard stop with options: + 1. Raise limit: "set limit $X.XX" + 2. Continue in sandbox (no further live writes) + 3. End session — show summary + +The Developer Console spending limit is your absolute safety net — set it to your maximum (e.g., $10/month for casual use). The session limit here is your active-use guardrail. Both should be configured. + +## Step 5: Common Workflows + +### Post + Monitor Engagement +``` +1. xurl post "..." → [LIVE $0.01] → note the post ID +2. Wait (user-specified interval) +3. xurl read {ID} → check public_metrics +4. If engagement exceeds user's threshold, reply or repost +``` + +### Search + Engage +``` +1. xurl search "query" -n 10 → review results (sandbox first if available) +2. Go live: batch-confirm engagement (like/repost) in one prompt +3. Show summary: "Engaged with N posts, spent $X.XX" +``` + +### Timeline Review +``` +1. xurl timeline -n 20 → surface results +2. User flags interesting posts +3. Batch-engage with flagged posts: one confirmation for the whole set +``` + +### Bookmark Processing +``` +1. xurl bookmarks -n 10 → fetch recent bookmarks (~$0.05) +2. For each bookmark: read full post → extract key content +3. Process into knowledge base, notes, or action items +4. Optionally unbookmark processed items to track progress +``` + +Cost: ~$0.005 per bookmark fetched + ~$0.005 per full read if needed. A 10-bookmark processing run costs ~$0.05–$0.10. + +## Step 6: Error Recovery + +**Playground not running (user expects sandbox):** +``` +Sandbox not available — playground is not running on port 3080. + 1. Start sandbox: `playground start -p 3080 &>/dev/null &` (~2s) + 2. Switch to live mode (real credits) + 3. Cancel +Which? (default: 1) +``` + +**Auth token expired mid-session (401):** +``` +[LIVE] Auth error — token may have expired. +Run outside this session: xurl auth oauth2 +Sandbox available in the meantime. +``` + +**Rate limited (429) — not billed:** +``` +[LIVE] Rate limited (429) — this request was NOT billed. +Next retry: check x-rate-limit-reset header. + 1. Wait and retry automatically + 2. Reduce batch size (e.g., -n 5 instead of -n 20) + 3. Cancel +``` + +**Spending limit reached:** +``` +[LIVE] Spending limit reached ($X.XX / $Y.YY). + 1. Raise limit: "set limit $Z.ZZ" + 2. Continue in sandbox (no further live writes) + 3. End session — show summary +Sandbox operations remain available. +``` + +## Step 7: Multi-Profile Setup + +Show this recommendation **after the user's first successful live write**, not at session start. + +If the user has only one app configured, suggest: + +- **Playground** — local sandbox, unlimited free testing +- **dev** app — real API with $5 spending limit in Developer Console +- **prod** app — real API with production spending limit + +The user registers apps manually (outside this session): +``` +xurl auth apps add dev --client-id --client-secret +xurl auth apps add prod --client-id --client-secret +``` + +Switch: `xurl auth default dev` or `xurl --app prod post "..."` + +## Step 8: Session Summary + +When the user asks for a summary, or at session end: + +1. Read `/tmp/xurl-session-config.json` for local spend tracking +2. If bearer token is configured, query real usage: + ```bash + xurl --auth app "/2/usage/tweets?days=1" + ``` + This returns actual post consumption (reads) for today. Note: it only tracks post reads, not writes/likes/follows. +3. If playground was used, show pricing reference: + ```bash + curl -s http://localhost:3080/api/credits/pricing | jq '.' + ``` + +Present as: + +``` +## X API Session Summary +| Category | Count | Est. Cost | +|----------|-------|-----------| +| Sandbox ops | N | $0.00 (free) | +| Live reads | N | ~$X.XX | +| Live writes | N | ~$X.XX | +| Live engagements | N | ~$X.XX | +| Est. total live spend | — | ~$X.XX | +| Session limit | — | $Y.YY | +| API usage (from /2/usage) | N posts consumed today | (real data) | +``` + +Local estimates are approximate. The `/2/usage/tweets` endpoint (requires bearer token auth) gives real post consumption data. For full billing details, check the X Developer Console. + +## Step 9: Quick Pick (no context) + +If the user invokes `/xurl` with no argument and no prior context: + +``` +What would you like to do? + 1. Post / reply / quote + 2. Search X + 3. Check timeline or mentions + 4. Engage with posts (like, repost, bookmark) + 5. Send or read DMs + 6. Check usage and session costs + 7. Set up or review xurl configuration +``` + +## Billing Reference + +**24h UTC deduplication**: Same resource requested multiple times within one UTC day is billed once. Note: reads of the same post on different UTC days each incur a separate charge. + +**Only successful responses are billed.** Failed requests (4xx, 5xx) cost nothing. Rate-limited (429) requests are not billed. + +**Monthly cap**: 2M post reads. Enterprise ($42K+/mo) required beyond that. + +**Spending limits**: Set in the Developer Console to prevent runaway costs. Auto-recharge tops up when balance is low. + +**xAI credit rewards**: 10% back at $200+ cumulative spend, 15% at $500+, 20% at $1,000+. Only meaningful at $200+/month — solo/casual users won't hit this threshold. + +### Rate Limits (separate from billing) + +| Endpoint | Per App / 15min | Per User / 15min | +|----------|----------------|-----------------| +| Post lookup | 450 | 900 | +| Search recent | 450 | 300 | +| Post create | 10K / 24h | 100 / 15min | +| Like | — | 50 / 15min, 1K / 24h | +| User lookup | 300 | 900 | + +On 429: back off and retry. Rate-limited requests are not billed. + +### Reference Links + +For up-to-date information (pricing may change): +- Pricing: https://docs.x.com/x-api/getting-started/pricing +- Usage & billing: https://docs.x.com/x-api/fundamentals/post-cap +- Rate limits: https://docs.x.com/x-api/fundamentals/rate-limits +- Usage API: https://docs.x.com/x-api/usage/introduction +- xurl: https://github.com/xdevplatform/xurl +- Playground: https://github.com/xdevplatform/playground +- Docs source: https://github.com/xdevplatform/docs