Skip to main content
API reference

Quick start · X (Twitter)

X (Twitter) WebSocket API quick start

Open one WebSocket with your API key and receive every tweet, reply, quote and retweet from the accounts on your list as JSON. This page gets a consumer running; the API reference holds every type.

From $250/month for 100 accounts · WebSocket included · 150-250ms typical · X Ultimate ~100ms
WebSocket
wss://ws.normal.1322.io/ws/normal
REST base
https://api.1322.io
Auth
X-API-Key header
Updated

What you get

Tweets, replies, quotes and retweets from the X accounts on your list, plus deletes, profile changes, pinned-tweet changes and follows. A tweet arrives in stages that share one tweet.id: tweet.mini.update comes first, and richer updates follow when the tweet has more to add (cards, quoted chains, untruncated text).

Typical delivery is 150-250ms. On X Ultimate, the selected accounts average around 100ms on their own stream.

Price: From $250/month for 100 accounts; WebSocket included. Plans: Starter $250/month (100 accounts), Standard $500/month (250 accounts), Professional $800/month (500 accounts), Ultimate $4,000/month (25 Ultimate + 350 accounts). Pay monthly, or prepay 3 months (10% off), 6 months (15% off) or a year (25% off). See X (Twitter) pricing.

Authentication

  • Copy your API key from the API access page in your dashboard. Each stream has its own key: the normal key for ws.normal, the ultimate key for ws.ultimate. GET /v1/keys lists both on a hybrid plan.
  • Send the key as the X-API-Key header on the WebSocket handshake and on every REST call. Authorization: Bearer <key> works too.
  • Run the client on your server. Your key is private, and the API is not built for browser (CORS) requests.

Connect to the WebSocket

Set these environment variables, then run either consumer from your server:

  • X_API_KEY: your normal (or ultimate) API key
# pip install "websockets>=13"   (Python 3.9+)
import asyncio, json, os
from websockets.asyncio.client import connect

# Normal plans. X Ultimate's selected accounts: wss://ws.ultimate.1322.io/ws/ultimate
WS_URL = "wss://ws.normal.1322.io/ws/normal"
HEADERS = {"X-API-Key": os.environ["X_API_KEY"]}

def handle(event):
    tweet = event.get("tweet") or {}
    author = tweet.get("author") or {}
    text = (tweet.get("body") or {}).get("text") or ""
    # Stages of one tweet share tweet["id"]: merge them instead of posting twice.
    print(event.get("type"), tweet.get("id"), author.get("handle"), text[:80])

async def main():
    backoff = 1
    while True:
        try:
            async with connect(WS_URL, additional_headers=HEADERS) as ws:
                backoff = 1
                async for raw in ws:
                    try:
                        event = json.loads(raw)
                    except ValueError:
                        continue
                    if isinstance(event, dict):
                        handle(event)
        except Exception as exc:
            print("disconnected:", exc)
        await asyncio.sleep(backoff)
        backoff = min(backoff * 2, 30)

asyncio.run(main())
// npm install ws, save as consumer.mjs, run: node consumer.mjs   (Node 18+)
import WebSocket from "ws"

// Normal plans. X Ultimate's selected accounts: wss://ws.ultimate.1322.io/ws/ultimate
const WS_URL = "wss://ws.normal.1322.io/ws/normal"
const API_KEY = process.env.X_API_KEY
if (!API_KEY) throw new Error("Set X_API_KEY")

function handle(event) {
  const tweet = event.tweet ?? {}
  // Stages of one tweet share tweet.id: merge them instead of posting twice.
  console.log(event.type, tweet.id, tweet.author?.handle, (tweet.body?.text ?? "").slice(0, 80))
}

let backoff = 1000

function connect() {
  const ws = new WebSocket(WS_URL, { headers: { "X-API-Key": API_KEY } })
  ws.on("open", () => { backoff = 1000 })
  ws.on("message", (raw) => {
    let event
    try { event = JSON.parse(raw.toString()) } catch { return }
    if (event && typeof event === "object" && !Array.isArray(event)) handle(event)
  })
  ws.on("error", (err) => console.error("socket error:", err.message))
  ws.on("close", () => {
    setTimeout(connect, backoff)
    backoff = Math.min(backoff * 2, 30000)
  })
}

connect()
  • Frames are text, one JSON object each. The server pings; both libraries answer automatically.
  • On X Ultimate, run one consumer per stream, each with its own key, and dedupe on tweet.id across both.
  • Events are not queued while you are disconnected, so reconnect promptly with a capped backoff, as above.

One example frame

Illustrative values for fictional accounts, trimmed to the fields most consumers read first. A real frame carries the full set for your tracked accounts.

{
  "id": "6f1d2c3b-1322-4a5e-9b7c-0d1e2f3a4b5c",
  "type": "tweet.mini.update",
  "source": "1322",
  "tweet": {
    "id": "1978456123456789012",
    "type": "TWEET",
    "created_at": 1792073001000,
    "author": {
      "id": "1500000000000000001",
      "handle": "exampledesk",
      "name": "Example Desk",
      "verified": false,
      "metrics": {
        "following": 210,
        "followers": 48210
      }
    },
    "subtweet": null,
    "reply": null,
    "quoted": null,
    "body": {
      "text": "Q3 numbers are live on the blog.",
      "urls": [],
      "mentions": []
    },
    "media": {
      "images": [],
      "videos": [],
      "thumbnails": []
    }
  }
}
  • tweet.type is TWEET, RETWEET, QUOTE or REPLY; created_at is Unix milliseconds.
  • tweet.update, tweet.update.expanded and tweet.full repeat the same tweet.id with more fields. Keep the longest body.text, union the media and never overwrite a filled field with an empty one.

Manage the list over REST

REST base https://api.1322.io. Each call below sends X-API-Key with your API key.

MethodPathWhat it does
GET/v1/trackedThe accounts on this key's list.
POST/v1/trackedAdd accounts: comma-separated usernames or numeric IDs.
DELETE/v1/trackedRemove accounts, same body.
GET/v1/keysEvery key on your account with its tier and usage (hybrid plans).
GET/v1/resolve/username/exampledeskResolve a username to its numeric X ID.
curl "https://api.1322.io/v1/tracked" \
  -H "X-API-Key: $X_API_KEY"

curl -X POST "https://api.1322.io/v1/tracked" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $X_API_KEY" \
  -d '{ "identifiers": "exampledesk,examplefounder", "type": "username" }'

curl -X DELETE "https://api.1322.io/v1/tracked" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $X_API_KEY" \
  -d '{ "identifiers": "examplefounder", "type": "username" }'

curl "https://api.1322.io/v1/keys" \
  -H "X-API-Key: $X_API_KEY"

curl "https://api.1322.io/v1/resolve/username/exampledesk" \
  -H "X-API-Key: $X_API_KEY"
  • Add and remove take up to 200 identifiers per request; the stream picks up list changes on its own, so there is no need to reconnect.

Full reference

Common questions

Which X WebSocket URL should I use?

Normal plans connect to wss://ws.normal.1322.io/ws/normal. X Ultimate adds wss://ws.ultimate.1322.io/ws/ultimate for its selected accounts; connect to both, each with its own key, and dedupe on tweet.id.

Why do I get several events for one tweet?

Tweets arrive in stages. tweet.mini.update is first; tweet.update, tweet.update.expanded and tweet.full add cards, quoted chains and untruncated text under the same tweet.id. Merge them into one record.

Do I need an X developer account?

No. Your 1322 API key is all you need; you do not need an X developer app or X API credits of your own.

Other quick starts

Related: