Quickstart
1 · Check your key
The first call costs nothing, changes nothing, and tells you what your key may do and how much of it you have used. If this returns 200, everything else in this document is available to you.
curl https://api.xsignalfeed.com/v1/account \ -H "Authorization: Bearer xsf_your_key"
import httpx r = httpx.get("https://api.xsignalfeed.com/v1/account", headers={"Authorization": "Bearer xsf_your_key"}) print(r.json())
const r = await fetch("https://api.xsignalfeed.com/v1/account", { headers: { Authorization: "Bearer xsf_your_key" } }); console.log(await r.json());
req, _ := http.NewRequest("GET", "https://api.xsignalfeed.com/v1/account", nil) req.Header.Set("Authorization", "Bearer xsf_your_key") res, err := http.DefaultClient.Do(req)
{
"name": "acme-trading",
"plan": "pro",
"key_id": "3f9a1c2b7d4e",
"scopes": [
"targets:read",
"targets:write",
"stream",
"replay",
"reads",
"webhook",
"keywords:read",
"keywords:write"
],
"event_types": [],
"event_types_all": true,
"limits": {
"max_targets": 200,
"max_conns": 5,
"max_keywords": 20,
"requests_per_minute": 600
},
"usage": {
"targets": 12,
"keywords": 3
}
}2 · Monitor an account
Name the account by handle, without the @. If we have never seen it, we resolve it against X and activate delivery — that takes under a minute and needs nothing from you. The response tells you which of the two happened.
curl -X POST https://api.xsignalfeed.com/v1/monitor/targets \ -H "Authorization: Bearer xsf_your_key" -H "Content-Type: application/json" \ -d '{"screen_name": "elonmusk", "event_types": ["followers.verified.add"]}'
{
"status": "subscribed",
"state": "subscribed",
"id": "44196397",
"screen_name": "elonmusk",
"name": "Elon Musk",
"event_types": [
"followers.verified.add"
],
"event_types_all": false
}
{
"status": "queued",
"state": "queued",
"screen_name": "newaccount",
"user_id": null,
"event_types": [],
"event_types_all": true,
"note": "target will be resolved and placed shortly; poll GET /v1/monitor/targets/{screen_name}"
}An account we already know is subscribed on the spot (201; 200 if you were already subscribed). One we have never seen is 202: check GET /v1/monitor/targets/{target} until state is subscribed, or read a 404 whose details.state is failed and whose details.reason says why: user_not_found, user_suspended or user_protected. If X told us that in the last ten minutes, the POST itself answers with that 404. By default every event type your key allows is delivered; send event_types to receive only some of them, for example only followers.verified.add (how). Adding many at once? Use the batch endpoint.
3 · Open the stream
Connect, and events arrive as single-line JSON. Nothing else is required — no subscribe message, no channels.
import asyncio, json, websockets async def main(): url = "wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key" async with websockets.connect(url) as ws: async for raw in ws: ev = json.loads(raw) if ev["t"].startswith("system."): continue print(ev["t"], ev["target"]["screen_name"], ev["d"].get("text", "")[:60]) asyncio.run(main())
import WebSocket from "ws"; const ws = new WebSocket("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"); ws.on("message", (raw) => { const ev = JSON.parse(raw); if (ev.t.startsWith("system.")) return; console.log(ev.t, ev.target.screen_name, (ev.d.text ?? "").slice(0, 60)); });
c, _, err := websocket.DefaultDialer.Dial("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key", nil) for { _, raw, err := c.ReadMessage() if err != nil { break } var ev Event json.Unmarshal(raw, &ev) if strings.HasPrefix(ev.T, "system.") { continue } log.Println(ev.T, ev.Target.ScreenName) }
4 · Read your first event
The first frame is always system.hello. After that, the real thing:
{
"v": 1,
"t": "tweet.create",
"op": "create",
"id": "tweet:2097879002992070835",
"ts": 1789012345678,
"sid": "1789012346012-0",
"target": {
"id": "1652541",
"screen_name": "Reuters",
"name": "Reuters"
},
"d": {
"tweet_id": "2097879002992070835",
"kind": "tweet",
"text": "Ash-covered shoes and a president in crisis…",
"lang": "en",
"author": {
"id": "1652541",
"screen_name": "Reuters",
"name": "Reuters",
"bio": "Top and breaking news, pictures and videos from Reuters.",
"avatar": "https://pbs.twimg.com/profile_images/1874154135869616128/nJDmubGJ_normal.jpg",
"banner": "https://pbs.twimg.com/profile_banners/1652541/1573687397",
"followers": 25900000,
"following": 1150,
"tweets": 1080000,
"location": "Around the world",
"url": "https://www.reuters.com",
"verified": true,
"verified_type": "Business",
"protected": false,
"created_at": "2007-03-20T17:46:05Z",
"category": null
},
"reply_to": null,
"retweet_of": null,
"quoted": null,
"media": [
"https://pbs.twimg.com/media/….jpg"
],
"media_items": [
{
"type": "photo",
"url": "https://pbs.twimg.com/media/….jpg",
"expanded_url": "https://x.com/Reuters/status/2097879002992070835/photo/1",
"width": 1200,
"height": 675
}
],
"urls": [
"https://reut.rs/…"
],
"counts": {
"reply": 12,
"rt": 40,
"like": 131,
"quote": 3,
"bookmark": 9
},
"views": 20411,
"url": "https://x.com/Reuters/status/2097879002992070835",
"full": true,
"kind_final": true
}
}