How do I search X (Twitter) posts by keyword?
Call the Openhandle search endpoint with your query in q. Pick sort=latest or sort=top. Each request returns one page of public posts with the author, text, time, and metrics, plus a cursor for the next page.
You pay per answered request, not per month. There is no plan to pick and no post cap to plan around.
Checked September 2026 · request and response run against the API · no affiliate links
curl "https://api.openhandle.dev/v1/twitter/search/posts?q=synthetic&sort=latest" \
-H "Authorization: Bearer $OPENHANDLE_API_KEY"import { OpenHandle } from '@openhandle/sdk';
const openhandle = new OpenHandle({ apiKey: process.env.OPENHANDLE_API_KEY! });
let page = await openhandle.twitter.search.posts.list({ q: 'synthetic', sort: 'latest' });
while (page) {
for (const post of page.data) console.log(post.author.handle, post.text);
page = await page.next();
}{
"platform": "twitter",
"resource": "post",
"capturedAt": "2026-09-18T09:42:19Z",
"source": "live",
"data": [
{
"id": "940100000000000001",
"url": "https://x.com/copperfield_lab_x_test/status/940100000000000001",
"text": "A deterministic text post from a fictional lab. #synthetic",
"createdAt": "2026-08-09T15:30:00Z",
"author": { "id": "940000000001", "handle": "copperfield_lab_x_test" },
"metrics": { "likes": 42, "reposts": 3, "comments": 3, "quotes": 0, "views": null }
}
],
"meta": { "cursors": { "next": "b3BhcXVlLWN1cnNvcg" } }
}Trimmed to one post and the fields this question needs. Views are null here because the Test fixture does not publish them. On a live post, views are the number X shows, or null when X hides it.
Why people leave the official X API
X sells read access in monthly tiers with a fixed post cap. The free tier cannot read. Teams that need a few thousand searches a month pay for a plan sized for far more. Openhandle bills one price per answered request and nothing per month.
Latest or top
- —latest. Newest posts first. Use it for monitoring a brand, a handle, or a product name.
- —top. Posts X ranks highest for the query. Use it for a snapshot of what people see.
- —Replies and threads. A post in the results carries
conversationId. Read the thread under it withGET /v1/twitter/posts/{id}/comments.
Monitor a keyword over time
Search returns what X shows now. It does not keep history for you. To monitor a keyword, run sort=latest on a schedule, store each post id, and skip IDs you already have. Store capturedAt with each run so the report can be repeated.
When this does not work
Empty is an answer. A cursor from another query is a mistake.
| Situation | Status | Code |
|---|---|---|
| No public posts match the query | 200 | empty data |
| The cursor came from another query or sort | 400 | CURSOR_MISMATCH |
{
"error": {
"code": "CURSOR_MISMATCH",
"message": "The cursor does not belong to this request.",
"requestId": "req_01j8…",
"retryable": false
}
}An empty page bills as an answered request. A cursor is bound to its query, sort, and freshness. Change any of them and start without a cursor. Every new workspace starts with 100 free live requests, no card needed.
Read next
More X (Twitter) questions
Send your agent ahead.
Paste the prompt into your agent and it sets everything up. Or come yourself, it takes five minutes.