Skip to docs content

Agent quickstart

An agent can run every step here as an HTTP call for the person it works for, without opening the dashboard: try the free sandbox, sign up with an email code, send one payment link, then connect.

Open OpenAPI spec
Open in chat
Reviewed against product code on October 29, 2026

Try the sandbox first

The sandbox needs no account and no key. It streams posts from a fixed list of 50 X accounts, 60 seconds late, as the same tweet envelopes the paid stream sends: content, update, meta, delete, pin and unpin. Code written against it runs on the paid stream once you change the host and add a key.

Each client can hold 2 connections, and a session ends after 30 minutes; reconnect to keep going. The first frame is sandbox_welcome. Requests to add or remove accounts are answered with an error.

Sandbox accounts: @_richardteng, @a1lon9, @binance, @binancewallet, @binancezh, @blknoiz06, @bnbchain, @brian_armstrong, @cb_doge, @cobie, @coinbase, @cz_binance, @deitaone, @elonmusk, @eth_cedric, @fern, @finkd, @firstsquawk, @flapdotsh, @fourdotmemezh, @heyibinance, @jensenhuang, @jerallaire, @jessepollak, @justinsuntron, @meadgod, @mert, @nikitabier, @nina_rong, @openai, @pmarca, @polymarket, @ponsdotfamily, @pumpfun, @realdonaldtrump, @robinhoodapp, @robinhoodcrypto, @sama, @saylor, @solana, @spacex, @theunipcs, @toly, @unusual_whales, @vitalikbuterin, @vladtenev, @watcherguru, @whitehouse, @zachxbt, @zerohedge.

curl -s https://tweetstream.io/api/agent/sandbox
npx wscat -c wss://sandbox.tweetstream.io/ws -s tweetstream.v1

First frame

json
{
  "v": 1,
  "t": "control",
  "op": "sandbox_welcome",
  "ts": 1793275200000,
  "d": {
    "signupUrl": "https://tweetstream.io/llms.txt",
    "accounts": 50,
    "delaySeconds": 60,
    "sessionSeconds": 1800
  }
}

Sign up with an email code

Sign-up takes two calls. The first emails a 6-digit code to the person. The second trades that code for the account's API key. The first response is the same whether or not the email already has an account, and an existing account gets its current key back.

The code expires at expiresAt. A new one can be requested after resendAt.

setupProof lasts 30 minutes. You need it to issue a Discord link and to read a key again. If it expires, request and verify a new code.

Email a code

bash
curl -s https://tweetstream.io/api/agent/email-code/request \
  -H 'Content-Type: application/json' \
  -d '{"email":"alice@example.com"}'

202 response

json
{
  "status": "awaiting_email",
  "challengeId": "4f1c9e2a7b3d8065c1e4a9f27d3b5c8e0a6f1d2b9c4e7a3f5d8b0c6e2a9f1d47",
  "expiresAt": "2026-10-29T12:10:00.000Z",
  "resendAt": "2026-10-29T12:01:00.000Z",
  "next": {
    "action": "verify_email",
    "who": "human_then_agent",
    "method": "POST",
    "url": "https://tweetstream.io/api/agent/email-code/verify"
  }
}
# Only after the person gave you the code and accepted the Terms.
curl -s https://tweetstream.io/api/agent/email-code/verify \
  -H 'Content-Type: application/json' \
  -d "{\"challengeId\":\"$CHALLENGE_ID\",\"code\":\"$EMAIL_CODE\",\"acceptTerms\":true}"

200 response

json
{
  "status": "payment_required",
  "family": "BASE",
  "reason": "new",
  "apiKey": "<the account's API key, shown once>",
  "setupProof": "<setup proof, valid for 30 minutes>",
  "next": {
    "action": "create_checkout",
    "who": "agent",
    "method": "POST",
    "url": "https://tweetstream.io/api/agent/checkout"
  }
}
# Keep both out of chat, commits and logs.
export TWEETSTREAM_API_KEY='<apiKey from the verify response>'
export TWEETSTREAM_SETUP_PROOF='<setupProof from the verify response>'

Start a checkout

Ask the person which plan they want, then start a checkout. The person gets one payment link, and that page shows the price before anything is charged. Crypto is the default: the page takes payment from a crypto wallet. Add "paymentRail":"CARD" to pay by card instead.

  • planType is BASIC for Minimum or ELITE for Pro. billingCycle is MONTHLY, QUARTERLY, SEMIANNUAL or YEARLY.
  • If the account still has a trial, the checkout uses it: offer.mode is trial and amountDueNow is 0.
  • offer.renewal.amount is the first charge, on renewal.at. renewal.thenAmount is every charge after that. They differ when a trial deposit is taken off the first charge.
  • Send the setup proof with the checkout. If the purchase needs Discord, the response then has the join link. Without a valid proof, next is reverify_email: request and verify a new code, then send the same checkout again with the new proof.
  • Make one Idempotency-Key per purchase and reuse it on retries, so a retry returns the same checkout.
  • Give the person paymentUrl and let them pay there. Then poll next.pollUrl.
  • GET /api/agent/catalog lists every plan, price and limit.
CHECKOUT_KEY=$(uuidgen)   # one key per purchase; reuse it on retries
curl -s https://tweetstream.io/api/agent/checkout \
  -H "Authorization: Bearer $TWEETSTREAM_API_KEY" \
  -H "X-TweetStream-Setup-Proof: $TWEETSTREAM_SETUP_PROOF" \
  -H "Idempotency-Key: $CHECKOUT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"family":"BASE","planType":"ELITE","billingCycle":"MONTHLY"}'

201 response

json
{
  "status": "payment_required",
  "family": "BASE",
  "reason": "new",
  "checkoutId": "pl_8k2m4q7x",
  "offer": {
    "mode": "trial",
    "planName": "Pro",
    "amountDueNow": {
      "currency": "USD",
      "minor": "0"
    },
    "renewal": {
      "amount": {
        "currency": "USD",
        "minor": "49900"
      },
      "at": "2026-11-01T12:00:00.000Z",
      "billingCycle": "MONTHLY",
      "thenAmount": {
        "currency": "USD",
        "minor": "49900"
      }
    }
  },
  "paymentUrl": "https://tweetstream.io/pay/EXAMPLE_PAYMENT_LINK",
  "next": {
    "action": "open_payment",
    "who": "human",
    "url": "https://tweetstream.io/pay/EXAMPLE_PAYMENT_LINK",
    "pollUrl": "https://tweetstream.io/api/agent/status?family=BASE",
    "afterSeconds": 5
  }
}
# Instead of the crypto checkout above, not after it: a new purchase gets its own key.
CARD_CHECKOUT_KEY=$(uuidgen)
curl -s https://tweetstream.io/api/agent/checkout \
  -H "Authorization: Bearer $TWEETSTREAM_API_KEY" \
  -H "X-TweetStream-Setup-Proof: $TWEETSTREAM_SETUP_PROOF" \
  -H "Idempotency-Key: $CARD_CHECKOUT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"family":"BASE","planType":"ELITE","billingCycle":"MONTHLY","paymentRail":"CARD"}'

Join Discord when asked

Some purchases need the person in the TweetStream Discord server first: a paid Base plan, and every Ultra Speed or Instagram Tracking purchase. A Base trial does not. The checkout then returns community_required.

  • With the setup proof, the checkout response includes the join link. Without a valid proof, next is reverify_email. To get the link again later, call POST /api/agent/discord-link with the proof.
  • Give the person the link. Status reads return wait_for_discord until they have joined, then resume_checkout.
  • Send the same checkout request with the same Idempotency-Key. The response has the payment link.
  • Status reads never include the join link.

200 response

json
{
  "status": "community_required",
  "family": "BASE",
  "reason": "COMMUNITY_REQUIRED",
  "checkoutId": "pl_8k2m4q7x",
  "discord": {
    "url": "https://tweetstream.io/connect/discord/EXAMPLE_DISCORD_LINK",
    "expiresAt": "2026-10-29T13:00:00.000Z"
  },
  "next": {
    "action": "join_discord",
    "who": "human",
    "url": "https://tweetstream.io/connect/discord/EXAMPLE_DISCORD_LINK",
    "pollUrl": "https://tweetstream.io/api/agent/status?checkoutId=pl_8k2m4q7x",
    "afterSeconds": 5
  }
}

Poll until ready

After a step the person takes, poll next.pollUrl every next.afterSeconds seconds. Every response has one status and one next, and next.who says whether you or the person acts.

Readiness can change later, for example when a trial ends. If the stream refuses the key, read status again.

StatusMeaningNext
awaiting_emailA code was emailed.verify_email: ask the person for the code and their agreement to the Terms.
payment_requiredNo plan or trial for this product yet.create_checkout, open_payment (give the person the link), or resume_checkout.
community_requiredThis purchase needs the person in the TweetStream Discord server.join_discord, issue_discord_link, wait_for_discord or reverify_email.
processingA payment is being confirmed.wait: poll again.
readyThe product is active.select_accounts, save_credential or connect. optionalSteps may offer the Discord link.
closedThe checkout expired or was canceled.create_checkout with a new key.
blockedSupport has to look at the account, or Ultra Speed has more accounts selected than the plan allows.resolve: tell the person, or turn Ultra accounts off.

Poll status

bash
until curl -s "https://tweetstream.io/api/agent/status?family=BASE" \
    -H "Authorization: Bearer $TWEETSTREAM_API_KEY" | grep -q '"status":"ready"'; do
  sleep 5
done
{
  "status": "ready",
  "family": "BASE",
  "access": {
    "kind": "trial",
    "through": "2026-11-01T12:00:00.000Z"
  },
  "stream": {
    "url": "wss://ws-global.tweetstream.io/ws",
    "subprotocol": "tweetstream.v1",
    "credentialScope": "STANDARD"
  },
  "next": {
    "action": "select_accounts",
    "who": "agent",
    "method": "POST",
    "url": "https://tweetstream.io/api/add-account"
  },
  "optionalSteps": [
    {
      "action": "issue_discord_link",
      "who": "agent",
      "method": "POST",
      "url": "https://tweetstream.io/api/agent/discord-link"
    }
  ]
}

Pick accounts and connect

Add the X accounts to stream, then open stream.url with the key in the Authorization header.

  • If your WebSocket client can't set headers, send the key as the subprotocol tweetstream.auth.token.<API_KEY> next to tweetstream.v1. Never put it in the URL.
  • From here, the WebSocket quickstart and the complete examples apply as written.

Add accounts

bash
curl -s https://tweetstream.io/api/add-account \
  -H "Authorization: Bearer $TWEETSTREAM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"accounts":["coinbase","binance"]}'

Connect

bash
npx wscat -c wss://ws-global.tweetstream.io/ws -s tweetstream.v1 \
  -H "Authorization: Bearer $TWEETSTREAM_API_KEY"

Error codes

Errors have the shape {"error":{"code","message","retryAfterSeconds"},"next"}. Branch on code; message is written for people. Every error has a next: do that. retry means send the same request again after next.afterSeconds.

CodeHTTPWhat to do
INVALID_REQUEST400Fix the body or headers. Unknown fields are rejected.
INVALID_CODE400Ask the person to check the code, or request a new one.
TERMS_NOT_ACCEPTED400Ask the person to accept the Terms, then send acceptTerms: true.
INVALID_API_KEY401Check TWEETSTREAM_API_KEY. A rotated key stops working.
REVERIFY_EMAIL401The setup proof is missing or expired. Request and verify a new code.
ACCOUNT_RESTRICTED403Stop and tell the person to contact support.
ORIGIN_REJECTED403Call from a server or terminal, without a browser Origin header.
CHECKOUT_NOT_FOUND404Check checkoutId, or read status by family.
NOT_ENTITLED404Finish the checkout first, then poll status until ready.
PAYMENT_LINK_INVALID404Start a new checkout to get a new payment link.
DISCORD_LINK_INVALID404Issue a new Discord link.
IDEMPOTENCY_CONFLICT409This Idempotency-Key was used for a different body. Use a new key.
CHECKOUT_CONFLICT409Another checkout for this product is open. Read status and follow it.
OFFER_CHANGED409The price or plan changed. Show the person the new offer.
TRIAL_UNAVAILABLE409No trial for this account. Start a paid checkout.
TRIAL_AUTHORIZATION_REQUIRED409This trial needs a code from the TweetStream team.
RATE_LIMITED429Wait retryAfterSeconds, then retry.
ACTIVATION_BUDGET_EXHAUSTED429Too many trial starts. Wait retryAfterSeconds.
TEMPORARILY_UNAVAILABLE503Retry with backoff and the same Idempotency-Key.

Keep the key safe

  • Store the key in TWEETSTREAM_API_KEY or a secret store. Don't paste it into chat, commit it, or print it.
  • Send it only in the Authorization header or the auth subprotocol. URLs end up in logs, so never use ?apiKey=.
  • If the key was exposed, for example committed, logged or left in a shared transcript, rotate it: POST /api/agent/credentials/rotate with the setup proof, or the rotate_api_key MCP tool. The old key stops working at once and the response holds the new key. Regenerate key on the dashboard's API tab does the same.
  • Rotating the main key (BASE) also ends the setup proof, because the proof belongs to that key: verify a new code before the next call that needs one. Rotating the Ultra Speed key (ULTRA) leaves the setup proof working.
  • Treat setupProof as a secret too. It expires after 30 minutes.
# Needs a setup proof from the last 30 minutes. If it expired, verify a new email code first.
curl -s https://tweetstream.io/api/agent/credentials/rotate \
  -H "Authorization: Bearer $TWEETSTREAM_API_KEY" \
  -H "X-TweetStream-Setup-Proof: $TWEETSTREAM_SETUP_PROOF" \
  -H 'Content-Type: application/json' \
  -d '{"family":"BASE"}'

Use the MCP server

Agents that support remote MCP servers can run the same flow as tools. The server is https://tweetstream.io/mcp. The person signs in once in a browser with an email code and accepts the Terms there. The client config holds no secrets.

Claude Code

bash
claude mcp add --transport http tweetstream https://tweetstream.io/mcp
# Then run /mcp in Claude Code and sign in.
{
  "mcpServers": {
    "tweetstream": {
      "url": "https://tweetstream.io/mcp"
    }
  }
}

Codex

bash
codex mcp add tweetstream --url https://tweetstream.io/mcp
codex mcp login tweetstream

Ultra Speed, Instagram and Scale

The same calls start every product; only the checkout body changes.

  • Ultra Speed: {"family":"ULTRA","accountLimit":10}. Once ready, read its separate key with POST /api/agent/credentials and {"family":"ULTRA"} plus the setup proof, then choose accounts with POST /api/ultra/accounts.
  • Instagram Tracking: {"family":"INSTAGRAM","accountLimit":15}. Add accounts with POST /api/instagram/add-account and connect to wss://insta-ws.tweetstream.io/ws with tweetstream.instagram.v1.
  • Scale: {"family":"BASE","planType":"ENTERPRISE","monitoredAccountLimit":500,"websocketLimit":2}. With a trial code from the TweetStream team, call POST /api/agent/scale-trial with {"code":"<code>"} instead.