X (Twitter) APIX (Twitter)

X (Twitter) search API

Call GET /v1/twitter/search/posts?q={query} with your keyword. Each request returns one page of public posts and a cursor for the next page.

You pay one price per page, however many posts it holds. You do not need an X developer account.

GET /v1/twitter/search/posts?q=…&sort=latest
curl "https://api.openhandle.dev/v1/twitter/search/posts?q=synthetic&sort=latest" \
  -H "Authorization: Bearer $OPENHANDLE_API_KEY"
TypeScript SDK
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();
}
200 OK
{
  "platform": "twitter",
  "resource": "post",
  "capturedAt": "2026-09-18T09:42:19Z",
  "source": "cache",
  "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-17T14:00: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 most apps need. The example is a Test query, so you can run it on a free Test key.

What you get

  • —Posts. Each post has id, url, text, createdAt, and the author with ID and handle.
  • —Counts. Likes, reposts, comments, quotes, and views sit in metrics.
  • —Latest or top. Use sort=latest for the newest posts first. Use sort=top for the posts X ranks highest.
  • —More search. Search users with GET /v1/twitter/search/users. Search images or videos with GET /v1/twitter/search/media.

Price

  • —Per request. $0.0025 per live request. $0.002 per request after 100,000 requests in a month. One page of results is one request.
  • —Older data costs less. Set freshness to say how old the data may be. Data up to 24 hours old: $0.0005. Data up to 7 days old: $0.0001. Data up to 30 days old: free.
  • —Failed means free. Errors and invalid requests are never billed. You pay for answers, and a confirmed not-found is an answer.
  • —Free to start. Every new workspace starts with 100 free live requests, no card needed. Test keys are never charged.

When this does not work

Empty is an answer. A cursor from another query is a mistake.

SituationStatusCode
No public posts match the query200empty data
The cursor came from another query or sort400CURSOR_MISMATCH
400 Bad Request
{
  "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.

Read next

More X (Twitter) data

Send your agent ahead.

Paste the prompt into your agent and it sets everything up. Or come yourself, it takes five minutes.

For agents

100 free requests. No card needed.