User Likes API
Read the Likes tab of the account named by user_id, read through your registered session, most recent first. Returns full tweet objects, cursor-paginated. Returns an empty list when the account hides its likes. Requires a registered session and a numeric user_id. Cost: $0.0008 per call.
GET user/likes returns the tweets a target account has publicly liked, most recent first, in cursor-paginated pages of full Tweet objects. Requires the account's numeric user_id and a registered session. If the account hides its Likes tab, the endpoint returns an empty list rather than an error, at $0.0008 per call.
Authorization
bearerAuth Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.
In: header
Query Parameters
Numeric ID of the account whose likes to read. Resolve a handle with GET /user/info first.
Items per page. Defaults to 20; capped at 100.
Pagination cursor from a previous response's next_cursor.
Comma-separated list of dotted field paths to keep in the response (one parameter, never repeated), applied to every object in the returned lists and to nested objects (for example fields=id,text,author.username; the list's own name may prefix a path, fields=tweets.id). A path prefix that lands on an array applies to each element (media.media_url_https). Pagination and envelope keys (next_cursor, cursor, has_more, count, partial, error, message, reason) are always kept. Unknown paths are ignored; up to 50 paths of up to 6 segments. Projection never changes status codes or billing, and applies to data reads only: account, feedback, session status, monitor (other than deliveries), webhook and stream-compat reads return their bodies unchanged.
Set to 1 (or true) to apply the built-in compact preset: ids, url, text, created_at, lang, the engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. The preset trims what it recognises and leaves alone what it would empty, so it never turns a body into {}. Combine with fields to add paths on top of the preset. Typically cuts a tweet page by 70 to 90 percent for LLM tool callers.
Filter this page's tweets by X's Paid partnership label: only keeps tweets whose is_paid_promotion is true, exclude keeps the rest. Omit it for every tweet. It filters the page X returned and does not fetch more, so a filtered page can hold fewer tweets than asked for, or none, while next_cursor still pages on. The response carries paid_promotion_filter { mode, kept, removed }, and count reflects what is returned. Any other value is a 400. Billing is the same as without it.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/user/likes?user_id=44196397&fields=id%2Ctext%2Ccreated_at%2Cauthor.username&compact=1&paid_promotion=only"{ "count": 1, "next_cursor": "DAACCgAC...", "tweets": [ { "id": "1759123456789012345", "text": "A tweet this account liked.", "created_at": "Tue Feb 20 14:02:11 +0000 2026", "author": { "id": "745273", "username": "naval", "name": "Naval" }, "favorite_count": 4821 } ]}{ "error": "bad_request", "message": "Missing or malformed parameter. Fix the request before retrying."}{ "error": "unauthorized", "message": "The API key is missing, malformed, or revoked. Check the Authorization header."}{ "error": "insufficient_credits", "message": "Your balance is exhausted. Top up your credits to continue."}{ "error": "forbidden", "message": "X refused this resource for the acting account: on a read, the account is not a participant in the DM thread, the target is protected or has blocked it, or the session was attestation-blocked for calling from a datacenter or rotating IP; on a write, the account is not authorized for the action or has no logged-in session. The credentials are still valid, so do not re-register."}{ "error": "not_found", "message": "The resource does not exist, for example a deleted tweet or a private account."}{ "error": "rate_limited", "message": "Too many requests. Back off and retry with exponential backoff."}{ "error": "server_error", "message": "Something failed on our side. Retry with backoff; if it persists, contact support."}Pricing
| Unit | Price |
|---|---|
| Per call | $0.0008 |
| Per 1,000 calls | $0.80 |
| Per 1,000 records (~20 per call) | ~$0.04 |
When to use
Use this when you need the tweets a specific account has liked, not posted, for example building a like-based interest graph or surfacing what content a user engages with. Reach for a different User Reads endpoint (like user tweets) when you need what the account posted rather than what it liked.
Notes
- Needs a registered session for the account you are reading (POST /customer/session). This endpoint does not accept per-call inline credentials: x-auth-token and x-ct0 headers are ignored here. Returns 409 session_required if no session is registered, 401 session_dead when the session has expired.
- Most accounts hide their likes; expect an empty tweets array unless the target made its Likes tab public (or it is your own account).
Pagination
This endpoint is cursor-paginated and returns roughly 20 records per call. Pass the next_cursor from each response back as the cursor parameter to read the next page. Stop when the records array comes back empty: follower-graph endpoints return a non-null cursor even on the final page, so the empty array is the only reliable stop signal. See Pagination for the full loop.
Related endpoints
- User Info by Username: Fetch a full profile by @handle.
- User Info by ID: Fetch a full profile by numeric user ID.
FAQ
Why did I get an empty tweets array with has_more false for a valid user_id?
The account has hidden its Likes tab. Twitter/X lets users disable public visibility of likes, and when that happens the endpoint returns an empty tweets array rather than an error, since there is no way to distinguish 'no likes' from 'likes hidden' at the API level. Check the account's is_liked_tweets_public status via a user info endpoint if you need to tell these cases apart.
How do I page through more than 100 liked tweets?
Set count up to its cap of 100 per request (default is 20), then pass the next_cursor value from the response into the cursor param on your next call. Keep paging while has_more is true; next_cursor is opaque and should not be parsed or constructed manually.
Bookmark Search GET
Search the bookmarks of the account behind your registered session by keyword. Matches against bookmarked tweet text and returns full tweet objects. Cursor-paginated. Requires a registered session. Cost: $0.0008 per call.
Full Tweet History GET
Collect a user's tweet history in bulk, auto-paginating server-side across upstream pages. Unlike GET /user/tweets (one upstream page per call), this drives pagination for you and returns a large batch in one request. Each call is bounded by both `max` and a server-side time budget, so it always returns inside a normal client timeout. When more history remains the response carries a `next_cursor` — pass it back as `cursor` to continue, and repeat until `next_cursor` is null. A public read: no registered session required. Cost: $0.0024 per call.