Twitter User Media API | Get Photos and Videos
Fetch the media tweets posted by a user, cursor-paginated. Returns the tweet objects from the account's Media tab, with media URLs included under extended_entities.media. The tab occasionally surfaces a link-or-card tweet with no attachment, so guard on extended_entities before reading it. Cost: $0.0008 per call.
User Media pages through a profile's media tweets, returning the tweet objects that carry attached photos or video with their media URLs included under extended_entities. Results are cursor-paginated and this endpoint honours count, so a page can return well over 20 tweets. Use it to build an image or video archive of an account at $0.0008 per page.
Authorization
bearerAuth Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.
In: header
Query Parameters
Handle without the leading @.
Numeric user ID; provide either username or user_id.
Pagination cursor from a previous response.
Max items to return for this page. Default 20; clamped to 1-100.
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/media?username=nasa&user_id=44196397&count=20&fields=id%2Ctext%2Ccreated_at%2Cauthor.username&compact=1&paid_promotion=only"{ "tweets": [ { "id": "1759987654321000111", "text": "A new image of the Pillars of Creation.", "extended_entities": { "media": [ { "type": "photo", "media_url_https": "https://pbs.twimg.com/media/abc.jpg", "url": "https://t.co/abc123", "ext_alt_text": "The Pillars of Creation imaged by JWST." } ] }, "author": { "id": "11348282", "username": "nasa" } } ], "next_cursor": "DAABCgABF..."}{ "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.", "top_up_url": "https://www.twitterapis.com/dashboard/buy-credits?utm_source=api&utm_medium=402"}{ "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 |
Each page is one $0.0008 read, so a media-heavy account costs one call per page. Stop paging once you have the media you need.
When to use
Use it to collect an account's visual posts, for example archiving every photo NASA has tweeted.
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 Tweets: Fetch a user's public timeline (original tweets, retweets, and their own replies).
- User Tweets and Replies: Fetch a user's timeline including their replies (same upstream timeline as User Tweets).
- User Info by Username: Fetch a full profile by @handle.
FAQ
Does User Media include video URLs?
Yes. Each media tweet carries its attachments under extended_entities.media, covering both photo and video with their URLs.
Does it return tweets without media?
Almost always no, but not guaranteed. The page mirrors the account's Media tab, which occasionally includes a tweet whose only attachment is a link or card, and that tweet arrives with no extended_entities. Check for extended_entities.media before reading it rather than assuming every row has one.
How do I page through all media?
Send next_cursor as the cursor parameter and repeat until the tweets array comes back empty.
User About GET
Read a profile's full About object: identity, bio, location, linked website, avatar, verification and identity-verification flags, professional account type and categories, follower and following counts, the account creation date, and X's "About this account" transparency panel (account country, how the account was created, and username-change history). Cost: $0.0008 per call.
User Tweets GET
Page through a user's public timeline, cursor-paginated at roughly 20 tweets per call. The page mirrors the account's timeline as X returns it, so it contains original tweets, retweets, and the account's own replies. Each tweet carries is_retweet and is_reply, so filter client-side if you want original posts only. Cost: $0.0008 per call.