How do you get the tweets a user has liked?
Last updated August 24, 2026
The likes timeline lives behind user/likes, a Session Read that needs a registered session and takes user_id, not a handle. It returns count, next_cursor, and tweets, newest first, 20 per page by default. Its sibling user/media pages the same profile's photo and video posts by username. Both bill $0.0008 a page.
Every rate here is the pricing TwitterAPIs publishes. The billed rate is $0.0008 per call; $0.04 per 1,000 tweets is derived from it at a full 20-tweet page, which is the default page size rather than a guaranteed yield (source: twitterapis.com/pricing).
The likes route and what it takes
GET user/likes takes user_id, the numeric identifier of the account whose favourites you want, as its one required argument. count is optional and defaults to 20. cursor is optional and omitted on the opening request. Nothing else exists on this route: there is no date range, no keyword filter and no way to ask only for likes on a particular author, so any narrowing happens after the page lands. The identifier is a string in the request for the usual reason, that X identifiers exceed the range JSON represents exactly, and the documented example uses 44196397. Auth is the same bearer header every route here uses, and the session cookies sit alongside it rather than replacing it: the API key decides who is billed, the registered session decides whose X account the read runs as.
What one page of favourites contains
Three fields come back: count, the number of tweets in this page; next_cursor, the token to pass forward; and tweets, the liked posts themselves. Those are full tweet objects rather than references, so each one carries id as a string, text with URLs expanded, created_at, an author object, and the engagement counters favorite_count, retweet_count, reply_count and view_count. The practical consequence is that a favourites pull is also an author list and an engagement dataset in one pass, with no follow-up lookup needed to find out who wrote the things somebody liked. text arrives with its URLs already expanded, and view_count is populated only when the upstream source exposes it, so treat that one field as optional rather than assuming a number is always there.
This route runs as your own account
user/likes is filed under Session Reads, which is a visibility classification rather than a pricing one. It executes as your own logged in X account instead of a shared pool identity, so the vantage point of the read is whoever you registered. Without a registered session there is no acting identity and the call has nothing to read as. Note that the price is unaffected: this sits at the same standard rate as an ordinary profile lookup, and the session requirement is about what X will show, not about what the request costs. Registering a session and revoking it are both free calls, and the revoke is priced at zero on purpose so an account that has run out of credit can still delete its own stored cookies.
Registering a session and reading its state
Registration is a free POST to the customer session route carrying auth_token and ct0, the two cookies from a logged in x.com browser session, in the JSON body rather than the query string. Optional user_agent and proxy_url fields exist. The response is worth parsing carefully: ok true only confirms the pair was stored, not that it works. The validation field is the one that matters and it has three states. validated means X answered an authenticated probe with a 200. dead means X explicitly rejected the cookies, the only state that proves them bad. unknown means the probe itself failed to resolve, through a timeout or a proxy fault, and must never be treated as invalid. A resolved username comes back too, or null when the identity lookup did not resolve, which is a separate concern from validation.
The identifier trap between the two feeds
These two neighbouring routes disagree about how to name an account. user/likes wants user_id, numeric. user/media wants username, the handle with the @ stripped. Wiring both feeds into one profiling job and passing the same variable to each is the usual first failure, and it produces a 400 rather than wrong data, so it surfaces quickly. Resolve the handle once through a profile lookup, keep both forms on the record, and hand each route the one it asks for. Since identifiers survive renames and handles do not, the numeric form is also the one worth storing long term. Both routes ship with concrete documented examples, 44196397 on the favourites side and the handle nasa on the media side, which is the quickest way to confirm each argument is wired to the route that wants it.
Why a favourites feed says something a timeline does not
What an account posts is written for an audience. What it favourites accumulates without an audience in mind, which is why the two rarely describe the same interests. For research that gap is the whole point: the authors appearing repeatedly in someone's likes map who they actually read, competitor mentions show up there before they show up in public commentary, and the topics are unedited. Because every entry is a full tweet object with its author attached, ranking authors by frequency across a few pages needs no additional requests, just a counter over the array you already have. The author object on every entry carries that writer's id and username, which is what makes a frequency count over a few pages possible with no additional lookups at all.
The media sibling and its attachment block
user/media is the visual counterpart. It takes username and cursor, returns tweets, count and next_cursor, and filters the timeline down to posts carrying attachments. Every tweet in that array includes an extended_entities.media block, and each entry there exposes a type, photo in the documented example, along with a media_url_https address you can fetch directly without any further API call. The example returns a NASA post whose media entry points at a pbs.twimg.com image URL. There is no count parameter on this route, so page size is whatever the feed gives you and cursor is the only control. Entries can carry other media types, so branch on that field rather than assuming every attachment is an image, and note the route returns the same count and next_cursor envelope the favourites feed uses.
Ordering, and running the job again tomorrow
Favourites come back newest first, which is what makes an incremental job cheap. Store the id of the most recent entry from each run, then on the next pass walk forward only until you meet that id and stop, rather than repaging the whole history. Read the count field from the response rather than assuming the size you requested was honoured. next_cursor arriving empty or null means there are no further pages. Keep the raw tweet bodies rather than only the fields you parse today, because a re-pull costs requests while a stored payload does not. The media route offers no equivalent page size control, so an incremental strategy on that side rests entirely on the identifiers you already stored from the previous run.
What a profiling pass costs
Both feeds sit at the flat standard rate, so the bill is page count multiplied out and nothing else. Fifty pages of favourites at the default twenty per page covers roughly a thousand liked posts for four cents. Adding fifty pages of attachments takes the total to a hundred requests, about eight cents. The $0.50 credit issued at signup covers 625 requests with no card, enough for several full profiles. Registering the session and revoking it later are both free, so the only thing you ever pay for here is the pages themselves. Neither route carries a premium rate: both sit at the same standard figure an ordinary profile lookup bills, and the classification separating them is about access rather than price. We bill $0.0008 a call, per our public pricing.
The likes feed against the media feed
| Property | user/likes | user/media |
|---|---|---|
| Identifier accepted | user_id, numeric | username, no leading @ |
| Category | Session Reads | User Reads |
| Registered session required | Yes | No |
| Page size argument | count, default 20 | None documented, cursor only |
| Distinctive payload | tweets, newest first, with engagement counters | extended_entities.media with media_url_https |
| Price per page | $0.0008 | $0.0008 |
The Likes endpoints let you like and unlike Posts, see which users liked a Post, and get Posts liked by a user.
Questions and answers
- Do I pass a handle or an ID to the likes endpoint?
- A numeric user_id. Unlike most profile routes this one does not accept a handle, so resolve the @name to an identifier first and cache it. The media route is the opposite and wants username with the @ stripped. Mixing the two arguments up is the usual first failure when wiring both feeds into one job, though it fails loudly with a 400 rather than returning the wrong account.
- Why does the request need a logged in account?
- The route sits in the Session Reads group, meaning it executes as your registered account rather than a shared identity, so the read has a vantage point. Register the auth_token and ct0 cookies once through the customer session endpoint and later calls apply them automatically. The requirement is about what X will show the caller, not about price, which stays at the standard per page rate.
- How do I register the session?
- POST the auth_token and ct0 cookies from a logged in x.com browser session to the customer session route, in the JSON body rather than the query string. The call is free. Optional user_agent and proxy_url fields let you control how session traffic is sent. As an alternative to registering once, any single call can carry the same pair as the x-auth-token and x-ct0 headers.
- What does a validation state of unknown mean?
- That the probe did not resolve, through a timeout, a rate limit or a proxy fault, and it must never be read as the cookies being bad. Only the dead state means X explicitly rejected them. validated means an authenticated request came back 200. Note that ok true on the registration response confirms storage only, so a stored pair can be expired and still return a 200 there.
- How many liked posts arrive per request?
- Twenty by default, and the count parameter adjusts it. The response repeats the actual number in its own count field, so read that rather than assuming the size you asked for was honoured. Ordering is newest first, which is what lets a recurring job walk forward only until it meets an entry it already stored instead of repaging the whole history.
- What happens if I revoke the session?
- Routes that act as your account return 409 session_required until you register again or start passing credentials per call. Revocation is a free, idempotent POST that returns 200 whether or not anything was stored. It deletes the stored copy of the cookies rather than logging the account out of x.com, so invalidating the cookies themselves means also ending the session from X account settings.
- What can a favourites feed tell me that a timeline cannot?
- Attention rather than presentation. A public timeline is written for an audience, while favourites accumulate quietly and track what genuinely held someone's interest. For research that means the authors and topics recurring in the feed describe an account's actual reading habits, which routinely differs from the subjects it posts about, and competitor interest often appears there long before any public comment does.
- How do I pull only images and video from a profile?
- Use user/media, which filters a timeline down to posts carrying attachments. Each returned tweet includes an extended_entities.media array whose entries expose a type such as photo and a media_url_https address you can fetch directly, with no further API call needed to reach the file. Paging uses the same cursor mechanic, and there is no page size parameter on that route.
- Can one loop serve both feeds?
- Mostly. Both return a cursor to pass forward and both stop when the cursor is empty or the collection runs out, so the paging skeleton is shared. Two things differ and need parameterising: the identifier argument, user_id against username, and the collection key, since likes returns its records under tweets while the media route returns the tweets it filtered plus a count.
- What does a research pass cost?
- It is multiplication, not a subscription. Every page of either feed is one request at the standard rate, so fifty pages of favourites covering about a thousand liked posts is four cents, and adding fifty pages of attachments takes it to eight. The free signup credit absorbs 625 requests, several full profiles worth, and session registration and revocation are both free.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.