---
title: "Agent quickstart"
canonical_url: "https://tweetstream.io/docs/agent-quickstart"
last_updated: 2026-10-29
---

# 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.

Canonical URL: https://tweetstream.io/docs/agent-quickstart

## 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`.

### Read the sandbox details

```bash
curl -s https://tweetstream.io/api/agent/sandbox
```

### Connect to the sandbox

```bash
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.

> Ask the person before you verify
>
> Send `acceptTerms: true` only after the person has read the Terms and said yes. Ask for the code and their agreement in the same message.
>
> - [Terms of Service](https://tweetstream.io/terms)

### 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"
  }
}
```

### 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.

- `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.

### Start a checkout

```bash
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
  }
}
```

### Or: pay by card (run one checkout, not both)

```bash
# 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.

| 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

```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
```

### Ready 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.

- [WebSocket quickstart](https://tweetstream.io/docs/quickstart): Print the first event, then route by `t` and `op`.
- [Complete example](https://tweetstream.io/docs/examples): A 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 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`.

| 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_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.

### 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

```bash
claude mcp add --transport http tweetstream https://tweetstream.io/mcp
# Then run /mcp in Claude Code and sign in.
```

### Cursor (~/.cursor/mcp.json)

```json
{
  "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.

- [OpenAPI 3.1](https://tweetstream.io/docs/openapi.json): Every agent route with its request and response schemas.

- 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.
