TwitterAPIs Docs
API ReferenceTweet Details

Twitter Quote Tweets API | Get Quotes of a Tweet

Fetch the quote tweets of a given tweet, cursor-paginated as full tweet objects, so you can read the commentary people attached rather than just count it. This endpoint is search-backed rather than served by a dedicated X operation, and every response says so in-band via source, search_query and quote_matched. Cost: $0.0008 per call.

Tweet Quotes returns the tweets that quote a given tweet, by id or url, cursor-paginated as full tweet objects. It is SEARCH-BACKED: X exposes no dedicated quote-tweets operation, so this endpoint runs the search query quoted_tweet_id:<id> and returns what X's search index holds. The count field is therefore how many quotes this search returned, never the tweet's true total, which is quote_count on the tweet object itself. Each page is one $0.0008 read.

GET
/tweet/quotes

Authorization

bearerAuth
AuthorizationBearer <token>

Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.

In: header

Query Parameters

id?string

Numeric ID of the tweet whose quotes you want. Provide either id or url.

url?string

Full status URL of the tweet whose quotes you want. Provide either id or url.

cursor?string

Pagination cursor from a previous response's next_cursor.

count?integer

Max items to request for this page. Defaults to 20 and is clamped to 1-100 by the underlying search, so a larger number returns at most 100 rather than erroring.

product?string

Search ordering: Latest (default, reverse-chronological) or Top (X's ranked ordering, materially slower upstream). Any other value falls back to Latest rather than changing what the endpoint means.

strict?boolean

Set to true to drop every returned row that does not demonstrably quote the requested tweet, instead of only counting them in quote_matched. Default false, because X does not embed the quoted original on every search result, so strict mode can hide real quotes.

fields?string

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.

compact?string

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.

paid_promotion?string

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/tweet/quotes?id=1759123456789012345&url=https%3A%2F%2Fx.com%2Fnaval%2Fstatus%2F1759123456789012345&count=20&product=Latest&strict=false&fields=id%2Ctext%2Ccreated_at%2Cauthor.username&compact=1&paid_promotion=only"
{  "count": 2,  "next_cursor": "DAADDAABCgABF...",  "has_more": true,  "tweets": [    {      "id": "1759987654321098765",      "text": "This is the clearest write-up of the problem I have read all year.",      "created_at": "Tue Feb 20 16:02:11 +0000 2026",      "author": {        "id": "9921",        "username": "devjane",        "name": "Jane"      },      "is_quote": true,      "quoted_tweet": {        "id": "1759123456789012345",        "text": "Launching today."      },      "favorite_count": 41    },    {      "id": "1759988812345678901",      "text": "Adding one caveat to this.",      "created_at": "Tue Feb 20 16:20:44 +0000 2026",      "author": {        "id": "3310",        "username": "buildlogs",        "name": "Build Logs"      },      "is_quote": true,      "quoted_tweet": {        "id": "1759123456789012345",        "text": "Launching today."      },      "favorite_count": 7    }  ],  "source": "search",  "search_query": "quoted_tweet_id:1759123456789012345",  "quote_matched": 2}
{  "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

UnitPrice
Per call$0.0008
Per 1,000 calls$0.80
Per 1,000 records (~20 per call)~$0.04

Each page of quotes is one $0.0008 read. strict=true is metered on the rows you actually receive, so the dropped non-matching rows are not billed.

When to use

Use it to read what people said when they quoted a post, which is the commentary Tweet Replies does not capture and Tweet Retweeters cannot show you (a retweet carries no text). Reach for Tweet Detail instead when all you need is the authoritative quote_count number.

Notes

  • SEARCH-BACKED, and this is the one thing to internalise before using the numbers. X exposes no dedicated quote-tweets operation, so this endpoint runs the search query quoted_tweet_id:<id> against X's search index. count is what that search returned. The authoritative total is quote_count on the tweet object, available from Tweet Detail, and the two WILL differ.
  • Why they differ, concretely: a quote posted seconds ago may not be indexed yet; quotes that were deleted, or posted by protected or suspended accounts, or withheld in your region, are absent from search; and X's index makes no promise to hold every historical match for a high-volume tweet.
  • quote_matched is the in-band honesty check. If it equals count, every returned tweet really is a quote of the id you asked for. If it is 0 while tweets is non-empty, X is no longer honouring the quoted_tweet_id: operator and is returning tweets that merely contain that number: discard the page rather than trusting it.
  • quote_matched is a LOWER bound, not an upper one. It only counts rows where X embedded the quoted original in the search result, and X does not always do that, so a genuine quote can be missing from quote_matched. That is why strict is off by default.
  • product=Top routes upstream to X's ranked search, which is materially slower than the default Latest. Use Latest for monitoring and paging.
  • An empty first Top page is re-asked under Latest. X's ranked search returns nothing for some tweets that do have quotes, so when that happens the page is served from Latest with product_used "Latest" and top_fallback true. Keep paging with next_cursor as usual: after a fallback it is tagged, so it keeps serving Latest even if you send product=Top again.

Pagination

Pass the returned next_cursor straight back as ?cursor= to get the next page. This endpoint pages on the search timeline's own bottom cursor, exactly like Advanced Tweet Search. Stop when tweets comes back empty or has_more is false.

FAQ

Why does count disagree with the quote_count on the tweet?

Because they measure different things. quote_count on the tweet object is X's authoritative total. count here is how many quotes X's SEARCH INDEX returned for this page, and the two will differ: a very fresh quote has not been indexed yet, and deleted, protected, suspended and region-withheld quotes are absent from search entirely. Treat count as a floor on what is publicly readable, not as the total.

What are source, search_query and quote_matched for?

They exist so you never have to guess how a number was produced. source is always "search", which tells you this endpoint is not backed by a dedicated X operation. search_query is the exact query sent upstream, quoted_tweet_id:<id>. quote_matched is how many of the returned tweets demonstrably carry the requested tweet as their quoted_tweet, so quote_matched equal to count means every row is a genuine quote.

What does strict=true do?

It drops every returned row whose embedded quoted_tweet is not the tweet you asked about, instead of merely counting them in quote_matched. It is off by default because X does not embed the quoted original on every search result, so strict trades a false-positive risk for a false-negative one, and only you can decide which is worse for your use case.

Should I worry if quote_matched comes back 0 on a non-empty page?

Yes, and that is exactly what the field is for. It means X stopped honouring the quoted_tweet_id: operator and tokenized it as plain text, so the rows returned are tweets that merely contain that number. Treat a zero quote_matched with a non-empty tweets array as a bad result, not as data.