xskill skill.md

API Reference

API Reference

Prepaid HTTP endpoints for reading X/Twitter posts, conversations, search results, parsed summaries, and image OCR through a stable agent-facing API.

xskill is a prepaid API for agents that need to read and understand X/Twitter posts, threads, conversations, search results, and image media.

Base URLs

  • Production API: https://api.xskill.md
  • Local development default: http://127.0.0.1:3000

Set these shell variables before using the examples:

shell
export XSKILL_BASE_URL="${XSKILL_BASE_URL:-https://api.xskill.md}"
export XSKILL_API_KEY="xsk_replace_me"
export X_POST_ID="1234567890123456789"

Authentication

Customer endpoints require an xsk_ API key. Prefer the bearer form:

http
Authorization: Bearer xsk_...

x-api-key: xsk_... is also accepted. Never send provider or parser secrets such as TWITTERAPI_IO_API_KEY, GETXAPI_API_KEY, or ANTHROPIC_API_KEY to xskill endpoints.

GET /health, POST /v1/signup, and POST /v1/stripe/webhook do not use customer API-key auth. GET /v1/ops/metrics is an internal endpoint protected by XSK_OPS_TOKEN; never use a customer xsk_ key for ops access.

Copy-Paste Quickstart

Check service health:

shell
curl -sS "$XSKILL_BASE_URL/health"

Create a new account and one-time xsk_ key:

shell
curl -sS \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"inviteCode":"replace-with-invite","name":"Research agent"}' \
  "$XSKILL_BASE_URL/v1/signup"

Read and summarize a conversation:

shell
curl -sS \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  "$XSKILL_BASE_URL/v1/thread?id=$X_POST_ID&mode=conversation&parse=summary"

Read one post:

shell
curl -sS \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  "$XSKILL_BASE_URL/v1/post?id=$X_POST_ID"

Search X:

shell
curl -sS -G \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  --data-urlencode "query=from:OpenAI agent" \
  --data-urlencode "type=Latest" \
  "$XSKILL_BASE_URL/v1/search"

Create a $10.00 top-up Checkout Session:

shell
curl -sS \
  -X POST \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amountCents":1000}' \
  "$XSKILL_BASE_URL/v1/topups/checkout"

Response Shape

Successful API responses return a data object. Billable read endpoints also return usage.

json
{
  "data": {},
  "usage": {
    "cost": {
      "currency": "USD",
      "estimatedUsd": 0.003,
      "itemsRead": 20,
      "unitCostUsd": 0.00015,
      "upstreamRequests": 1
    },
    "pricing": {
      "currency": "USD",
      "operation": "parsed_thread",
      "priceCardVersion": "2026-06-25.default",
      "priceMicroCredits": 20000,
      "priceUsd": 0.02,
      "units": {
        "tweets": 20
      }
    },
    "provider": "twitterapi.io",
    "tweetsRead": 20
  }
}

usage.provider is the active backend adapter for the call. It is normally twitterapi.io, but may be getxapi during an operator failover. Customer routes and price-card billing stay the same across that provider flip.

Errors use one envelope:

json
{
  "error": {
    "code": "invalid_request",
    "message": "Request validation failed",
    "statusCode": 400
  }
}

Rate-limited authenticated requests include x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, and, for rejected requests, retry-after.

Pricing

xskill stores balances as integer micro-credits:

  • 1_000_000 micro-credits = $1.00
  • $0.02 = 20_000 micro-credits

Default price card:

OperationPrice
Raw post4_000 micro-credits
Raw search300 * tweetsReturned
Raw thread4_000 + 300 * tweetsRead
Parsed thread, Haiku14_000 + 300 * tweetsRead
Parsed thread, Sonnet44_000 + 300 * tweetsRead
Vision image OCR/description20_000 * images

The default free tier tops each account balance up to 2_000_000 micro-credits once per UTC month. Free credits are account-scoped, not API-key-scoped, and unused free credits do not stack above the monthly cap.

Top-ups use Stripe Checkout. The minimum top-up is $10.00, which credits 10_000_000 micro-credits after Stripe reports a paid Checkout Session. Signup and checkout surfaces must link the Terms of Service and Pricing and Refund Policy before creating a Checkout Session.

See pricing.md for price-card overrides and reservation behavior.

Endpoints

GET/health

Returns service health. No auth.

Example:

shell
curl -sS "$XSKILL_BASE_URL/health"

Response:

json
{
  "environment": "production",
  "ok": true,
  "service": "xskill-api"
}

POST/v1/signup

Creates a new account and returns a one-time xsk_ API key. Public signup is invite-gated with XSK_SIGNUP_INVITE_CODE, invite reuse tracking, and IP throttling via XSK_SIGNUP_RATE_LIMIT_MAX_REQUESTS / XSK_SIGNUP_RATE_LIMIT_WINDOW_MS. Set XSK_DATA_FILE to persist account metadata, API-key hashes, invite-use hashes, balances, credit-grant idempotency, and usage records across single-replica restarts. Without XSK_DATA_FILE, those stores are process-local. Direct deployments do not trust proxy headers by default; hosted deployments behind a proxy that strips inbound forwarding headers should set XSK_TRUST_PROXY=true so throttling keys on the forwarded client IP instead of the load balancer. The key is only returned in this response; store it before leaving the page or terminal.

Request body:

NameRequiredValuesDefault
inviteCodeyesactive signup invite codenone
nameno1 to 80 charactersomitted

Example:

shell
curl -sS \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"inviteCode":"replace-with-invite","name":"Research agent"}' \
  "$XSKILL_BASE_URL/v1/signup"

Response:

json
{
  "data": {
    "account": {
      "createdAt": "2026-06-26T00:00:00.000Z",
      "id": "acct_..."
    },
    "apiKey": {
      "accountId": "acct_...",
      "createdAt": "2026-06-26T00:00:00.000Z",
      "id": "key_...",
      "name": "Research agent",
      "prefix": "xsk_..."
    },
    "docs": {
      "api": "/api",
      "pricing": "/pricing",
      "refundPolicy": "/refund-policy",
      "skill": "/skill.md",
      "terms": "/terms"
    },
    "key": "xsk_...",
    "topUp": {
      "amountCents": 1000,
      "amountMicroCredits": 10000000,
      "endpoint": "/v1/topups/checkout"
    }
  }
}

The hosted landing page uses this endpoint, then calls POST /v1/topups/checkout with the new key after showing the Terms of Service and Pricing and Refund Policy links.

GET/v1/post

Fetches one post by numeric X post ID.

Query parameters:

NameRequiredValuesDefault
idyes1 to 25 digitsnone
parsenosummary, json, tldromitted
modelnohaiku, sonnethaiku
visionnotrue, falsefalse

Billing:

  • No parse: raw_post.
  • parse=...&model=haiku: parsed_thread for one tweet, with raw-post fallback reservation.
  • parse=...&model=sonnet: premium_parsed_thread for one tweet. Requires premium models to be enabled.
  • vision=true: separately bills vision_image for photo media analyzed.

Example:

shell
curl -sS \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  "$XSKILL_BASE_URL/v1/post?id=$X_POST_ID&parse=tldr&vision=true"

Response fields:

  • data.post: normalized post.
  • data.parsed: present when parse is requested.
  • data.vision: image OCR/description results when vision=true.
  • usage.tweetsRead: upstream post reads.
  • usage.pricing: customer price metadata.
  • usage.vision: separate image billing metadata when applicable.

GET/v1/thread

Fetches tweets in a conversation through the provider's conversation_id search path. Use the root conversation ID when possible.

Query parameters:

NameRequiredValuesDefault
idyes1 to 25 digitsnone
modenoconversation, threadconversation
maxPagesnointeger >= 1provider default
maxTweetsnointeger >= 1provider default
parsenosummary, json, tldromitted
modelnohaiku, sonnethaiku
visionnotrue, falsefalse

mode=conversation returns all fetched conversation tweets. mode=thread returns the root author's self-reply chain when root-author and reply metadata are available; otherwise it falls back to the full conversation to avoid guessing.

Billing:

  • No parse: raw_thread.
  • parse=...&model=haiku: parsed_thread.
  • parse=...&model=sonnet: premium_parsed_thread. Requires premium models.
  • vision=true: separately bills vision_image for photo media analyzed.

The twitterapi.io adapter caps thread fetches at 5 pages / 100 tweets.

Example:

shell
curl -sS \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  "$XSKILL_BASE_URL/v1/thread?id=$X_POST_ID&mode=thread&parse=summary&maxPages=2"

Response fields:

  • data.id: conversation ID.
  • data.mode: resolved mode.
  • data.tweets: normalized tweets.
  • data.truncated: true when the provider reports additional pages.
  • data.parsed: present when parse is requested.
  • data.vision and data.visionTruncated: present when vision=true.
  • usage.tweetsRead: upstream tweets read.
  • usage.tweetsReturned: tweets returned after mode filtering.

Runs provider-backed X advanced search.

Query parameters:

NameRequiredValuesDefault
queryyesnon-empty string, max 512 charsnone
typenoLatest, TopLatest
cursornoprovider cursoromitted
maxPagesnointeger 1..51
maxTweetsnointeger 1..10020

Billing: raw_search, settled to 300 * tweetsReturned. The route reserves the provider-estimated reachable result window before provider spend.

Example:

shell
curl -sS -G \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  --data-urlencode "query=conversation_id:$X_POST_ID" \
  --data-urlencode "type=Latest" \
  --data-urlencode "maxTweets=20" \
  "$XSKILL_BASE_URL/v1/search"

Response fields:

  • data.query: query sent to the provider.
  • data.type: Latest or Top.
  • data.tweets: normalized tweets.
  • data.pageInfo.nextCursor: cursor for the next page when present.
  • usage.tweetsRead: upstream tweets read.
  • usage.tweetsReturned: tweets returned to the caller.

POST/v1/topups/checkout

Creates a Stripe Checkout Session for prepaid credits. Show or link the Terms of Service and Pricing and Refund Policy before creating a Checkout Session.

Request body:

json
{
  "amountCents": 1000
}

amountCents must be an integer of at least 1000.

Example:

shell
curl -sS \
  -X POST \
  -H "Authorization: Bearer $XSKILL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amountCents":1000}' \
  "$XSKILL_BASE_URL/v1/topups/checkout"

Response:

json
{
  "data": {
    "amountCents": 1000,
    "amountMicroCredits": 10000000,
    "currency": "usd",
    "id": "cs_test_...",
    "url": "https://checkout.stripe.com/..."
  }
}

Credits are added only after Stripe sends a paid Checkout webhook.

POST/v1/stripe/webhook

Stripe-only webhook endpoint. No customer API-key auth.

Headers:

http
Stripe-Signature: ...
Content-Type: application/json

The route verifies the raw request body with STRIPE_WEBHOOK_SECRET. Paid checkout.session.completed and checkout.session.async_payment_succeeded events credit the account once using the Checkout Session ID as the idempotency key.

GET/v1/ops/metrics

Internal observability endpoint. Requires the configured ops token:

shell
curl -sS \
  -H "Authorization: Bearer $XSK_OPS_TOKEN" \
  "$XSKILL_BASE_URL/v1/ops/metrics"

The response is a JSON dashboard with real-time in-process usage, cost, revenue, margin, credit grants, recent per-call billing logs, and twitterapi.io prepaid float alert status.

Response fields:

  • data.totals.revenueUsd, data.totals.costUsd, and data.totals.marginUsd: revenue vs upstream cost vs margin.
  • data.byEndpoint and data.byOperation: request, unit, revenue, cost, and margin breakdowns.
  • data.byProvider: provider/parser cost breakdown. Parsed calls split twitterapi.io fetch cost from Anthropic parser cost.
  • data.recentUsage: recent billable reservations/settlements.
  • data.float.status: ok, low, or unknown.
  • data.alerts: includes twitterapi_io_float_low when XSK_TWITTERAPI_IO_FLOAT_BALANCE_USD is below XSK_TWITTERAPI_IO_FLOAT_ALERT_USD.

Error Codes

HTTPCodeMeaning
400invalid_requestRequest validation failed or JSON body is invalid.
400invalid_stripe_eventStripe webhook payload is not a valid xskill top-up event.
400invalid_stripe_signatureStripe signature is missing or invalid.
401unauthorizedMissing, invalid, or revoked xsk_ API key.
402insufficient_balancePrepaid balance is too low; top up to continue.
403invalid_signup_inviteSignup invite code is invalid.
403premium_model_requiredmodel=sonnet was requested but premium models are disabled.
404post_not_foundThe requested post was not found.
404not_foundRoute does not exist.
409stripe_idempotency_conflictStripe top-up idempotency conflict.
413invalid_requestRequest body is too large.
413cost_ceiling_exceededRequest would exceed the configured per-call cost ceiling.
413parse_budget_exceededParse input/output budget is too large.
415invalid_requestRequest media type is not supported.
429rate_limitedPer-key or public signup rate limit exceeded.
500internal_errorUnexpected server error.
503auth_unavailableAPI-key auth is not configured.
503billing_unavailableCredit ledger is not configured or did not return a usage id.
503ops_unavailableOps metrics are missing or XSK_OPS_TOKEN is not configured.
503parser_provider_unavailableThe configured parser provider rejected or failed the parse request.
503parser_unavailableParse was requested but no parser is configured.
503pricing_unavailablePricing engine is not configured.
503provider_unavailableTweet provider is missing or unavailable.
503signup_unavailablePublic API-key issuance is not configured.
503stripe_unavailableStripe Checkout or webhook verification is not configured.
503vision_unavailableVision was requested but no analyzer is configured or available.

Normalized Data Notes

Tweet objects can include:

  • id, text, url, createdAt, conversationId, inReplyToTweetId
  • author fields such as id, username, name, verified, profileImageUrl
  • count fields such as replyCount, retweetCount, likeCount, quoteCount, bookmarkCount, viewCount
  • media[] with type, url, previewImageUrl, altText, and optional vision OCR/description data

Provider and parser failures are sanitized; responses do not expose upstream API keys, Anthropic keys, raw prompt internals, or customer API-key material.