> ## Documentation Index
> Fetch the complete documentation index at: https://getanyapi.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# X / Twitter Search

> Sell twitter.search on AnyAPI: the request your endpoint receives, the response it must return, and what the live trial grades.

Search X (Twitter) with full advanced-search syntax (operators like from:, since:, until:, min\_faves: work inline in the query) and get structured tweets with text, author, engagement, and cursor pagination. Limit is a per-page maximum; native pages contain approximately 20 tweets unless requireSinglePage selects a bulk lane.

This page is the seller-facing summary of one catalog API, `twitter.search`. The transport rules that apply to every API a seller serves - signed requests, the response envelope, timeouts, and body limits - are on [the operator endpoint contract](https://getanyapi.com/docs/sell/operator-endpoint-contract.md).

## The price you compete with

AnyAPI publishes this API from **\$0.60 per 1,000 requests**. That is the price a buyer sees today, so it is the price your endpoint has to come in under to win traffic.

## The request your endpoint receives

| Field | Type | Required | What it means |
| - | - | - | - |
| `cursor` | string | no | Opaque pagination cursor from a previous response's nextCursor. Omit for the first page; pass it to fetch the next page of search results. |
| `limit` | integer | no | Page size to ask for (1-50, default 20). Some sources honor it; others always return their whole native page of about 20. A page is never trimmed after the call, so follow nextCursor to continue exactly where this response ended. Default `20`. |
| `query` | string | yes | Search query using X (Twitter) advanced-search syntax. IMPORTANT: bare terms are ANDed - a tweet must contain EVERY word, so a list of loosely related keywords matches nothing; use one short phrase or OR between alternatives (e.g. 'anyapi OR getanyapi'). When an operator follows an OR list, put the list in parentheses, e.g. '(anyapi OR getanyapi) -filter:replies': without them X applies the operator to the last alternative only, so such a query still runs as X reads it and the response's hint carries the corrected query. You can embed X advanced-search operators directly in the query to filter results: from:username and to:username (author or recipient), since:YYYY-MM-DD and until:YYYY-MM-DD (date range), min\_faves:N, min\_retweets:N, min\_replies:N (engagement floors), "exact phrase", -term to exclude, filter:media and filter:links and -filter:replies (content filters), lang:en, near:city, and geocode:lat,long,radius. Examples: 'from:OpenAI', 'AI agents min\_faves:500 -filter:replies', 'nvidia since:2026-01-01 until:2026-03-01'. A query with no matches returns an empty items array; prefer the fewest words that identify the topic. |
| `queryType` | string | no | Which posts to return: 'Latest' (newest first, the default), 'Top' (most popular), 'Photos' (newest posts with images), or 'Videos' (newest posts with video). Photos and Videos search your query as written with filter:images or filter:videos added after it, so they cost the same as a Latest search. One of: `Latest`, `Top`, `Photos`, `Videos`. Default `"Latest"`. |
| `requireFields` | array | no | Optional; omit it and routing is unchanged, with the cheapest source serving. Name the output fields this request must be able to return, for example `nextCursor`, and it is served only by a source that returns every one of them. Fields you do not name are still returned whenever the serving source has them. This can raise your price: when the cheapest source cannot return a named field, a dearer source serves, and you are quoted and charged its price. A named field can still be absent on a post that genuinely lacks it. Naming a combination that no single source returns together is refused as invalid input, with no charge. On a paginated walk it applies to the first page only; later pages stay with the source that page chose, at the price it was quoted. |
| `requireSinglePage` | boolean | no | Set true to get up to limit results in one response instead of provider-native pages, served by a bulk provider when needed. |

> **Full input JSON Schema**
>
> ```json theme={"dark"}
> {
>   "$schema": "https://json-schema.org/draft/2020-12/schema",
>   "$id": "https://getanyapi.com/schemas/twitter.search.input.json",
>   "example": {
>     "query": "openai"
>   },
>   "title": "X / Twitter search input",
>   "type": "object",
>   "additionalProperties": false,
>   "x-anyapi-compat-inputs": {
>     "lang": {
>       "fold_into": "query",
>       "template": " lang:{value}",
>       "wrap": true
>     },
>     "queryType": {
>       "fold_into": "query",
>       "template": " filter:{value}",
>       "wrap": true,
>       "values": {
>         "Photos": "images",
>         "Videos": "videos"
>       }
>     }
>   },
>   "required": [
>     "query"
>   ],
>   "properties": {
>     "requireFields": {
>       "type": "array",
>       "uniqueItems": true,
>       "items": {
>         "type": "string",
>         "enum": [
>           "nextCursor"
>         ]
>       },
>       "description": "Optional; omit it and routing is unchanged, with the cheapest source serving. Name the output fields this request must be able to return, for example `nextCursor`, and it is served only by a source that returns every one of them. Fields you do not name are still returned whenever the serving source has them. This can raise your price: when the cheapest source cannot return a named field, a dearer source serves, and you are quoted and charged its price. A named field can still be absent on a post that genuinely lacks it. Naming a combination that no single source returns together is refused as invalid input, with no charge. On a paginated walk it applies to the first page only; later pages stay with the source that page chose, at the price it was quoted."
>     },
>     "limit": {
>       "type": "integer",
>       "minimum": 1,
>       "maximum": 50,
>       "default": 20,
>       "description": "Page size to ask for (1-50, default 20). Some sources honor it; others always return their whole native page of about 20. A page is never trimmed after the call, so follow nextCursor to continue exactly where this response ended."
>     },
>     "query": {
>       "type": "string",
>       "description": "Search query using X (Twitter) advanced-search syntax. IMPORTANT: bare terms are ANDed - a tweet must contain EVERY word, so a list of loosely related keywords matches nothing; use one short phrase or OR between alternatives (e.g. 'anyapi OR getanyapi'). When an operator follows an OR list, put the list in parentheses, e.g. '(anyapi OR getanyapi) -filter:replies': without them X applies the operator to the last alternative only, so such a query still runs as X reads it and the response's hint carries the corrected query. You can embed X advanced-search operators directly in the query to filter results: from:username and to:username (author or recipient), since:YYYY-MM-DD and until:YYYY-MM-DD (date range), min_faves:N, min_retweets:N, min_replies:N (engagement floors), \"exact phrase\", -term to exclude, filter:media and filter:links and -filter:replies (content filters), lang:en, near:city, and geocode:lat,long,radius. Examples: 'from:OpenAI', 'AI agents min_faves:500 -filter:replies', 'nvidia since:2026-01-01 until:2026-03-01'. A query with no matches returns an empty items array; prefer the fewest words that identify the topic."
>     },
>     "queryType": {
>       "type": "string",
>       "enum": [
>         "Latest",
>         "Top",
>         "Photos",
>         "Videos"
>       ],
>       "description": "Which posts to return: 'Latest' (newest first, the default), 'Top' (most popular), 'Photos' (newest posts with images), or 'Videos' (newest posts with video). Photos and Videos search your query as written with filter:images or filter:videos added after it, so they cost the same as a Latest search.",
>       "default": "Latest"
>     },
>     "cursor": {
>       "type": "string",
>       "description": "Opaque pagination cursor from a previous response's nextCursor. Omit for the first page; pass it to fetch the next page of search results."
>     },
>     "requireSinglePage": {
>       "type": "boolean",
>       "description": "Set true to get up to limit results in one response instead of provider-native pages, served by a bulk provider when needed."
>     }
>   }
> }
> ```

## The response your endpoint must return

| Field | Type | Always present | What it means |
| - | - | - | - |
| `data` | null or object | yes | |
| `data.items` | array | yes | Tweet records: text, author profile, timestamp, and engagement metrics (likes, retweets, replies, views). |
| `data.items[].authorFollowers` | integer | no | Follower count of the account that posted. |
| `data.items[].authorId` | string | no | Numeric X user id of the account that posted, as a string. |
| `data.items[].authorImage` | string | no | Avatar image URL of the account that posted. |
| `data.items[].authorName` | string | no | |
| `data.items[].authorUsername` | string | no | |
| `data.items[].authorVerified` | boolean | no | |
| `data.items[].bookmarkCount` | integer | no | |
| `data.items[].conversationId` | string | no | |
| `data.items[].createdUtc` | number | no | UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds. |
| `data.items[].id` | string | yes | |
| `data.items[].inReplyToId` | string or null | no | Id of the post this post directly replies to, as a string, or null when the post is not a reply. |
| `data.items[].isReply` | boolean | no | |
| `data.items[].lang` | string | no | |
| `data.items[].likeCount` | integer | no | |
| `data.items[].media` | array | no | Photo, video, and GIF attachments on the post. Empty when the post has none. |
| `data.items[].media[].height` | integer | no | Pixel height of the media item, when the lane reports it. |
| `data.items[].media[].type` | string | yes | One of photo, video, or gif. |
| `data.items[].media[].url` | string | yes | Image URL. For a video or GIF this is the poster/thumbnail frame. X media URLs on pbs.twimg.com are publicly fetchable without authentication; append ?name=orig for the full-resolution original. |
| `data.items[].media[].videoUrl` | string | no | Playable video file URL. Present only for video and gif items. |
| `data.items[].media[].width` | integer | no | Pixel width of the media item, when the lane reports it. |
| `data.items[].quoteCount` | integer | no | |
| `data.items[].replyCount` | integer | no | |
| `data.items[].retweetCount` | integer | no | |
| `data.items[].text` | string | yes | |
| `data.items[].url` | string | yes | |
| `data.items[].viewCount` | integer or null | no | Number of views. Null when the source does not report it, as for posts older than X view counts. |
| `data.nextCursor` | string or null | no | Opaque cursor for the next page of search results, or null when there are no more. Pass it back as cursor to continue. |
| `found` | boolean | yes | |

Extra fields are kept, missing declared fields fail.

> **Full output JSON Schema**
>
> ```json theme={"dark"}
> {
>   "$schema": "https://json-schema.org/draft/2020-12/schema",
>   "$id": "https://getanyapi.com/schemas/twitter.search.output.json",
>   "title": "X / Twitter search output",
>   "type": "object",
>   "additionalProperties": false,
>   "required": [
>     "found",
>     "data"
>   ],
>   "properties": {
>     "found": {
>       "type": "boolean"
>     },
>     "data": {
>       "oneOf": [
>         {
>           "type": "null"
>         },
>         {
>           "type": "object",
>           "additionalProperties": false,
>           "required": [
>             "items"
>           ],
>           "properties": {
>             "items": {
>               "x-anyapi-must-populate": true,
>               "type": "array",
>               "description": "Tweet records: text, author profile, timestamp, and engagement metrics (likes, retweets, replies, views).",
>               "items": {
>                 "type": "object",
>                 "required": [
>                   "id",
>                   "url",
>                   "text"
>                 ],
>                 "properties": {
>                   "id": {
>                     "x-anyapi-must-populate": true,
>                     "type": "string"
>                   },
>                   "url": {
>                     "x-anyapi-must-populate": true,
>                     "type": "string"
>                   },
>                   "text": {
>                     "x-anyapi-must-populate": true,
>                     "type": "string"
>                   },
>                   "authorUsername": {
>                     "x-anyapi-must-populate": true,
>                     "type": "string"
>                   },
>                   "authorName": {
>                     "x-anyapi-must-populate": true,
>                     "type": "string"
>                   },
>                   "authorVerified": {
>                     "type": "boolean"
>                   },
>                   "authorId": {
>                     "type": "string",
>                     "description": "Numeric X user id of the account that posted, as a string."
>                   },
>                   "authorImage": {
>                     "type": "string",
>                     "description": "Avatar image URL of the account that posted."
>                   },
>                   "authorFollowers": {
>                     "type": "integer",
>                     "description": "Follower count of the account that posted."
>                   },
>                   "likeCount": {
>                     "type": "integer"
>                   },
>                   "retweetCount": {
>                     "type": "integer"
>                   },
>                   "replyCount": {
>                     "type": "integer"
>                   },
>                   "quoteCount": {
>                     "type": "integer"
>                   },
>                   "viewCount": {
>                     "type": [
>                       "integer",
>                       "null"
>                     ],
>                     "description": "Number of views. Null when the source does not report it, as for posts older than X view counts."
>                   },
>                   "bookmarkCount": {
>                     "type": "integer"
>                   },
>                   "createdUtc": {
>                     "x-anyapi-must-populate": true,
>                     "type": "number",
>                     "description": "UTC epoch timestamp in seconds (Unix time). Multiply by 1000 for a JS Date in milliseconds."
>                   },
>                   "lang": {
>                     "type": "string"
>                   },
>                   "isReply": {
>                     "type": "boolean"
>                   },
>                   "conversationId": {
>                     "type": "string"
>                   },
>                   "inReplyToId": {
>                     "type": [
>                       "string",
>                       "null"
>                     ],
>                     "description": "Id of the post this post directly replies to, as a string, or null when the post is not a reply."
>                   },
>                   "media": {
>                     "type": "array",
>                     "description": "Photo, video, and GIF attachments on the post. Empty when the post has none.",
>                     "items": {
>                       "type": "object",
>                       "required": [
>                         "type",
>                         "url"
>                       ],
>                       "properties": {
>                         "type": {
>                           "type": "string",
>                           "description": "One of photo, video, or gif."
>                         },
>                         "url": {
>                           "type": "string",
>                           "format": "uri",
>                           "description": "Image URL. For a video or GIF this is the poster/thumbnail frame. X media URLs on pbs.twimg.com are publicly fetchable without authentication; append ?name=orig for the full-resolution original."
>                         },
>                         "videoUrl": {
>                           "type": "string",
>                           "format": "uri",
>                           "description": "Playable video file URL. Present only for video and gif items."
>                         },
>                         "width": {
>                           "type": "integer",
>                           "description": "Pixel width of the media item, when the lane reports it."
>                         },
>                         "height": {
>                           "type": "integer",
>                           "description": "Pixel height of the media item, when the lane reports it."
>                         }
>                       }
>                     }
>                   }
>                 }
>               }
>             },
>             "nextCursor": {
>               "type": [
>                 "string",
>                 "null"
>               ],
>               "description": "Opaque cursor for the next page of search results, or null when there are no more. Pass it back as cursor to continue."
>             }
>           }
>         }
>       ]
>     }
>   }
> }
> ```

## Fields that must never come back empty

* `authorName`
* `authorUsername`
* `createdUtc`
* `id`
* `items`
* `text`
* `url`

The repository marks these fields as ones every source must be able to fill. An empty value is only correct when the entity itself genuinely has nothing there.

## Not found, and how it differs from empty

When the entity does not exist, answer 2xx with the not-found shape:

```json theme={"dark"}
{
  "found": false,
  "data": null
}
```

An empty list is not a not-found: when the entity exists and has nothing to return, answer 2xx with `found: true` and an empty `items` array.

This output schema declares no `reason` field, so `found: false` is the whole not-found answer.

## Example request

```json theme={"dark"}
{
  "query": "openai"
}
```

The compatibility checks fire the schema's example at your endpoint. After that, your answers are graded against what the currently serving source returned for the same real customer inputs.

## Behavior

* Latest is the default ranking, so a request that omits queryType asks for the newest matching posts.
* There are no separate filter inputs: an author, date range, engagement floor or language is written inside the query string with X advanced-search operators such as from:, since:, until:, min\_faves: and lang:en.
* A query that matches nothing returns found true with an empty items array; found false never means no matches.
* limit is a per-page maximum of 1 to 50, never a total, and nextCursor is null only when there is no further page.
* Every item carries id, url and text.

## Next

* [The operator endpoint contract](https://getanyapi.com/docs/sell/operator-endpoint-contract.md) for signing, budgets, and the live trial.
* [Every API open to sellers](https://getanyapi.com/docs/sell/apis/index.md).
* Want to sell an API that is not on that list? Email [support@getanyapi.com](mailto:support@getanyapi.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.