One keyword in. Clean JSON out.
Send a search query, get back an array of public posts from X (Twitter) as JSON, each with its own engagement counters and author. The query string accepts X's advanced search operators, so `from:`, `since:`, `min_faves:` and the rest work exactly as they do in the site's own search box. Billing is per result at 2 credits, so a 20-post search costs 40 credits.
The difference
On the left, what a headless browser hands you. On the right, what the endpoint hands you.
<article data-testid="tweet" class="css-175oi2r r-1adg3ll r-1ny4l3l"><div class="css-175oi2r r-18u37iz"><div dir="ltr" class="css-901oao r-1nao33i">Shipping notes: response times are down 40% after this week's cache work.</div></div><div role="group" aria-label="90211 views, 1211 likes, 204 reposts" class="css-175oi2r r-1kbdv8c"><div data-testid="like"><span class="css-1jxf684">1,211</span></div><div data-testid="retweet"><span>204</span></div></div><time datetime="2026-07-06T08:15:00.000Z">8h</time><script>window.__INITIAL_STATE__={"entities":{"tweets":{"entities":{"186112... {"data": [{"id": "1861122334455667788","url": "https://x.com/XDevelopers/status/1861122334455667788","text": "Cursor pagination is now live on every list endpoint.","posted_at": "2026-07-06T08:15:00Z","metrics": {"views": 90211,"likes": 1211,"reposts": 204,"replies": 88}}],"meta": {"count": 2,"limit": 20,"item": "result"},"request_id": "req_01JZX4M8Q2TE9W"}
The payload
An array. Every element is one post: the text, the counters (views, likes, replies, reposts, quotes, bookmarks), the publish timestamp, the language X assigned it, and a small author object with the handle and display name. Two booleans tell you whether the post is a reply and whether it is a repost, which is usually the first thing you filter on.
The array comes back in the order the search surface produced it. That order is not stable between runs and is not strictly chronological, so if you need a timeline, sort by posted_at yourself after the response lands. Use `id` as your primary key when you dedupe across repeated searches — the same post will reappear across overlapping queries.
This is the same public search anyone can run logged out. Posts from protected accounts are not in the index and will not appear, no matter how the query is phrased.
Like count at the time of the call.
Pricing
Billing is per post returned at 1¢ each on the monthly plan, so a default 20-post search costs 40 credits — about 20¢ — and a search that matches nothing still costs 1 credit.
Estimate only, on a full-year basis. 2 credits per returned result. Monthly: $5 per 1,000 credits with $4/1,000 top-ups. Annual: $54 per 12,000 credits with $3/1,000 top-ups. We show whichever plan is cheaper for your annual volume, divided by 12. Failed calls are never charged; cache hits cost the same as fresh calls.
Entry price, side by side
Credits are not equivalent units across vendors — each meters differently. Price your own workload on both before deciding on entry price alone. At high volume, several of these are cheaper per credit than we are.
Engineering notes
We document exactly how every endpoint behaves — nulls, caching, billing — so what you ship on day one is still running in month six. No surprises, no support tickets.
2 credits for every post returned. The default limit is 20, which is 40 credits. Pass `limit` to change it. A broad query at limit=200 is 400 credits, and it is easy to spend a plan by putting that inside a loop.
You pay for what actually comes back. A narrow query that only matches 3 posts costs 6 credits, not the full limit.
Operators pass straight through: `from:`, `to:`, `since:`, `until:`, `min_faves:`, `min_retweets:`, `filter:links`, `-filter:replies`, quoted phrases, `OR`, and leading-minus exclusions. Combining a couple of them is almost always cheaper and better than a bare keyword plus a high limit.
Remember to URL-encode the query. A `#` left raw in a URL truncates everything after it, which is the single most common cause of a search returning the wrong thing.
The search backend works in batches and treats roughly 50 posts per query as its data-availability floor. Asking for 5 results still returns 5, but they are drawn from that wider pool rather than being strictly the top 5 of a ranked list. If you need a precise top-N by engagement, request more and sort yourself.
A keyword query returns originals, replies and reposts together. For mention counting this will overstate reach badly — the same text can appear dozens of times as reposts. Either filter on `is_retweet` and `is_reply` after the response lands, or add `-filter:replies -filter:retweets` to the query and avoid paying for them at all.
Responses sit in the fast cache class, so the same query repeated inside the window returns in milliseconds with `x-cache: hit`. Note that the cache saves you time, not credits — a hit is billed per result exactly like a fresh call. If you are polling a query for new material, pass `fresh=true` and accept the extra latency, otherwise you can pay full price for a snapshot you have already seen.
Questions
Send a GET request to /v1/twitter/search with your query and API key. Nothing to register for, nothing to get approved, no token exchange to implement. The endpoint reads the same public search results you would get logged out in a browser and returns them as JSON. X's own API tiers price search access far above per-result billing for most small projects.
2 credits per post returned. Credits are $0.005 each on monthly and $0.0045 on annual, so a 20-result search is about 20 cents. You are charged on what comes back, not on the limit you asked for — a query matching only 4 posts costs 8 credits. Failed calls are never charged.
Yes. The query goes to the same search surface the site uses, so `from:`, `to:`, `since:`, `until:`, `min_faves:`, `min_retweets:`, `filter:links`, `-filter:replies`, quoted phrases and OR all behave as documented by X. Combining operators is the cheapest way to keep result counts down, since you pay per post returned. URL-encode the whole query string, especially any `#`.
As far back as the public search index goes for that query, which in practice means recent weeks are reliable and older material gets patchy. A `since:`/`until:` pair narrows the window but cannot recover posts the index has dropped. Note also that there is no cursor or page token on this endpoint: one call returns up to `limit` results and repeating it re-runs the query rather than continuing from where the last one ended. For one account's back catalogue, the X Tweets API called against the handle is usually a better fit than date-bounded search, though it is capped by a single call too.
Search ranking on X is not deterministic and the index is constantly moving. Running the same query twice can return overlapping but different sets, especially on high-volume terms. Treat each call as a sample rather than a complete list: store results keyed on `id`, run on a schedule, and let the union build up over time instead of expecting one call to be exhaustive.
No. Protected accounts are not in the public search index, and we do not log in, so their posts never appear regardless of query. The same applies to deleted posts and suspended accounts. Everything this endpoint returns is content you could see yourself in a logged-out browser.
Two ways. Add `-filter:retweets -filter:replies` to the query so they never come back and you never pay for them, which is the option to prefer. Or filter after the fact on the `is_retweet` and `is_reply` booleans on each result, which is more flexible but costs credits for posts you then discard.
Next
100 trial credits on signup — no card, key on screen immediately.