Why the X (Twitter) API Returns Old Tweets, and How to Get the Latest Ones
You want an account's latest tweets. You call the endpoint that sounds right, the one for a user's tweets, and you get back a hundred posts. The newest is a year old. The oldest is from 2016. They're not even in order.
It's a fair thing to be confused by, because the name promises something it doesn't do. X gives you three quite different views of an account, and only one of them is "what did they post today."
We tested all three on @AmazonHelp, which is close to the busiest account on the platform. It has posted almost 14 million tweets and replies to customers every few minutes. If any endpoint was going to show fresh tweets, it'd be on this account.
Endpoint 1: user-tweets is a highlight reel
/twitter/user-tweets?handle=AmazonHelp returned 100 tweets. The newest was from September 2025, a full year old. The oldest was from 2016. The rest were spread across every year in between.
They also weren't in date order. We checked each tweet against the one after it: in 44 of 99 pairs, the next tweet was newer, not older. That's close to random. A newest-first timeline would score zero.
What you're getting is a ranked selection of the account's posts, the kind X surfaces to show what an account is about. It's useful for exactly that: a quick sample of what someone typically says, with good engagement numbers attached. It's the wrong tool for "what's new," and it has no pagination, so you can't page your way to recent posts either.
That's why one account's 2018 World Cup campaign can crowd out everything they posted this year. Popular old posts win.
Endpoint 2: user-tweets-all walks back through history
/twitter/user-tweets-all is the paginated one. It takes the account's numeric user_id rather than the handle, and returns a cursor for the next page.
On @AmazonHelp, page one ran from 20 September back to 4 July 2026, 40 tweets. Page two picked up on 7 July and went back to 28 April. So it moves steadily backwards through time, page after page, which is exactly what you want for building an archive or backfilling history.
Two things to know. The newest tweet on page one was 11 days old, on an account that posts constantly, so this isn't a live feed either. And it returns X's raw timeline structure, so the tweets are nested a few levels down rather than in a flat list. The code below handles that.
Endpoint 3: search is the live one
/twitter/search?query=from:AmazonHelp&type=Latest returned 20 tweets. Every one was from the same day we ran it, and the newest was from that morning. All 20 were authored by @AmazonHelp.
That's the answer to "latest tweets": search, with the from: operator and type=Latest. Without type=Latest you get X's "top" ranking again, and you're back to a highlight reel.
Which one to use
| You want | Use | Notes |
|---|---|---|
| What an account posted today | search with from:handle, type=Latest | Freshest by far |
| Full history, page by page | user-tweets-all with cursor | Needs numeric user_id |
| A quick sample of typical posts | user-tweets | Ranked, not dated; no pagination |
A note on replies
Accounts like @AmazonHelp are almost entirely replies. Their "latest tweets" are customer conversations, not announcements. Search returns those replies along with everything else, so if you only want original posts, filter them out yourself: a reply has legacy.in_reply_to_status_id_str set.
The highlight reel can mislead you here. On one brand account we looked at, user-tweets showed mostly polished campaign posts, while everything it had posted in the last four months was replies. Looking only at the highlight reel, you'd think the account had gone quiet.
The code
First, the parts every approach shares. X returns its own nested format, so we walk it and pull out tweet objects:
const API = "https://api.sociavault.com/v1/scrape/twitter";
const KEY = process.env.SOCIAVAULT_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function get(path, params) {
const res = await fetch(`${API}/${path}?${new URLSearchParams(params)}`, {
headers: { "x-api-key": KEY },
});
const body = await res.json();
if (body?.error) throw new Error(body.error);
return body.data ?? {};
}
// Pull every tweet out of X's nested timeline format, deduplicated by ID.
function extractTweets(data) {
const out = new Map();
const stack = [data];
while (stack.length) {
const node = stack.pop();
if (!node || typeof node !== "object") continue;
if (
node.__typename === "Tweet" &&
node.rest_id &&
node.legacy?.created_at
) {
out.set(node.rest_id, {
id: node.rest_id,
createdAt: new Date(node.legacy.created_at),
text: node.legacy.full_text ?? "",
likes: node.legacy.favorite_count ?? 0,
isReply: Boolean(node.legacy.in_reply_to_status_id_str),
});
}
for (const v of Object.values(node))
if (v && typeof v === "object") stack.push(v);
}
return [...out.values()].sort((a, b) => b.createdAt - a.createdAt);
}
The latest tweets:
async function latestTweets(handle, { originalsOnly = false } = {}) {
const data = await get("search", { query: `from:${handle}`, type: "Latest" });
const tweets = extractTweets(data);
return originalsOnly ? tweets.filter((t) => !t.isReply) : tweets;
}
const today = await latestTweets("AmazonHelp");
console.log(
today
.slice(0, 5)
.map((t) => `${t.createdAt.toISOString()} ${t.text.slice(0, 60)}`),
);
Full history, with a page cap:
async function tweetHistory(handle, { maxPages = 5 } = {}) {
// user-tweets-all wants the numeric ID, not the handle.
const profile = await get("profile", { handle });
const userId = profile.rest_id;
if (!userId) throw new Error(`No rest_id for @${handle}`);
const all = new Map();
let cursor = null;
for (let page = 1; page <= maxPages; page++) {
const params = cursor ? { user_id: userId, cursor } : { user_id: userId };
const data = await get("user-tweets-all", params);
const tweets = extractTweets(data);
tweets.forEach((t) => all.set(t.id, t));
// The cursor is an object; "bottom" pages backwards in time.
cursor = data.cursor?.bottom;
if (!cursor || !tweets.length) break;
await sleep(1000);
}
return [...all.values()].sort((a, b) => b.createdAt - a.createdAt);
}
The same in Python:
import os, time, requests
from datetime import datetime
API = "https://api.sociavault.com/v1/scrape/twitter"
HEADERS = {"x-api-key": os.environ["SOCIAVAULT_API_KEY"]}
def get(path, **params):
body = requests.get(f"{API}/{path}", params=params, headers=HEADERS, timeout=90).json()
if body.get("error"):
raise RuntimeError(body["error"])
return body.get("data") or {}
def extract_tweets(data):
out, stack = {}, [data]
while stack:
node = stack.pop()
if isinstance(node, dict):
legacy = node.get("legacy") or {}
if node.get("__typename") == "Tweet" and node.get("rest_id") and legacy.get("created_at"):
out[node["rest_id"]] = {
"id": node["rest_id"],
"created_at": datetime.strptime(legacy["created_at"], "%a %b %d %H:%M:%S %z %Y"),
"text": legacy.get("full_text", ""),
"is_reply": bool(legacy.get("in_reply_to_status_id_str")),
}
stack.extend(v for v in node.values() if isinstance(v, (dict, list)))
elif isinstance(node, list):
stack.extend(node)
return sorted(out.values(), key=lambda t: t["created_at"], reverse=True)
def latest_tweets(handle):
return extract_tweets(get("search", query=f"from:{handle}", type="Latest"))
def tweet_history(handle, max_pages=5):
user_id = get("profile", handle=handle).get("rest_id")
tweets, cursor = {}, None
for _ in range(max_pages):
params = {"user_id": user_id, **({"cursor": cursor} if cursor else {})}
data = get("user-tweets-all", **params)
batch = extract_tweets(data)
tweets.update({t["id"]: t for t in batch})
cursor = (data.get("cursor") or {}).get("bottom")
if not cursor or not batch:
break
time.sleep(1)
return sorted(tweets.values(), key=lambda t: t["created_at"], reverse=True)
If you're watching accounts continuously, run the search on a schedule and keep the highest tweet ID you've seen. Anything newer is new. Our guide to real-time brand mention monitoring on X builds that loop out properly.
The honest limits
- Search is the freshest, not a firehose. It returns the most recent matches in pages of about 20. On an account that posts constantly, you'll need to poll often or page with the cursor to avoid gaps.
- Busy searches can get throttled. Heavy search use can return 429s. Back off and retry rather than hammering it.
- History has a practical floor. Paging
user-tweets-allgoes back a long way, but every page is a request and a credit. Cap it at what you actually need. - Protected accounts are off-limits. Only public tweets are available.
- Counts are a moment in time. Likes and views keep moving after you fetch them.
For the wider picture of getting X data without the official API, see our Twitter API alternative guide.
Frequently Asked Questions
Why does the Twitter API return old tweets instead of new ones?
The user-tweets endpoint returns a ranked selection of an account's posts, not a timeline. On a very active account, its newest tweet was a year old and the order was close to random. Use search with from: and type=Latest for current tweets.
How do I get the latest tweets from a user?
Search with query=from:username and type=Latest. In our test on @AmazonHelp, every result was from the same day. Leaving out type=Latest gives you top-ranked results instead.
How do I paginate through a user's tweets?
Use user-tweets-all with the account's numeric user_id, which you get from the profile endpoint as rest_id. Each response includes a cursor object; pass cursor.bottom to get the next, older page.
Why doesn't user-tweets have pagination?
It isn't a timeline. It returns a fixed ranked selection, so there's nothing further to page through. For paging, use user-tweets-all.
How do I exclude replies?
Check legacy.in_reply_to_status_id_str on each tweet. If it's set, the tweet is a reply. Filter those out if you only want original posts.
Why does an active account look inactive?
If it mostly replies to people, its recent activity is replies, and the ranked endpoint favours older popular posts. Support accounts in particular can look dormant there while posting hundreds of times a day.
Need to track what accounts are posting right now? Sign up for 50 free credits and run the latest-tweets snippet on the accounts you care about.
Found this helpful?
Share it with others who might benefit
Ready to Try SociaVault?
Start extracting social media data with our powerful API. No credit card required.