A extra in-depth take a look at X’s API: fetching data, linking entities, and fixing under-fetching.
When designing a system’s API, software program program engineers sometimes take into account assorted approaches, similar to REST vs RPC vs GraphQL, or hybrid fashions, to seek out out top-of-the-line match for a particular course of or mission. These approaches define how data flows between the backend and frontend, along with the development of the response data:
- Should all data be packed proper right into a single “batch” and returned in a single response?
- Can the “batch” be configured to include solely the required fields for a particular client (e.g., browser vs. mobile) to stay away from over-fetching?
- What happens if the patron under-fetches data and requires additional backend calls to retrieve missing entities?
- How must parent-child relationships be handled? Should baby entities be embedded inside their guardian, or must normalization be utilized, the place guardian entities solely reference baby entity IDs to reinforce reusability and reduce response measurement?
On this text, we uncover how the X (beforehand Twitter) dwelling timeline API (x.com/dwelling) addresses these challenges, along with:
- Fetching the itemizing of tweets
- Returning hierarchical or linked data (e.g., tweets, prospects, media)
- Sorting and paginating outcomes
- Retrieving tweet particulars
- Liking a tweet
Our focus will possible be on the API design and efficiency, treating the backend as a black area since its implementation is inaccessible.
Exhibiting the exact requests and responses proper right here may very well be cumbersome and arduous to adjust to given that deeply nested and repetitive objects are arduous to be taught. To make it less complicated to see the request/response payload development, I’ve made my attempt to “type out” the home timeline API in TypeScript. So within the case of the request/response examples I’ll use the request and response kinds as an alternative of exact JSON objects. Moreover, don’t forget that the classes are simplified and loads of properties are omitted for brevity.
Likelihood is you’ll uncover every kind in types/x.ts file or on the bottom of this textual content inside the “Appendix: All types at one place” half.
All footage, besides othewise well-known, are by the author.
Fetching the itemizing of tweets for the home timeline begins with the POST
request to the subsequent endpoint:
POST https://x.com/i/api/graphql/{query-id}/HomeTimeline
Right here’s a simplified request physique type:
type TimelineRequest = {
queryId: string; // 's6ERr1UxkxxBx4YundNsXw'
variables: {
rely: amount; // 20
cursor?: string; // 'DAAACgGBGedb3Vx__9sKAAIZ5g4QENc99AcAAwAAIAIAAA'
seenTweetIds: string[]; // ['1867041249938530657', '1867041249938530659']
};
choices: Choices;
};type Choices = {
articles_preview_enabled: boolean;
view_counts_everywhere_api_enabled: boolean;
// ...
}
Right here’s a simplified response physique type (we’ll dive deeper into the response sub-types beneath):
type TimelineResponse = {
data: {
dwelling: {
home_timeline_urt: {
instructions: (TimelineAddEntries | TimelineTerminateTimeline)[];
responseObjects: {
feedbackActions: TimelineAction[];
};
};
};
};
};type TimelineAddEntries = TimelineCursor ;
type TimelineItem = {
entryId: string; // 'tweet-1867041249938530657'
sortIndex: string; // '1866561576636152411'
content material materials: {
__typename: 'TimelineTimelineItem';
itemContent: TimelineTweet;
feedbackInfo: {
feedbackKeys: ActionKey[]; // ['-1378668161']
};
};
};
type TimelineTweet = {
__typename: 'TimelineTweet';
tweet_results: {
end result: Tweet;
};
};
type TimelineCursor = {
entryId: string; // 'cursor-top-1867041249938530657'
sortIndex: string; // '1866961576813152212'
content material materials: 'Bottom';
;
};
type ActionKey = string;
It’s attention-grabbing to note proper right here, that “getting” the knowledge is accomplished by the use of “POSTing”, which isn’t widespread for the REST-like API nonetheless it’s frequent for a GraphQL-like API. Moreover, the graphql
part of the URL signifies that X is using the GraphQL style for his or her API.
I’m using the phrase “style” proper right here on account of the request physique itself doesn’t seem to be a pure GraphQL query, the place we might describe the required response development, itemizing the entire properties we want to fetch:
# An occasion of a pure GraphQL request development that is *not* getting used inside the X API.
{
tweets {
id
description
created_at
medias {
type
url
# ...
}
author {
id
establish
# ...
}
# ...
}
}
The concept proper right here is that the home timeline API isn’t a pure GraphQL API, nonetheless is a combination of a variety of approaches. Passing the parameters in a POST request like this seems nearer to the “helpful” RPC identify. Nevertheless on the similar time, it seems as if the GraphQL choices may very well be used someplace on the backend behind the HomeTimeline endpoint handler/controller. A mix like this may also be attributable to a legacy code or some form of ongoing migration. Nevertheless as soon as extra, these are merely my speculations.
You might also uncover that the similar TimelineRequest.queryId
is used inside the API URL along with inside the API request physique. This queryId is almost certainly generated on the backend, then it’s going to get embedded inside the main.js
bundle, after which it’s used when fetching the knowledge from the backend. It’s arduous for me to understand how this queryId
is used exactly since X’s backend is a black area in our case. Nevertheless, as soon as extra, the speculation proper right here may very well be that, it may very well be needed for some form of effectivity optimization (re-using some pre-computed query outcomes?), caching (Apollo related?), debugging (be part of logs by queryId?), or monitoring/tracing features.
It’s additionally attention-grabbing to note, that the TimelineResponse
accommodates not a list of tweets, nonetheless considerably a list of instructions, like “add a tweet to the timeline” (see the TimelineAddEntries
type), or “terminate the timeline” (see the TimelineTerminateTimeline
type).
The TimelineAddEntries
instruction itself may also comprise a number of varieties of entities:
- Tweets — see the
TimelineItem
type - Cursors — see the
TimelineCursor
type - Conversations/suggestions/threads — see the
TimelineModule
type
type TimelineResponse = {
data: {
dwelling: {
home_timeline_urt: TimelineTerminateTimeline)[]; // <-- Proper right here
// ...
;
};
};
};type TimelineAddEntries = TimelineCursor ;
That’s attention-grabbing from the extendability standpoint as a result of it permits a larger variety of what could also be rendered inside the dwelling timeline with out tweaking the API an extreme quantity of.
The TimelineRequest.variables.rely
property models what variety of tweets we want to fetch straight (per net web page). The default is 20. Nonetheless, larger than 20 tweets could also be returned inside the TimelineAddEntries.entries
array. As an example, the array may comprise 37 entries for the first net web page load, on account of it consists of tweets (29), pinned tweets (1), promoted tweets (5), and pagination cursors (2). I’m not sure why there are 29 frequent tweets with the requested rely of 20 though.
The TimelineRequest.variables.cursor
is chargeable for the cursor-based pagination.
“Cursor pagination is most regularly used for real-time data on account of frequency new knowledge are added and since when learning data you sometimes see the most recent outcomes first. It eliminates the chance of skipping devices and displaying the similar merchandise larger than as quickly as. In cursor-based pagination, a seamless pointer (or cursor) is used to keep up monitor of the place inside the data set the next devices have to be fetched from.” See the Offset pagination vs Cursor pagination thread for the context.
When fetching the itemizing of tweets for the first time the TimelineRequest.variables.cursor
is empty, since we want to fetch the best tweets from the default (almost certainly pre-computed) itemizing of personalized tweets.
Nonetheless, inside the response, along with the tweet data, the backend moreover returns the cursor entries. Proper right here is the response type hierarchy: TimelineResponse → TimelineAddEntries → TimelineCursor
:
type TimelineResponse = {
data: {
homet: {
home_timeline_urt: TimelineTerminateTimeline)[]; // <-- Proper right here
// ...
;
};
};
};type TimelineAddEntries = TimelineModule)[]; // <-- Proper right here (tweets + cursors)
;
type TimelineCursor = {
entryId: string;
sortIndex: string;
content material materials: 'Bottom';
;
};
Every net web page accommodates the itemizing of tweets along with “excessive” and “bottom” cursors:
After the online web page data is loaded, we are going to go from the current net web page in every directions and fetch each the “earlier/older” tweets using the “bottom” cursor or the “subsequent/newer” tweets using the “excessive” cursor. My assumption is that fetching the “subsequent” tweets using the “excessive” cursor happens in two situations: when the model new tweets have been added whereas the patron continues to be learning the current net web page, or when the patron begins scrolling the feed upwards (and there aren’t any cached entries or if the sooner entries have been deleted for the effectivity causes).
The X’s cursor itself may seem to be this: DAABCgABGemI6Mk__9sKAAIZ6MSYG9fQGwgAAwAAAAIAAA
. In some API designs, the cursor is also a Base64 encoded string that accommodates the id of the ultimate entry inside the itemizing, or the timestamp of the ultimate seen entry. As an example: eyJpZCI6ICIxMjM0NTY3ODkwIn0= --> {"id": "1234567890"}
, after which, this data is used to query the database accordingly. Throughout the case of X API, it appears to be similar to the cursor is being Base64 decoded into some personalized binary sequence which will require some further decoding to get any which means out of it (i.e. by the use of the Protobuf message definitions). Since we have no idea if it’s a .proto
encoding and as well as we have no idea the .proto
message definition we might assume that the backend is conscious of straightforward strategies to query the next batch of tweets primarily based totally on the cursor string.
The TimelineResponse.variables.seenTweetIds
parameter is used to inform the server about which tweets from the in the meanwhile energetic net web page of the infinite scrolling the patron has already seen. This almost certainly helps make certain that the server doesn’t embody duplicate tweets in subsequent pages of outcomes.
One in all many challenges to be solved inside the APIs like dwelling timeline (or Home Feed) is to find out straightforward strategies to return the linked or hierarchical entities (i.e. tweet → client
, tweet → media
, media → author
, and so forth):
- Should we solely return the itemizing of tweets first after which fetch the dependent entities (like client particulars) in a bunch of separate queries on-demand?
- Or must we return all the knowledge straight, rising the time and the size of the first load, nonetheless saving the time for all subsequent calls?
- Do we’ve to normalize the knowledge on this case to chop again the payload measurement (i.e. when the similar client is an author of many tweets and we want to stay away from repeating the patron data time and again in each tweet entity)?
- Or must it’s a mixture of the approaches above?
Let’s see how X handles it.
Earlier inside the TimelineTweet
type the Tweet
sub-type was used. Let’s take a look on the manner it appears to be:
export type TimelineResponse = {
data: {
dwelling: {
home_timeline_urt: TimelineTerminateTimeline)[]; // <-- Proper right here
// ...
;
};
};
};type TimelineAddEntries = TimelineCursor ;
type TimelineItem = {
entryId: string;
sortIndex: string;
content material materials: {
__typename: 'TimelineTimelineItem';
itemContent: TimelineTweet; // <-- Proper right here
// ...
};
};
type TimelineTweet = {
__typename: 'TimelineTweet';
tweet_results: {
end result: Tweet; // <-- Proper right here
};
};
// A Tweet entity
type Tweet = {
__typename: 'Tweet';
core: {
user_results: {
end result: Individual; // <-- Proper right here (a dependent Individual entity)
};
};
legacy: {
full_text: string;
// ...
entities: { // <-- Proper right here (a dependent Media entities)
media: Media[];
hashtags: Hashtag[];
urls: Url[];
user_mentions: UserMention[];
};
};
};
// A Individual entity
type Individual = {
__typename: 'Individual';
id: string; // 'VXNlcjoxNDUxM4ADSG44MTA4NDc4OTc2'
// ...
legacy: {
location: string; // 'San Francisco'
establish: string; // 'John Doe'
// ...
};
};
// A Media entity
type Media = {
// ...
source_user_id_str: string; // '1867041249938530657' <-- Proper right here (the dependant client is being talked about by its ID)
url: string; // 'https://t.co/X78dBgtrsNU'
choices: {
huge: { faces: FaceGeometry[] };
medium: { faces: FaceGeometry[] };
small: { faces: FaceGeometry[] };
orig: { faces: FaceGeometry[] };
};
sizes: {
huge: MediaSize;
medium: MediaSize;
small: MediaSize;
thumb: MediaSize;
};
video_info: VideoInfo[];
};
What’s attention-grabbing proper right here is that loads of the dependent data like tweet → media
and tweet → author
is embedded into the response on the first identify (no subsequent queries).
Moreover, the Individual
and Media
connections with Tweet
entities aren’t normalized (if two tweets have the similar author, their data will possible be repeated in each tweet object). Nonetheless it seems as if it have to be okay, since inside the scope of the home timeline for a particular client the tweets will possible be authored by many authors and repetitions are potential nonetheless sparse.
My assumption was that the UserTweets
API (that we don’t cowl proper right here), which is chargeable for fetching the tweets of one specific client will cope with it in one other manner, nonetheless, apparently, it’s not the case. The UserTweets
returns the itemizing of tweets of the similar client and embeds the similar client data time and again for each tweet. It’s attention-grabbing. Maybe the simplicity of the strategy beats some data measurement overhead (maybe client data is taken into consideration pretty small in measurement). I’m not sure.
One different assertion regarding the entities’ relationship is that the Media
entity moreover has a hyperlink to the Individual
(the author). Nonetheless it does it not by the use of direct entity embedding as a result of the Tweet
entity does, nonetheless considerably it hyperlinks by the use of the Media.source_user_id_str
property.
The “suggestions” (which might be moreover the “tweets” by their nature) for each “tweet” inside the dwelling timeline aren’t fetched the least bit. To see the tweet thread the patron ought to click on on on the tweet to see its detailed view. The tweet thread will possible be fetched by calling the TweetDetail
endpoint (additional about it inside the “Tweet component net web page” half beneath).
One different entity that each Tweet
has is FeedbackActions
(i.e. “Recommend a lot much less sometimes” or “See fewer”). The easiest way the FeedbackActions
are saved inside the response object is completely completely different from the best way wherein the Individual
and Media
objects are saved. Whereas the Individual
and Media
entities are part of the Tweet
, the FeedbackActions
are saved individually in TimelineItem.content material materials.feedbackInfo.feedbackKeys
array and are linked by the use of the ActionKey
. That was a slight shock for me as a result of it might not look like the case that any movement is re-usable. It appears to be like one movement is used for one specific tweet solely. So it seems as if the FeedbackActions
could very effectively be embedded into each tweet within the similar method as Media
entities. Nevertheless I may very well be missing some hidden complexity proper right here (like the reality that each movement can have youngsters actions).
Further particulars regarding the actions are inside the “Tweet actions” half beneath.
The sorting order of the timeline entries is printed by the backend by the use of the sortIndex
properties:
type TimelineCursor = {
entryId: string;
sortIndex: string; // '1866961576813152212' <-- Proper right here
content material materials: 'Bottom';
;
};type TimelineItem = {
entryId: string;
sortIndex: string; // '1866561576636152411' <-- Proper right here
content material materials: {
__typename: 'TimelineTimelineItem';
itemContent: TimelineTweet;
feedbackInfo: {
feedbackKeys: ActionKey[];
};
};
};
type TimelineModule = {
entryId: string;
sortIndex: string; // '73343543020642838441' <-- Proper right here
content material materials: {
__typename: 'TimelineTimelineModule';
devices: {
entryId: string,
merchandise: TimelineTweet,
}[],
displayType: 'VerticalConversation',
};
};
The sortIndex
itself may look one factor like this '1867231621095096312'
. It potential corresponds on to or is derived from a Snowflake ID.
Actually loads of the IDs you see inside the response (tweet IDs) adjust to the “Snowflake ID” convention and seem to be
'1867231621095096312'
.
If that’s used to sort entities like tweets, the system leverages the inherent chronological sorting of Snowflake IDs. Tweets or objects with a greater sortIndex value (a extra moderen timestamp) appear larger inside the feed, whereas these with lower values (an older timestamp) appear lower inside the feed.
Proper right here’s the step-by-step decoding of the Snowflake ID (in our case the sortIndex
) 1867231621095096312
:
- Extract the Timestamp:
- The timestamp is derived by right-shifting the Snowflake ID by 22 bits (to remove the lower 22 bits for data coronary heart, worker ID, and sequence):
1867231621095096312 → 445182709954
- Add Twitter’s Epoch:
- Together with Twitter’s personalized epoch (1288834974657) to this timestamp presents the UNIX timestamp in milliseconds:
445182709954 + 1288834974657 → 1734017684611ms
- Convert to a human-readable date:
- Altering the UNIX timestamp to a UTC datetime presents:
1734017684611ms → 2024-12-12 15:34:44.611 (UTC)
So we are going to assume proper right here that the tweets inside the dwelling timeline are sorted chronologically.
Each tweet has an “Actions” menu.
The actions for each tweet are coming from the backend in a TimelineItem.content material materials.feedbackInfo.feedbackKeys
array and are linked with the tweets by the use of the ActionKey
:
type TimelineResponse = {
data: {
dwelling: {
home_timeline_urt: {
instructions: (TimelineAddEntries | TimelineTerminateTimeline)[];
responseObjects: {
feedbackActions: TimelineAction[]; // <-- Proper right here
};
};
};
};
};type TimelineItem = {
entryId: string;
sortIndex: string;
content material materials: {
__typename: 'TimelineTimelineItem';
itemContent: TimelineTweet;
feedbackInfo: {
feedbackKeys: ActionKey[]; // ['-1378668161'] <-- Proper right here
};
};
};
type TimelineAction = {
key: ActionKey; // '-609233128'
value: ...
affirmation: string; // 'Thanks. You’ll see fewer posts like this.'
childKeys: ActionKey[]; // ['1192182653', '-1427553257'], i.e. NotInterested -> SeeFewer
feedbackUrl: string; // '/2/timeline/strategies.json?feedback_type=NotRelevant&action_metadata=SRwW6oXZadPHiOczBBaAwPanEwEpercent3D'
hasUndoAction: boolean;
icon: string; // 'Frown'
;
};
It’s attention-grabbing proper right here that this flat array of actions is unquestionably a tree (or a graph? I didn’t study), since each movement might have baby actions (see the TimelineAction.value.childKeys
array). That is good, for example, when after the patron clicks on the “Don’t Like” movement, the follow-up may very well be to point the “This submit isn’t associated” movement, as a method of explaining why the patron wouldn’t similar to the tweet.
As quickly as the patron want to see the tweet component net web page (i.e. to see the thread of suggestions/tweets), the patron clicks on the tweet and the GET
request to the subsequent endpoint is carried out:
GET https://x.com/i/api/graphql/{query-id}/TweetDetail?variables={"focalTweetId":"1867231621095096312","referrer":"dwelling","controller_data":"DACABBSQ","rankingMode":"Relevance","includePromotedContent":true,"withCommunity":true}&choices={"articles_preview_enabled":true}
I was curious proper right here why the itemizing of tweets is being fetched by the use of the POST
identify, nonetheless each tweet component is fetched by the use of the GET
identify. Seems inconsistent. Notably retaining in ideas that comparable query parameters like query-id
, choices
, and others this time are handed inside the URL and by no means inside the request physique. The response format can be comparable and is re-using the classes from the itemizing identify. I’m not sure why is that. Nevertheless as soon as extra, I’m constructive I may very well be may very well be missing some background complexity proper right here.
Listed below are the simplified response physique kinds:
type TweetDetailResponse = {
data: {
threaded_conversation_with_injections_v2: TimelineTerminateTimeline)[],
,
},
}type TimelineAddEntries = TimelineCursor ;
type TimelineTerminateTimeline = {
type: 'TimelineTerminateTimeline',
path: 'Excessive',
}
type TimelineModule = {
entryId: string; // 'conversationthread-58668734545929871193'
sortIndex: string; // '1867231621095096312'
content material materials: {
__typename: 'TimelineTimelineModule';
devices: {
entryId: string, // 'conversationthread-1866876425669871193-tweet-1866876038930951193'
merchandise: TimelineTweet,
}[], // Suggestions to the tweets are moreover tweets
displayType: 'VerticalConversation',
};
};
The response is pretty comparable (in its kinds) to the itemizing response, so we acquired’t for too prolonged proper right here.
One attention-grabbing nuance is that the “suggestions” (or conversations) of each tweet are actually completely different tweets (see the TimelineModule
type). So the tweet thread appears to be much like the home timeline feed by exhibiting the itemizing of TimelineTweet
entries. This appears to be elegant. occasion of a typical and re-usable methodology to the API design.
When a client likes the tweet, the POST
request to the subsequent endpoint is being carried out:
POST https://x.com/i/api/graphql/{query-id}/FavoriteTweet
Proper right here is the request physique kinds:
type FavoriteTweetRequest = {
variables: {
tweet_id: string; // '1867041249938530657'
};
queryId: string; // 'lI07N61twFgted2EgXILM7A'
};
Proper right here is the response physique kinds:
type FavoriteTweetResponse = {
data: {
favorite_tweet: 'Completed',
}
}
Appears straightforward and as well as resembles the RPC-like methodology to the API design.
We have now now touched on some basic components of the home timeline API design by looking at X’s API occasion. I made some assumptions alongside the best way wherein to top-of-the-line of my knowledge. I take into account some points I’d want interpreted incorrectly and I’d want missed some difficult nuances. Nevertheless even with that in ideas, I hope you purchased some useful insights from this high-level overview, one factor that you could possibly presumably apply in your subsequent API Design session.
Initially, I had a plan to endure comparable top-tech web pages to get some insights from Fb, Reddit, YouTube, and others and to assemble battle-tested best practices and choices. I’m not sure if I’ll uncover the time to do that. Will see. Nonetheless it might very effectively be an attention-grabbing prepare.
For the reference, I’m together with every kind in a single go proper right here. You might also uncover every kind in types/x.ts file.
/**
* This file accommodates the simplified kinds for X's (Twitter's) dwelling timeline API.
*
* These kinds are created for exploratory features, to see the current implementation
* of the X's API, to see how they fetch Home Feed, how they do a pagination and sorting,
* and the best way they transfer the hierarchical entities (posts, media, client info, and so forth).
*
* Many properties and varieties are omitted for simplicity.
*/// POST https://x.com/i/api/graphql/{query-id}/HomeTimeline
export type TimelineRequest = {
queryId: string; // 's6ERr1UxkxxBx4YundNsXw'
variables: {
rely: amount; // 20
cursor?: string; // 'DAAACgGBGedb3Vx__9sKAAIZ5g4QENc99AcAAwAAIAIAAA'
seenTweetIds: string[]; // ['1867041249938530657', '1867041249938530658']
};
choices: Choices;
};
// POST https://x.com/i/api/graphql/{query-id}/HomeTimeline
export type TimelineResponse = {
data: {
dwelling: {
home_timeline_urt: {
instructions: (TimelineAddEntries | TimelineTerminateTimeline)[];
responseObjects: {
feedbackActions: TimelineAction[];
};
};
};
};
};
// POST https://x.com/i/api/graphql/{query-id}/FavoriteTweet
export type FavoriteTweetRequest = {
variables: {
tweet_id: string; // '1867041249938530657'
};
queryId: string; // 'lI07N6OtwFgted2EgXILM7A'
};
// POST https://x.com/i/api/graphql/{query-id}/FavoriteTweet
export type FavoriteTweetResponse = {
data: {
favorite_tweet: 'Completed',
}
}
// GET https://x.com/i/api/graphql/{query-id}/TweetDetail?variables={"focalTweetId":"1867041249938530657","referrer":"dwelling","controller_data":"DACABBSQ","rankingMode":"Relevance","includePromotedContent":true,"withCommunity":true}&choices={"articles_preview_enabled":true}
export type TweetDetailResponse = {
data: {
threaded_conversation_with_injections_v2: TimelineTerminateTimeline)[],
,
},
}
type Choices = {
articles_preview_enabled: boolean;
view_counts_everywhere_api_enabled: boolean;
// ...
}
type TimelineAction = {
key: ActionKey; // '-609233128'
value: ...
affirmation: string; // 'Thanks. You’ll see fewer posts like this.'
childKeys: ActionKey[]; // ['1192182653', '-1427553257'], i.e. NotInterested -> SeeFewer
feedbackUrl: string; // '/2/timeline/strategies.json?feedback_type=NotRelevant&action_metadata=SRwW6oXZadPHiOczBBaAwPanEwEpercent3D'
hasUndoAction: boolean;
icon: string; // 'Frown'
;
};
type TimelineAddEntries = TimelineCursor ;
type TimelineTerminateTimeline = {
type: 'TimelineTerminateTimeline',
path: 'Excessive',
}
type TimelineCursor = {
entryId: string; // 'cursor-top-1867041249938530657'
sortIndex: string; // '1867231621095096312'
content material materials: 'Bottom';
;
};
type TimelineItem = {
entryId: string; // 'tweet-1867041249938530657'
sortIndex: string; // '1867231621095096312'
content material materials: {
__typename: 'TimelineTimelineItem';
itemContent: TimelineTweet;
feedbackInfo: {
feedbackKeys: ActionKey[]; // ['-1378668161']
};
};
};
type TimelineModule = {
entryId: string; // 'conversationthread-1867041249938530657'
sortIndex: string; // '1867231621095096312'
content material materials: {
__typename: 'TimelineTimelineModule';
devices: {
entryId: string, // 'conversationthread-1867041249938530657-tweet-1867041249938530657'
merchandise: TimelineTweet,
}[], // Suggestions to the tweets are moreover tweets
displayType: 'VerticalConversation',
};
};
type TimelineTweet = {
__typename: 'TimelineTweet';
tweet_results: {
end result: Tweet;
};
};
type Tweet = {
__typename: 'Tweet';
core: {
user_results: {
end result: Individual;
};
};
views: {
rely: string; // '13763'
};
legacy: {
bookmark_count: amount; // 358
created_at: string; // 'Tue Dec 10 17:41:28 +0000 2024'
conversation_id_str: string; // '1867041249938530657'
display_text_range: amount[]; // [0, 58]
favorite_count: amount; // 151
full_text: string; // "How I'd promote my startup, if I had 0 followers (Half 1)"
lang: string; // 'en'
quote_count: amount;
reply_count: amount;
retweet_count: amount;
user_id_str: string; // '1867041249938530657'
id_str: string; // '1867041249938530657'
entities: {
media: Media[];
hashtags: Hashtag[];
urls: Url[];
user_mentions: UserMention[];
};
};
};
type Individual = {
__typename: 'Individual';
id: string; // 'VXNlcjoxNDUxM4ADSG44MTA4NDc4OTc2'
rest_id: string; // '1867041249938530657'
is_blue_verified: boolean;
profile_image_shape: 'Circle'; // ...
legacy: {
following: boolean;
created_at: string; // 'Thu Oct 21 09:30:37 +0000 2021'
description: string; // 'I help startup founders double their MRR with outside-the-box promoting and advertising cheat sheets'
favourites_count: amount; // 22195
followers_count: amount; // 25658
friends_count: amount;
location: string; // 'San Francisco'
media_count: amount;
establish: string; // 'John Doe'
profile_banner_url: string; // 'https://pbs.twimg.com/profile_banners/4863509452891265813/4863509'
profile_image_url_https: string; // 'https://pbs.twimg.com/profile_images/4863509452891265813/4863509_normal.jpg'
screen_name: string; // 'johndoe'
url: string; // 'https://t.co/dgTEddFGDd'
verified: boolean;
};
};
type Media = {
display_url: string; // 'pic.x.com/X7823zS3sNU'
expanded_url: string; // 'https://x.com/johndoe/standing/1867041249938530657/video/1'
ext_alt_text: string; // 'Image of two bridges.'
id_str: string; // '1867041249938530657'
indices: amount[]; // [93, 116]
media_key: string; // '13_2866509231399826944'
media_url_https: string; // 'https://pbs.twimg.com/profile_images/1867041249938530657/4863509_normal.jpg'
source_status_id_str: string; // '1867041249938530657'
source_user_id_str: string; // '1867041249938530657'
type: string; // 'video'
url: string; // 'https://t.co/X78dBgtrsNU'
choices: {
huge: { faces: FaceGeometry[] };
medium: { faces: FaceGeometry[] };
small: { faces: FaceGeometry[] };
orig: { faces: FaceGeometry[] };
};
sizes: {
huge: MediaSize;
medium: MediaSize;
small: MediaSize;
thumb: MediaSize;
};
video_info: VideoInfo[];
};
type UserMention = {
id_str: string; // '98008038'
establish: string; // 'Yann LeCun'
screen_name: string; // 'ylecun'
indices: amount[]; // [115, 122]
};
type Hashtag = {
indices: amount[]; // [257, 263]
textual content material: string;
};
type Url = {
display_url: string; // 'google.com'
expanded_url: string; // 'http://google.com'
url: string; // 'https://t.co/nZh3aF0Aw6'
indices: amount[]; // [102, 125]
};
type VideoInfo = {
aspect_ratio: amount[]; // [427, 240]
duration_millis: amount; // 20000
variants: ...
url: string; // 'https://video.twimg.com/amplify_video/18665094345456w6944/pl/-ItQau_LRWedR-W7.m3u8?tag=14'
;
};
type FaceGeometry = { x: amount; y: amount; h: amount; w: amount };
type MediaSize="crop" ;
type ActionKey = string;