Events
Envelope
Every event has the same envelope; d carries the type-specific payload described below.
| Field | Type | Meaning |
|---|---|---|
v | int | Envelope version. 1 today |
t | string | Event type, from the table below |
op | create | update | delete | create, update (enrichment of the same id) or delete |
id | string | Stable identity. Deduplicate on this |
ts | int (ms) | When it happened on X, ms since the epoch. X does not say when a profile changed, a follow or unfollow happened, a verified follower arrived or a post was deleted: for those events it is when we detected it |
sid | string | Stream position for replay; present on the stream and in history |
target.id | string | Numeric X id of the monitored account (for search.match, of the post's author) |
target.screen_name | string | Handle of the monitored account |
target.name | string | The account's display name |
d | object | Type-specific payload |
Event types
| Type | Category | Meaning |
|---|---|---|
tweet.create | Posts | The account posted something new |
tweet.reply | Posts | It replied to someone; reply_to carries the post being replied to, with its text and media |
tweet.retweet | Posts | It reposted someone; retweet_of carries the original post in full |
tweet.quote | Posts | It quoted someone; quoted carries the quoted post in full |
tweet.edit | Posts | It edited a post; the event carries the new version, and edit_of the version it replaces |
tweet.delete | Posts | A post the account had published is gone |
profile.name | Profile | Display name changed |
profile.screen_name | Profile | Handle changed |
profile.bio | Profile | Bio changed |
profile.location | Profile | Location changed |
profile.url | Profile | Profile link changed |
profile.avatar | Profile | Avatar changed; old and new are image URLs |
profile.banner | Profile | Banner changed; old and new are image URLs |
profile.pinned | Profile | Pinned post changed; tweet and old_tweet carry the posts themselves |
profile.verified | Profile | Verification badge appeared or disappeared |
follow.add | Follow graph | The account started following someone; user is that account's full profile |
follow.remove | Follow graph | The account stopped following someone |
followers.verified.add | Follow graph | A verified account started following it; user carries bio, counts, location, join date |
account.status | Account | The account became protected, suspended or unavailable, or active again |
search.match | Search | Anyone posted something matching one of your keywords; keyword says which, author is their full profile |
X provides no timestamp for "started following" or "gained a follower", so for those events ts is the moment we detected the change.
Payloads
Five payload shapes cover all eighteen types.
Tweet payload
The d of tweet.create, tweet.reply, tweet.retweet and tweet.quote, and the base of search.match.
| Field | Type | Meaning |
|---|---|---|
tweet_id | string | The post's id, as a string |
kind | tweet | reply | retweet | quote | What kind of post it is |
text | string | Full text (long posts included) |
lang | string | null | Language X detected |
author | User | The account that posted it, as a full User — the same object as the author of a search.match: handle, name, bio, avatar, banner, counts, verification, location, link and join date. On a thin first frame it is the account as last read; the update carries it as read with the post |
reply_to | Brief+ | null | For replies: the post being replied to — a Brief plus screen_name, tweet_id and user_id of its author |
retweet_of | Brief | null | For reposts: the original post |
quoted | Brief | null | For quotes: the quoted post |
media | string[] | Plain image URLs, one per media item |
media_items | Media[] | Every media item with type, video variants and dimensions |
urls | string[] | Expanded links in the post |
article | Article | null | For posts that link an X Article: the article |
counts | {reply, rt, like, quote, bookmark} | Reply, repost, like, quote and bookmark counts at the time we read it |
views | int | null | View count, when X exposes it |
url | string | Canonical link to the post |
full | bool | false on a first, thin create frame; true once the event carries the whole post |
kind_final | bool | true when kind is settled. false only on a thin first frame whose kind is tweet: the post may still turn out to be a quote, and the update on the same id settles it |
kind_was | tweet | null (update only) | Only on the update that settles a post's kind, and only when the first frame said otherwise: the kind that frame had (tweet for a post that turned out to be a quote) |
edit_of | Brief | null | On tweet.edit: the version this one replaces (its id, and its text and author when we had it) |
{
"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
}
}A first create frame is deliberately thin: it carries the id, the author (author, the account as last read) and the text, which may be shortened, so it can leave the moment the post is seen; url already points at the post. At that moment a quote cannot always be told from an original post, so such a frame says tweet.create with kind_final: false. The update that follows on the same id carries the full payload above and the settled type: tweet.quote with kind_was: "tweet" if it was a quote.
{
"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…",
"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
},
"url": "https://x.com/Reuters/status/2097879002992070835",
"full": false,
"kind_final": false
}
}Delete payload
The d of tweet.delete. Only ever sent for a post we had seen and that is now gone. Its ts is when the deletion was detected; the post's own time is in its earlier create frame.
| Field | Type | Meaning |
|---|---|---|
tweet_id | string | The deleted post's id |
url | string | Where it used to be |
{
"v": 1,
"t": "tweet.delete",
"op": "delete",
"id": "tweet:2097879002992070835:delete",
"ts": 1789012345678,
"sid": "…",
"target": {
"id": "1652541",
"screen_name": "Reuters",
"name": "Reuters"
},
"d": {
"tweet_id": "2097879002992070835",
"url": "https://x.com/Reuters/status/2097879002992070835"
}
}Profile change payload
The d of every profile.* event. op is update for these.
| Field | Type | Meaning |
|---|---|---|
field | string | Which profile field changed; the same word as after profile. in the type |
old | any | The previous value |
new | any | The new value |
tweet | Brief (pinned only) | For profile.pinned: the newly pinned post |
old_tweet | Brief (pinned only) | For profile.pinned: the previously pinned post, if we had it |
{
"v": 1,
"t": "profile.bio",
"op": "update",
"id": "profile:1652541:bio:9c1d2e3f4a5b",
"ts": 1789012400000,
"sid": "…",
"target": {
"id": "1652541",
"screen_name": "Reuters",
"name": "Reuters"
},
"d": {
"field": "bio",
"old": "Top news and analysis from around the world.",
"new": "Top news, analysis and video from around the world."
}
}Follow payload
The d of follow.add, follow.remove and followers.verified.add.
| Field | Type | Meaning |
|---|---|---|
user | User | The other account, as a full User |
{
"v": 1,
"t": "follow.add",
"op": "create",
"id": "follow:44196397:12:add:496948",
"ts": 1789012400000,
"sid": "…",
"target": {
"id": "44196397",
"screen_name": "elonmusk",
"name": "Elon Musk"
},
"d": {
"user": {
"id": "12",
"screen_name": "jack",
"name": "jack",
"bio": "…",
"avatar": "https://pbs.twimg.com/profile_images/…",
"banner": null,
"followers": 6400000,
"following": 4200,
"tweets": 29000,
"location": "",
"url": null,
"verified": true,
"verified_type": "blue",
"protected": false,
"created_at": "2006-03-21T20:50:14Z",
"category": null
}
}
}Account status payload
The d of account.status. A status is one of active, protected (posts visible to approved followers only), suspended (by X) or unavailable (deactivated, deleted or otherwise not visible). ts is when we detected the change.
| Field | Type | Meaning |
|---|---|---|
old | active | protected | suspended | unavailable | The status before |
new | active | protected | suspended | unavailable | The status now |
reason | string | null | X's own wording, when it gives one (for example Suspended) |
{
"v": 1,
"t": "account.status",
"op": "update",
"id": "account:9000002:status:active>suspended:497010",
"ts": 1789236000000,
"sid": "…",
"target": {
"id": "9000002",
"screen_name": "some_account",
"name": "Some Account"
},
"d": {
"old": "active",
"new": "suspended",
"reason": "Suspended"
}
}Search match payload
The d of search.match: a Tweet payload plus three fields about the match. The envelope's target is the post's author, who need not be one of your targets.
| Field | Type | Meaning |
|---|---|---|
keyword.id | int | Id of the keyword that matched |
keyword.query | string | Its query |
keyword.label | string | null | Its label, if you set one |
author | User | The author's full profile |
author_is_target | bool | true if the author is also an account monitored on this system |
… | TweetPayload | Every field of the Tweet payload |
{
"v": 1,
"t": "search.match",
"op": "create",
"id": "search:12:2097900000000000000",
"ts": 1789012500000,
"sid": "…",
"target": {
"id": "9000001",
"screen_name": "some_trader",
"name": "Some Trader"
},
"d": {
"tweet_id": "2097900000000000000",
"kind": "tweet",
"text": "$SOL breaking out…",
"lang": "en",
"author": {
"id": "9000001",
"screen_name": "some_trader",
"name": "Some Trader",
"bio": "…",
"followers": 1200,
"verified": false,
"…": "…"
},
"keyword": {
"id": 12,
"query": "($SOL OR \"Solana\") -filter:retweets",
"label": "solana"
},
"author_is_target": false,
"media": [],
"media_items": [],
"urls": [],
"url": "https://x.com/some_trader/status/2097900000000000000",
"full": true
}
}