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.
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/sandboxnpx wscat -c wss://sandbox.tweetstream.io/ws -s tweetstream.v1First 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
bashcurl -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"
}
}Verify the code
bash# 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"
}
}Store the key
bash# 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.
planTypeisBASICfor Minimum orELITEfor Pro.billingCycleisMONTHLY,QUARTERLY,SEMIANNUALorYEARLY.- If the account still has a trial, the checkout uses it:
offer.modeistrialandamountDueNowis 0. offer.renewal.amountis the first charge, onrenewal.at.renewal.thenAmountis 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,
nextisreverify_email: request and verify a new code, then send the same checkout again with the new proof. - Make one
Idempotency-Keyper purchase and reuse it on retries, so a retry returns the same checkout. - Give the person
paymentUrland let them pay there. Then pollnext.pollUrl. GET /api/agent/cataloglists every plan, price and limit.
Start a checkout
bashCHECKOUT_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,
nextisreverify_email. To get the link again later, callPOST /api/agent/discord-linkwith the proof. - Give the person the link. Status reads return
wait_for_discorduntil they have joined, thenresume_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.
| Status | Meaning | Next |
|---|---|---|
awaiting_email | A code was emailed. | verify_email: ask the person for the code and their agreement to the Terms. |
payment_required | No plan or trial for this product yet. | create_checkout, open_payment (give the person the link), or resume_checkout. |
community_required | This purchase needs the person in the TweetStream Discord server. | join_discord, issue_discord_link, wait_for_discord or reverify_email. |
processing | A payment is being confirmed. | wait: poll again. |
ready | The product is active. | select_accounts, save_credential or connect. optionalSteps may offer the Discord link. |
closed | The checkout expired or was canceled. | create_checkout with a new key. |
blocked | Support 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
bashuntil curl -s "https://tweetstream.io/api/agent/status?family=BASE" \
-H "Authorization: Bearer $TWEETSTREAM_API_KEY" | grep -q '"status":"ready"'; do
sleep 5
doneReady response
json{
"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.
t and op.Complete exampleA TypeScript or Python consumer that reconnects and merges updates.- If your WebSocket client can't set headers, send the key as the subprotocol
tweetstream.auth.token.<API_KEY>next totweetstream.v1. Never put it in the URL. - From here, the WebSocket quickstart and the complete examples apply as written.
Add accounts
bashcurl -s https://tweetstream.io/api/add-account \
-H "Authorization: Bearer $TWEETSTREAM_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"accounts":["coinbase","binance"]}'Connect
bashnpx 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.
| Code | HTTP | What to do |
|---|---|---|
INVALID_REQUEST | 400 | Fix the body or headers. Unknown fields are rejected. |
INVALID_CODE | 400 | Ask the person to check the code, or request a new one. |
TERMS_NOT_ACCEPTED | 400 | Ask the person to accept the Terms, then send acceptTerms: true. |
INVALID_API_KEY | 401 | Check TWEETSTREAM_API_KEY. A rotated key stops working. |
REVERIFY_EMAIL | 401 | The setup proof is missing or expired. Request and verify a new code. |
ACCOUNT_RESTRICTED | 403 | Stop and tell the person to contact support. |
ORIGIN_REJECTED | 403 | Call from a server or terminal, without a browser Origin header. |
CHECKOUT_NOT_FOUND | 404 | Check checkoutId, or read status by family. |
NOT_ENTITLED | 404 | Finish the checkout first, then poll status until ready. |
PAYMENT_LINK_INVALID | 404 | Start a new checkout to get a new payment link. |
DISCORD_LINK_INVALID | 404 | Issue a new Discord link. |
IDEMPOTENCY_CONFLICT | 409 | This Idempotency-Key was used for a different body. Use a new key. |
CHECKOUT_CONFLICT | 409 | Another checkout for this product is open. Read status and follow it. |
OFFER_CHANGED | 409 | The price or plan changed. Show the person the new offer. |
TRIAL_UNAVAILABLE | 409 | No trial for this account. Start a paid checkout. |
TRIAL_AUTHORIZATION_REQUIRED | 409 | This trial needs a code from the TweetStream team. |
RATE_LIMITED | 429 | Wait retryAfterSeconds, then retry. |
ACTIVATION_BUDGET_EXHAUSTED | 429 | Too many trial starts. Wait retryAfterSeconds. |
TEMPORARILY_UNAVAILABLE | 503 | Retry with backoff and the same Idempotency-Key. |
Keep the key safe
- Store the key in
TWEETSTREAM_API_KEYor a secret store. Don't paste it into chat, commit it, or print it. - Send it only in the
Authorizationheader 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/rotatewith the setup proof, or therotate_api_keyMCP 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
setupProofas a secret too. It expires after 30 minutes.
Rotate the key
bash# 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
bashclaude 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
bashcodex mcp add tweetstream --url https://tweetstream.io/mcp
codex mcp login tweetstreamUltra 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 withPOST /api/agent/credentialsand{"family":"ULTRA"}plus the setup proof, then choose accounts withPOST /api/ultra/accounts. - Instagram Tracking:
{"family":"INSTAGRAM","accountLimit":15}. Add accounts withPOST /api/instagram/add-accountand connect towss://insta-ws.tweetstream.io/wswithtweetstream.instagram.v1. - Scale:
{"family":"BASE","planType":"ENTERPRISE","monitoredAccountLimit":500,"websocketLimit":2}. With a trial code from the TweetStream team, callPOST /api/agent/scale-trialwith{"code":"<code>"}instead.