How do you get quote tweets and retweeters of a post?
Last updated August 24, 2026
Two endpoints, two shapes. tweet/retweeters hands back user objects with followers_count, because a repost adds no words of its own. tweet/quotes hands back tweet objects, because a quote attaches commentary worth reading. Both accept the post id and page on a cursor at $0.0008 each, and the quotes route runs over X's search index, which the payload declares in-band.
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).
Why amplification splits into two object types
A repost forwards a post unchanged, so the only new information it creates is the identity of the person who forwarded it. tweet/retweeters therefore returns a users array, each row holding id, username, name, followers_count and verified. A quote attaches fresh text, so tweet/quotes returns a tweets array instead, each row flagged is_quote with the original nested under quoted_tweet as an object carrying its id and text. Folding the two into one endpoint would force one of those shapes to throw away what makes it useful: either you lose the commentary or you lose the follower figures. Keeping them apart is why each response is directly usable without a reshaping step, and it is also why the two routes have different parameter lists, different provenance fields and different end-of-pages behaviour.
The retweeters request and what it returns
GET /twitter/tweet/retweeters accepts id, the numeric identifier of the post, and an optional cursor. That is the whole parameter list, so there is no page size to set, no ordering to choose and no filter to apply. The envelope is a users array, a count for the page and next_cursor. Each user row carries followers_count, which is the field that turns a raw list of accounts into a rough reach estimate, and verified, which lets you weight established accounts differently from new ones. It also carries description, so a bio-based classifier can run over the roster without a second profile lookup. Nothing on the row records when the repost happened, so if timing matters, write your own fetch timestamp beside each page as it lands and treat the ordering as opaque.
The quotes request has five parameters
GET /twitter/tweet/quotes takes either id or url, so a full x.com status link works without you parsing the number out of it first. Supply one or the other rather than both. cursor drives paging exactly as it does elsewhere. count sets the requested page size, defaults to 20, and is clamped by the underlying search into a 1 to 100 range, meaning an oversized value silently returns at most a hundred rather than raising an error you could catch. product picks the ordering. strict controls whether rows that fail the match check are dropped rather than merely counted. Five parameters against the retweeters route's two, and the extra three all exist because of how this particular endpoint is served underneath.
The response tells you it is search-backed
X publishes no dedicated quote-tweets operation, so this route runs the query quoted_tweet_id against X's search index. Three fields say so without you reading anybody's source code. source is always the literal string search, present precisely so a caller comparing these numbers against X's own interface knows where they came from. search_query echoes the exact query sent upstream, so a surprising page is debuggable in place rather than by raising a support ticket. quote_matched counts how many of the returned rows demonstrably quote the id you asked for, read off each row's embedded quoted_tweet. Publishing provenance inside the payload is unusual for a commercial API, and it is the reason you can audit these numbers rather than having to take them on trust.
Reading quote_matched before trusting a page
When quote_matched equals count, every row on that page is a genuine quote of your post and you can ingest it as-is. Zero on a page that still returned rows means X has stopped honouring the operator upstream and is handing back posts that merely contain the number somewhere in their text, so discard that page rather than storing it. Those two states look almost identical in a log line and mean completely different things, which is why the check belongs in code rather than in a runbook. The figure is a lower bound and never an upper one, because it only counts rows where X embedded the quoted original, and X does not always do that. A genuine quote can therefore be missing from the tally while still sitting in the array you received.
Why the total disagrees with what X displays
The authoritative quote total is quote_count on the tweet object, available from tweet/detail. What a search page returns is something narrower, and the reference is explicit about each reason why. A quote posted seconds ago may not be indexed yet. Quotes that were deleted, or written by protected or suspended accounts, or withheld in your region, are absent from the index entirely and no amount of paging will surface them. And the index makes no promise to retain every historical match for a high-volume post, so an old viral thread is the worst case for completeness. Treat the returned count as a floor on what is publicly readable, reconcile against quote_count when the gap matters, and record both numbers rather than picking one.
has_more, and paging to the end of both routes
The quotes envelope carries has_more, true when the page returned rows and also carried a usable next_cursor, false on the final page. Its next_cursor is documented as null when there is no further page, so this route gives you two independent end signals. The retweeters route has neither, so there you page until the users array comes back empty, which is the rule that holds across the whole API anyway. In both cases the cursor is opaque and goes straight back into the cursor parameter unchanged. Do not try to interpret it, cache it across runs, or edit it to skip ahead, because a cursor is only meaningful to the response chain that produced it and a mutated one will not fail loudly.
What strict and product actually change
With strict set true, any returned row that does not demonstrably quote the requested post is dropped instead of merely being excluded from quote_matched, and metering follows the rows you actually receive, so the discarded ones are not billed to you. It defaults to false precisely because X omits the embedded original often enough that strict mode can hide quotes that are genuinely real, which is a worse failure than including a few false rows you can filter yourself. product accepts Latest, the reverse-chronological default, or Top, which routes upstream to X's ranked search and is materially slower. Any other value quietly falls back to Latest rather than erroring or changing what the endpoint means, so a typo there costs you nothing but also warns you about nothing.
Mapping reach against argument, and the bill
Retweeters answer who spread the post, and followers_count on each row turns that roster into a rough reach picture, though it is an upper bound because audiences overlap and a follower is not a viewer. Quotes answer what people said while spreading it, which is where the disagreement and the criticism live, and is usually the more interesting half. Both routes bill at the standard read rate of $0.0008 a page, so twelve pages of quotes alongside twelve pages of retweeters is under two cents in total. Since price attaches to the call rather than to the record, a viral post costs more only because exhausting it takes more pages, and raising count on the quotes route lowers that page count directly. The 600 per minute ceiling per key applies here unchanged. Per our published rates: $0.0008 a call.
tweet/retweeters against tweet/quotes
| Property | tweet/retweeters | tweet/quotes |
|---|---|---|
| Array returned | users, array of User | tweets, array of Tweet |
| Text the sharer added | None, a repost carries no words | Present on every row as text |
| Accepts a status URL | No, numeric id only | Yes, id or url |
| Page-size parameter | Not exposed, cursor only | count, default 20, clamped 1 to 100 |
| Ordering control | Not exposed | product, Latest or Top |
| End-of-pages signal | Empty users array | has_more false, next_cursor null |
| Provenance fields | count and next_cursor | source, search_query, quote_matched |
The Retweets endpoints let you retweet and undo retweets, see who retweeted a Post, and get reposts of your own Posts.
Questions and answers
- Why can I not get quotes and reposts from one endpoint?
- They return different objects. A repost yields a person, so the payload is a users array with follower figures on it. A quote yields a new post carrying its own commentary, so the payload is a tweets array with the original nested under quoted_tweet. Folding them together would force one of the two shapes to lose information the other one needs, and it would leave the response with fields that are meaningless for half the rows.
- Can I pass a status URL instead of a numeric id?
- On the quotes route, yes. It accepts either id or url, so a full x.com status link works without you extracting the number first. Supply one or the other, not both. The retweeters route documents only the numeric id parameter, so a link has to be reduced to its id before you call it. If an earlier tweet/detail call already handed you the id, prefer that in both places, since it removes a parsing step that can silently mangle an edge case.
- Why does the quote count differ from what X shows?
- Because this route reads a search index rather than a dedicated counter. The index lags fresh posts, and quotes that were deleted, written by protected or suspended accounts, or withheld in your region never enter it at all. The index also makes no promise to hold every historical match for a high-volume post. Treat the returned count as a floor on what is publicly readable, and take quote_count from tweet/detail as the real total when the gap matters.
- What does strict mode actually change?
- With strict on, any returned row that does not demonstrably quote the requested post is dropped rather than merely being left out of quote_matched. Metering follows the rows you receive, so the discarded ones are not billed. It ships off by default because X omits the embedded original often enough that turning it on can hide quotes that are genuinely real, and losing a true row is a worse outcome than passing a false one to your own filter.
- How do I tell a broken page from an empty one?
- Compare quote_matched against count. Equal means every row is verified and safe to ingest. Zero while rows came back means the operator stopped being honoured upstream and the page is noise, so discard it. A count of zero with an empty array is simply the end of the results and nothing is wrong. The two failure-looking states are easy to confuse in a log, so branch on them explicitly rather than treating any zero the same way.
- Should I use product=Top for amplification work?
- Rarely. Top hands the request to X's ranked search, which the reference describes as materially slower upstream, and any value that is not Latest or Top quietly falls back to Latest without telling you. For monitoring, and for exhausting a full result set page by page, stay on the default and let the cursor supply the ordering rather than fighting it. Top earns its cost only when you want the loudest few quotes and do not intend to page at all.
- Does raising count reduce the number of calls?
- It can, since the underlying search clamps the value into a 1 to 100 range and the default is only 20. Asking for 100 packs up to five times as many rows into each billed call, which is the single cheapest optimisation available on this route. Asking for 500 does not error, it simply returns at most a hundred, so treat the clamp as the real ceiling and size your loop against that rather than against the number you sent.
- How do I know when the quote pages are finished?
- Read has_more. It is true when the page returned rows and carried a usable next_cursor, and false on the final page, and next_cursor itself comes back null when there is nothing further. The retweeters route has neither field, so there you page until the users array comes back empty. Writing both loops against the empty-array test is the safer habit if you would rather maintain one code path across every endpoint you call.
- Can I estimate reach from the retweeter list?
- Roughly. Each user row carries followers_count, so summing across the accounts that reposted gives an upper bound on potential impressions. It is only an upper bound, because audiences overlap heavily and a follower is not a viewer. Use view_count from tweet/detail as the measured figure and the follower sum as context around it, not as a substitute. Weighting by the verified flag helps separate a handful of large accounts from a long tail of small ones.
- What does a full amplification pull cost?
- Both routes bill at the standard read rate of $0.0008 per page, so twelve pages of quotes alongside twelve pages of retweeters is under two cents. Because the price attaches to the call rather than to each record, a viral post costs more only because exhausting it takes more pages. Raising count on the quotes route lowers the page count directly, and the $0.50 credited at signup covers 625 reads before a card is involved at all.
Keep reading
Start with $0.50 in free credits
No credit card. Roughly 12,500 tweets to test every endpoint.