Skip to content

How do you download an account's entire tweet history?

Last updated August 24, 2026

user/tweets/complete takes a numeric user_id and a max, paginates inside the request, then returns the posts together rather than one cursor page at a time. It bills $0.0024 against $0.0008 for a standard timeline page, so 800 posts that would take 40 paged reads at $0.032 arrive in a single call for roughly a thirteenth of that.

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).

Three routes, and which one old posts come from

There are three ways to reach an account's back catalogue and they differ in who drives the loop. user/tweets pages a public timeline of original posts and reposts at $0.0008 a page, roughly twenty posts at a time, with your code holding the cursor and deciding when to stop. user/tweets_and_replies pages that same timeline with the account's replies included. user/tweets/complete is the premium route: it walks the history internally and hands everything back at once for $0.0024, whatever the volume turns out to be. If the question is how to get old posts specifically rather than recent ones, the premium route is the one that reaches backwards without dozens of round trips, and it is the only one where a single request can span years.

What the archive call actually does differently

Instead of returning a cursor and making your code drive the walk, this endpoint paginates inside the request and returns a count alongside a tweets array, newest first. There is no next_cursor in the envelope at all, because there is no second page to fetch and no chain to resume. max defaults to 800 and controls how far back the internal walk reaches, so raising it buys history at the cost of wall clock time. The reference is explicit that large values can take tens of seconds, which makes this a job-shaped call rather than something to drop inside a request handler with a short timeout. It also means a failed call is retried whole rather than resumed, so pick a max you are willing to pay for twice.

It wants a user_id, never a handle

The required parameter is user_id, the numeric identifier such as 44196397, and the reference says plainly that it is not the @handle. If all you hold is a handle, resolve it through user/info first, which returns the full user object with id on it for one standard read. Cache that id against the account, because it is stable in a way the handle is not: someone can rename themselves tomorrow and the numeric id will not move. Every later archive run for that account then skips the lookup entirely and goes straight to the premium call. The reverse lookup exists too, user/info_by_id, for when you have the id from an archived row and want the current profile behind it.

Reaching one old window instead of everything

Sometimes the goal is not the whole history but a specific stretch of it, a launch week two years ago or the run-up to one announcement. That is a search job rather than an archive job. Put a from: clause and a date bound into the query parameter on tweet/advanced_search and you pay $0.0008 a page for exactly that window instead of pulling everything newer to reach it. Post ids are monotonically increasing and therefore time-sortable, so since_id: and max_id: bound the same range when you already know the id sitting at each edge. Pick the archive route when completeness is the requirement and the search route when precision is, and note that the search route lets you apply an engagement floor while the archive route does not.

Decide about replies before you archive

The plain timeline route covers original posts and reposts. Only user/tweets_and_replies is documented as also including the replies an account wrote to other handles, and its rows carry in_reply_to_username so you can separate the two kinds afterwards without guessing. That distinction matters more than it sounds: for a heavy replier, the replies are most of the volume, and an archive missing them is not a public record of anything. If a complete picture of public activity is the goal, plan a second pass rather than assuming one pull already holds it, then merge the two sets on the post id. Deciding this before the first call is cheaper than discovering it after you have stored a partial archive.

The fields you get on every row

Each archived row is a standard post object: id as a string, text with URLs already expanded, created_at in X's date format, and an author block holding id, username, name and the verification flags. Engagement counters ride along where the upstream source exposes them, favorite_count, retweet_count, reply_count and view_count. Those counters are snapshots taken when you fetched rather than values frozen at posting time, so an archive pulled today and the same archive pulled next year will show identical text against different numbers. Store the fetch time next to each row, or any comparison you run later is measuring two different moments and reporting the difference as growth. The text field being pre-expanded means outbound link analysis needs no extra resolution pass.

Arithmetic on a genuinely large account

An account sitting on 5,000 posts is roughly 250 cursor pages through the standard timeline route at about twenty a page, which comes to $0.20 and 250 separate round trips your code has to sequence. The premium route charges $0.0024 per call regardless of what a single call returns, and the documented lever for reaching further back is a larger max rather than more calls. Archiving a hundred accounts at that rate is $0.24 in total, which is less than a single cup of coffee for a dataset that would take an afternoon to assemble by hand. The $0.50 sitting in a new account at signup covers more than two hundred archive calls before a card is ever involved, which is enough to validate the whole pipeline.

Concurrency when archiving many accounts

The ceiling is 600 requests a minute per key with 20 concurrent, and a fleet of archive calls is the one job that genuinely uses that concurrency, because each account is independent of every other. Run twenty in flight and the limiting factor becomes the tens of seconds a large max takes upstream, not the request allowance, which you will never come close to exhausting with calls this slow. Since it is a pooled read with no session registration involved, there is no per-account credential to rotate and nothing to keep warm between runs. The worker pool stays as simple as a queue of numeric user ids, a concurrency of twenty, and a retry that repeats the whole call.

Dedupe, re-runs and merging two pulls

Every id arrives as a string precisely so a 19 digit number does not lose its trailing digits to JavaScript precision, and that string is your deduplication key. Sort on created_at rather than on array position when you merge two datasets, because two pulls taken at different times put the same post at different offsets. Overlapping a backfill with an earlier run is normal and safe: duplicate ids collapse cleanly and nothing about the endpoint holds state between calls, so there is no watermark to maintain and no cursor to persist. That is what makes an incremental top-up strategy work with no bookkeeping on the server side at all. Run it monthly, overlap generously, and let the id key absorb the repeats. $0.0008 a call, per our published pricing.

Three routes to an account's posts

EndpointPer callWhat one call returns
user/tweets$0.0008About twenty timeline posts plus a cursor
user/tweets_and_replies$0.0008The same page, with the account's replies included
user/tweets/complete$0.0024Up to max posts, default 800, in one response
user/info$0.0008The user object, which is where the numeric id comes from
tweet/advanced_search$0.0008One page of a from: query bounded to a date window
tweet/detail$0.0008A single post resolved by id, with its counters
The Timelines endpoints let you retrieve Posts from user timelines, mention feeds, and home feeds.
X Developer Platform, Timelines documentation. Source

Questions and answers

How far back can I pull an account's posts?
The archive endpoint fetches up to whatever max you request, defaulting to 800, and the reference states directly that larger values fetch more history and take longer to return. Start with a small max, around forty, to see the shape of the response and confirm your parser handles it, then raise it once the pipeline is stable. Opening with the largest number you can think of just makes the first failure slower and more expensive to diagnose.
How do I get old tweets from a specific year?
Use search rather than the archive route. Put a from: clause together with since: and until: bounds into the query parameter on tweet/advanced_search and page the cursor at $0.0008 a page. That returns exactly the window you asked for instead of everything newer as well, which is usually cheaper when the target is one stretch of history. It also lets you add an engagement floor, which the archive route has no way to express.
Why does it reject the @ handle?
The parameter is user_id and expects the numeric identifier, which stays constant even when someone renames their account. Resolve a handle into that id through user/info once, store it against the account record, and every later archive run skips the lookup entirely. Handles change and get recycled by other people; the numeric id does not, which is exactly why the premium route insists on it rather than accepting the friendlier-looking input.
Does the archive include the account's replies?
Treat that as a separate pass. The plain timeline route covers original posts and reposts, and user/tweets_and_replies is the endpoint documented as also carrying the replies an account sent to other handles. Run both if the goal is a full public record, then merge on the post id, using in_reply_to_username to label which rows came from the reply side. For a heavy replier the two datasets differ by most of the total volume.
Is one big call cheaper than paging?
For a full history, clearly. Eight hundred posts through the standard timeline is roughly forty cursor pages, near $0.032, while a single archive call is $0.0024 regardless of how much comes back. Paging still wins when you only want the newest handful, since one timeline page is $0.0008 and returns immediately instead of taking tens of seconds. Pick by what you need, not by which number looks smaller in isolation.
Why is the post id a string and not a number?
Post ids run to 19 digits, which overflows the precision an ordinary JavaScript number carries, so they are serialised as strings to keep the trailing digits intact. Store and compare them as text throughout your pipeline, including in whatever database column holds them. A silently rounded id is the classic cause of duplicate rows in an archive, and it stays invisible until two records that should be distinct collide on the same key.
Do I need a logged-in X account for this?
No. The archive endpoint is documented as a pooled read, so no session registration is involved and your key alone authorises the call. Sessions matter for write actions and for the routes that read private surfaces, not for pulling a public timeline. That keeps a bulk archive job simple, with no cookie to register, nothing to refresh partway through a long run, and no per-account credential to rotate across a worker pool.
How long does a large archive call take?
The reference warns that larger max values can take tens of seconds, because the endpoint is doing internally the pagination you would otherwise be driving yourself. Give the request a generous client timeout, run it from a worker rather than a web handler, and treat a single archive call as a background job that produces a result. There is no cursor to resume from, so a timeout means repeating the whole call and paying for it again.
Can I archive many accounts at once?
Yes, and it is the job that best fits the allowance. Each account is independent, so twenty concurrent calls stay inside the 20 concurrent and 600 per minute ceilings while overlapping the upstream wait that dominates the wall clock. A hundred accounts at $0.0024 each is $0.24 in total. Since these are pooled reads there is no per-account credential to manage, so the worker pool is just a queue of numeric ids.
What happens if I run the same archive twice?
Nothing breaks. The endpoint holds no state between calls, so a second run simply returns the history again, newest first. Duplicate ids collapse against your existing rows if you key on the id string, which makes an overlapping incremental top-up the safe default rather than something to engineer around. Expect the engagement counters to differ between runs, since those are read at fetch time rather than frozen when the post was published.

Start with $0.50 in free credits

No credit card. Roughly 12,500 tweets to test every endpoint.