# TwittrAPI: full documentation

> TwittrAPI is a REST API for public Twitter/X data (tweets, users, followers, search, communities, lists, trends, jobs) from $0.065 per 1,000 API calls. Prepaid, no X developer account needed. Not affiliated with X Corp. Twitter and X are trademarks of X Corp. Only publicly available data is accessible.

This file holds all guides and the full endpoint reference in one Markdown document, generated from the same source as the website.

## Quick facts

- Base URL: https://api.twittrapi.com
- Authentication: header `X-API-KEY: YOUR_API_KEY`. A human signs up at https://www.twittrapi.com/start and passes the key to the agent or app.
- RapidAPI: the same paths on https://twitter-aio.p.rapidapi.com with the headers x-rapidapi-key and x-rapidapi-host.
- Prices (direct): Standard $0.09, Volume $0.075, Scale $0.065 per 1,000 calls. $0.10 free test credit for 14 days.
- Payment: Crypto (USDT or USDC on Polygon, USDT on Tron, or Litecoin) with no surcharge, the cheapest way to pay. Card for one-time top-ups from $20 (9% card fee). USDT on Tron: minimum $50 and a $3 network fee per deposit, deducted from the credit.
- Responses: X's raw JSON by default; add `format=simple` for compact, flat JSON with a fixed schema (same price).
- Machine-readable: OpenAPI 3.1 https://www.twittrapi.com/openapi.json (also https://www.twittrapi.com/openapi.yaml), pricing https://www.twittrapi.com/pricing.json, index https://www.twittrapi.com/llms.txt. Every docs page is also available as Markdown at its URL plus .md.

## Contents

- Introduction
- First request
- SDKs and integrations
- Using TwittrAPI from an AI agent
- Teams, resellers and agent keys
- Hosted MCP server
- Authentication
- Errors
- Rate limits
- Pagination
- Pricing and billing
- Response format
- Simple format
- Account monitoring
- Recipes: mentions, polling and datasets
- No-code: n8n and Make
- API reference
  - Users
  - Tweets
  - Search
  - Lists
  - Communities
  - Jobs
  - Trends
  - Misc
- Account actions

---

## Introduction

Source: https://www.twittrapi.com/docs

> TwittrAPI is a REST API for public Twitter/X data. Send your key in a header, get X's JSON back, pay per call from a prepaid balance.

TwittrAPI is a REST API for public X (Twitter) data: profiles, tweets, followers, search, lists, communities, jobs and trends. You do not need an X developer account. TwittrAPI is not affiliated with X Corp.

### How it works

- **Base URL:** `https://api.twittrapi.com`
- **Auth:** your API key in the `X-API-KEY` header. See [Authentication](https://www.twittrapi.com/docs/helpful-infos/authentication).
- **Requests:** plain HTTPS. Read endpoints are `GET` with path and query parameters.
- **Responses:** the JSON that X's own web app receives, passed through unchanged, with a few documented exceptions. See [Response format](https://www.twittrapi.com/docs/response-format).
- **Billing:** one call is one billed request, from a prepaid balance. Calls that fail with 400, 408, 409 or a 5xx error are refunded. See [Pricing and billing](https://www.twittrapi.com/docs/billing).
- **Limits:** a per-key limit in requests per second. See [Rate limits](https://www.twittrapi.com/docs/helpful-infos/rate-limits).

```bash
curl 'https://api.twittrapi.com/user/by/username/jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

### Where to go next

- [First request](https://www.twittrapi.com/docs/getting-started): get a key and make a call with curl, Python, Node.js or Go.
- [API reference](https://www.twittrapi.com/docs/reference): every read endpoint with parameters, limits, errors and examples.
- [Account actions](https://www.twittrapi.com/docs/account-actions): post, reply, like and follow with your own X account, cookie and proxy.
- [SDKs and integrations](https://www.twittrapi.com/docs/integrations): Python, Node.js and Go clients, an MCP server, n8n, LangChain, LlamaIndex, Apify and Make.
- [For AI agents](https://www.twittrapi.com/docs/for-agents): how an agent gets a key, the MCP server, budget and retry rules. Machine-readable: [OpenAPI spec](https://www.twittrapi.com/openapi.json), [all docs in one Markdown file](https://www.twittrapi.com/llms-full.txt), [pricing as JSON](https://www.twittrapi.com/pricing.json).
- Switching from another provider? Migration guides with endpoint mappings: [from twitterapi.io](https://www.twittrapi.com/guides/migrate-from-twitterapi-io), [from SocialData](https://www.twittrapi.com/guides/migrate-from-socialdata) and [from the official X API](https://www.twittrapi.com/guides/migrate-from-official-x-api).

### What you can and cannot get

The API returns public data only: what a logged-out visitor or any logged-in user could see. Protected accounts, direct messages and other private content are not available through the read endpoints. Account actions work only on the account whose cookie you send.

---

## First request

Source: https://www.twittrapi.com/docs/getting-started

> Get a TwittrAPI key, make your first request with curl, Python, Node.js or Go, and check your balance.

This page takes you from no account to a working request.

Video: [Get your API key and make your first request](https://www.youtube.com/watch?v=ju71Al0KPv0)

### 1. Get a key

Create an account on the [get started page](https://www.twittrapi.com/start) and copy the API key from the dashboard. New accounts get $0.10 of test credit, valid for 14 days, at 3 requests per second. Trial keys can call every read endpoint, but not the [account actions](https://www.twittrapi.com/docs/account-actions). After your first top-up the key gets the Standard rate limit. Prices are on the [pricing page](https://www.twittrapi.com/pricing).

Keep the key out of your code. The SDKs and examples read it from the `TWITTRAPI_API_KEY` environment variable:

```bash
export TWITTRAPI_API_KEY=your_key
```

### 2. Make a request

The base URL is `https://api.twittrapi.com`. Send the key in the `X-API-KEY` header. This looks up the profile of `@jack`:

```bash
curl 'https://api.twittrapi.com/user/by/username/jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

The response is X's JSON for that profile. The numeric user ID is in `data.user.result.rest_id`. Most other endpoints take that ID. For a one-off lookup without code, use the free [user ID finder](https://www.twittrapi.com/tools/twitter-user-id).

#### Python

```bash
pip install twittrapi
```

```python
from twittrapi import TwittrAPI

client = TwittrAPI()  # reads TWITTRAPI_API_KEY
user = client.users.get_by_username("jack")
print(user["data"]["user"]["result"]["rest_id"])
```

#### Node.js

```bash
npm install twittrapi
```

```js
import { TwittrAPI } from "twittrapi";

const client = new TwittrAPI(); // reads TWITTRAPI_API_KEY
const user = await client.users.getByUsername("jack");
console.log(user.data.user.result.rest_id);
```

#### Go

```bash
go get github.com/twittrapi/twittrapi-go
```

```go
client, err := twittrapi.NewClient() // reads TWITTRAPI_API_KEY
if err != nil {
	log.Fatal(err)
}
user, err := client.Users.GetByUsername(context.Background(), "jack")
if err != nil {
	log.Fatal(err)
}
fmt.Println(string(user))
```

The SDKs retry 429 and 5xx responses with backoff and raise typed errors. More tools are on [SDKs and integrations](https://www.twittrapi.com/docs/integrations).

### 3. Check your balance

`GET /balance` returns your balance and price per call. It is not billed.

```bash
curl 'https://api.twittrapi.com/balance' -H 'X-API-KEY: YOUR_API_KEY'
```

Every billed response also carries `X-Remaining-Balance` and `X-Price-Per-Call` headers. See [Pricing and billing](https://www.twittrapi.com/docs/billing).

### Using RapidAPI instead

The same API is sold on [RapidAPI](https://rapidapi.com/viperscores-viperscores-default/api/twitter-aio) with monthly plans, including a free plan for testing. Paths and parameters are the same; only the host and headers change:

```bash
curl 'https://twitter-aio.p.rapidapi.com/user/by/username/jack' \
  --header 'x-rapidapi-key: YOUR_RAPIDAPI_KEY' \
  --header 'x-rapidapi-host: twitter-aio.p.rapidapi.com'
```

The SDKs accept a RapidAPI key too (`TwittrAPI(rapidapi_key=...)`, `new TwittrAPI({ rapidApiKey })`, or the `RAPIDAPI_KEY` variable). Limits and billing for RapidAPI keys are set on RapidAPI; the balance, price tiers and rate-limit tiers on these pages apply to direct keys. The API is also listed on JojAPI; see the [get started page](https://www.twittrapi.com/start).

### Next steps

- [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling): status codes and which ones are charged.
- [Pagination](https://www.twittrapi.com/docs/helpful-infos/cursor): getting more than one page of results.
- [API reference](https://www.twittrapi.com/docs/reference): all endpoints.

---

## SDKs and integrations

Source: https://www.twittrapi.com/docs/integrations

> Official TwittrAPI clients for Python, Node.js and Go, an MCP server for AI agents (local or hosted), and nodes and tools for n8n, LangChain, LlamaIndex, Apify and Make.

All of these are open source under [github.com/twittrapi](https://github.com/twittrapi). They cover the read endpoints; [account actions](https://www.twittrapi.com/docs/account-actions) are plain HTTP calls. Every tool accepts a direct key (`TWITTRAPI_API_KEY`) or a RapidAPI key (`RAPIDAPI_KEY`), and every call they make is one billed API call.

### SDKs

| Language | Install | Repository |
| --- | --- | --- |
| Python 3.9+ | `pip install twittrapi` | [twittrapi-python](https://github.com/twittrapi/twittrapi-python) |
| Node.js 18+ / TypeScript | `npm install twittrapi` | [twittrapi-node](https://github.com/twittrapi/twittrapi-node) |
| Go 1.21+ | `go get github.com/twittrapi/twittrapi-go` | [twittrapi-go](https://github.com/twittrapi/twittrapi-go) |

The three SDKs work the same way: one method per endpoint, X's raw JSON returned unchanged, retries for 429 and 5xx with backoff, typed errors, and a paginator that follows X's bottom cursor.

```python
from twittrapi import TwittrAPI

client = TwittrAPI()  # reads TWITTRAPI_API_KEY
tweets = client.users.tweets(username="jack", count=20)
```

```js
import { TwittrAPI } from "twittrapi";

const client = new TwittrAPI(); // reads TWITTRAPI_API_KEY
const tweets = await client.users.tweets({ username: "jack", count: 20 });
```

```go
client, _ := twittrapi.NewClient() // reads TWITTRAPI_API_KEY
user, err := client.Users.GetByUsername(context.Background(), "jack")
```

Endpoints without a wrapper method can be called through the Node.js client with `client.get({ path, query })`, or with any HTTP client. Each [reference page](https://www.twittrapi.com/docs/reference) shows both.

### AI agents

| Tool | Install | Repository |
| --- | --- | --- |
| Hosted MCP server (Claude.ai, ChatGPT, Claude Code, Cursor, VS Code) | Nothing to install: `https://mcp.twittrapi.com/mcp` | [Hosted MCP server](https://www.twittrapi.com/docs/hosted-mcp) |
| MCP server, local (stdio) | `npx -y twittrapi-mcp` | [twittrapi-mcp](https://github.com/twittrapi/twittrapi-mcp) |
| LangChain / LangGraph | `pip install -U langchain-twittrapi` | [langchain-twittrapi](https://github.com/twittrapi/langchain-twittrapi) |
| LlamaIndex | `pip install -U llama-index-tools-twittrapi` | [llama-index-tools-twittrapi](https://github.com/twittrapi/llama-index-tools-twittrapi) |

All agent tools are read-only and return the [simple format](https://www.twittrapi.com/docs/simple-format) unless you ask for raw JSON.

#### Hosted MCP server

The quickest way to connect an AI app: the tools run at `https://mcp.twittrapi.com/mcp`, so nothing has to be installed. Apps sign in with your TwittrAPI account (OAuth) or send your API key in a header, and every call is billed to your balance. Full guide: [Hosted MCP server](https://www.twittrapi.com/docs/hosted-mcp).

Claude Code:

```bash
claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp
```

| Client | Setup |
| --- | --- |
| Claude.ai, Claude Desktop | Settings > Connectors > Add custom connector, URL `https://mcp.twittrapi.com/mcp`, then Connect and allow access |
| ChatGPT | Settings > Apps & Connectors > Advanced settings > Developer mode, then Create with the URL above and OAuth |
| Claude Code | `claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp` (OAuth in `/mcp`, or add `--header "Authorization: Bearer $TWITTRAPI_API_KEY"`) |
| Cursor | `"url": "https://mcp.twittrapi.com/mcp"` with `"headers": { "X-API-KEY": "${env:TWITTRAPI_API_KEY}" }` in `mcp.json` |
| VS Code | `.vscode/mcp.json`: `{ "servers": { "twittrapi": { "type": "http", "url": "https://mcp.twittrapi.com/mcp" } } }` |

Connected apps are listed in the dashboard under **Connected apps**, where you can disconnect them.

#### Local MCP server (stdio)

For clients that only start local servers, or to keep the server on your own machine, run the [twittrapi-mcp](https://github.com/twittrapi/twittrapi-mcp) package. It needs Node.js 20 or newer and your key.

Claude Code:

```bash
claude mcp add twittrapi --env TWITTRAPI_API_KEY=your-key -- npx -y twittrapi-mcp
```

Claude Desktop (`claude_desktop_config.json`) and Cursor (`~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "twittrapi": {
      "command": "npx",
      "args": ["-y", "twittrapi-mcp"],
      "env": { "TWITTRAPI_API_KEY": "your-key" }
    }
  }
}
```

- Set `TWITTRAPI_RESPONSE_FORMAT=raw` for raw JSON from the whole server, or pass `format` per tool call.
- Every tool takes `max_chars` (default 50,000). Longer output is cut with a `[TRUNCATED …]` marker; the cursor is printed above the JSON so it is never lost.
- Errors come back as tool errors (402 balance, 429 rate limit, 404 not found), so the model can react.

To run your own shared server for a team (for example inside a company network), start it in HTTP mode. It then ignores its own environment key and uses the key in each request (`X-API-KEY` or `Authorization: Bearer`), so usage is billed to each caller. Put it behind HTTPS if it is reachable from the internet.

```bash
# One server for the team; each request carries the caller's own key
npx -y twittrapi-mcp --http --port 3333 --host 0.0.0.0

claude mcp add --transport http twittrapi https://your-host.example.com/mcp \
  --header "X-API-KEY: your-key"
```

#### LangChain and LlamaIndex

Both packages are built on the Python SDK, return the simple format and cut each result at `max_chars` (default 20,000). The SDK underneath retries 429 and 5xx with backoff. They work with any chat model that supports tool calling.

```python
from langchain.agents import create_agent
from langchain_twittrapi import TwittrAPIToolkit

# A small tool set keeps the model's choice simple; results are the simple format
tools = TwittrAPIToolkit(max_chars=8_000).get_tools(
    ["search_tweets", "get_tweet", "get_user_tweets"]
)

agent = create_agent(
    "anthropic:claude-opus-5-5",
    tools=tools,
    system_prompt="You research X with the tools you have. Quote tweet IDs for every claim.",
)
```

```python
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.anthropic import Anthropic
from llama_index.tools.twittrapi import TwittrAPIToolSpec

agent = FunctionAgent(
    tools=TwittrAPIToolSpec(max_chars=8_000).to_tool_list(
        spec_functions=[("search_tweets", "asearch_tweets"), ("get_tweet", "aget_tweet")]
    ),
    llm=Anthropic(model="claude-opus-5-5"),
)
```

For raw JSON (cards, polls, edit history, community notes) pass `api_response_format="raw"` to `TwittrAPIToolkit` or `TwittrAPIToolSpec`. The option has that name because LangChain tools already have a `response_format` field.

#### Your own agent tool

If you run your own agent loop, the SDKs return the simple format when you ask for it (`response_format` in Python, `format` in Node.js, `WithFormat` in Go). Keep only the fields the agent needs and the result shrinks further.

```python
from twittrapi import TwittrAPI

client = TwittrAPI(response_format="simple")  # key from TWITTRAPI_API_KEY

def search_x(query: str, cursor: str | None = None) -> dict:
    """Search recent public tweets on X. Supports X operators such as
    from:user, lang:en, since:2026-10-01, min_faves:100, -filter:replies."""
    page = client.search.tweets(query, category="Latest", cursor=cursor)
    tweets = [
        {"id": t["id"], "author": (t["author"] or {}).get("username"),
         "createdAt": t["createdAt"], "text": t["text"], "likes": t["counts"]["likes"]}
        for t in page["data"]
        if not t.get("unavailable")
    ]
    return {"tweets": tweets, "nextCursor": page["nextCursor"]}
```

```ts
import { TwittrAPI, isUnavailableTweet } from "twittrapi";

const client = new TwittrAPI({ format: "simple" }); // key from TWITTRAPI_API_KEY

export async function searchX(query: string, cursor?: string) {
  const page = await client.search.tweets({ query, category: "Latest", cursor });
  return {
    tweets: page.data
      .filter((t) => !isUnavailableTweet(t))
      .map((t) => ({ id: t.id, author: t.author?.username, createdAt: t.createdAt, text: t.text, likes: t.counts.likes })),
    nextCursor: page.nextCursor,
  };
}
```

```go
client, err := twittrapi.NewClient(twittrapi.WithFormat(twittrapi.FormatSimple))
if err != nil {
	log.Fatal(err)
}
page, err := twittrapi.FetchSimplePage[twittrapi.SimpleTweet](ctx, client.Search.Tweets,
	twittrapi.SearchParams{Query: "from:XDevelopers", Category: twittrapi.CategoryLatest})
if err != nil {
	return err
}
for _, t := range page.Data {
	fmt.Println(t.ID, t.Text, t.Counts.Likes)
}
```

To keep an agent from burning through your balance: give it a small tool set, set `max_chars` low, cap the number of pages in the system prompt, and keep the balance small. At zero balance calls return 402 and are not charged. Example prompts and token sizes: [Twitter/X data for AI agents](https://www.twittrapi.com/use-cases/ai-agents). Agents that sign themselves up or pay on their own: [For AI agents](https://www.twittrapi.com/docs/for-agents).

### Automation platforms

| Platform | How to add it | Repository |
| --- | --- | --- |
| n8n | Settings > Community Nodes > Install > `n8n-nodes-twittrapi` | [n8n-nodes-twittrapi](https://github.com/twittrapi/n8n-nodes-twittrapi) |
| Make | Custom app; setup in the repository | [twittrapi-make](https://github.com/twittrapi/twittrapi-make) |
| Apify | Actor that returns flat JSON, CSV or Excel; runs on Apify's billing, no TwittrAPI key needed | [twittrapi-apify](https://github.com/twittrapi/twittrapi-apify) |

The n8n node can also be attached to an AI Agent node as a tool. On self-hosted n8n that needs `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true`. Step-by-step workflows for n8n and Make: [No-code: n8n and Make](https://www.twittrapi.com/docs/no-code).

### Examples

[github.com/twittrapi/examples](https://github.com/twittrapi/examples) has short, runnable programs in Python, Node.js, Go and shell: export followers to CSV, monitor a keyword into Slack, trends by country, replies to a tweet and more. Each folder has its own README.

```bash
git clone https://github.com/twittrapi/examples.git twittrapi-examples
```

---

## Using TwittrAPI from an AI agent

Source: https://www.twittrapi.com/docs/for-agents

> How an AI agent gets a TwittrAPI key, connects through the MCP server or plain HTTP, keeps responses small with the simple format, stays inside a budget and handles errors and retries.

This page is for AI agents (coding agents, browsing agents, MCP clients) and for the people who set them up. Everything here is also available as plain text for machines:

| What | URL |
| --- | --- |
| All docs and every endpoint in one Markdown file | [/llms-full.txt](https://www.twittrapi.com/llms-full.txt) |
| OpenAPI 3.1 spec (also [YAML](https://www.twittrapi.com/openapi.yaml)) | [/openapi.json](https://www.twittrapi.com/openapi.json) |
| Prices, rate limits and payment methods as JSON | [/pricing.json](https://www.twittrapi.com/pricing.json) |
| Short index of the site | [/llms.txt](https://www.twittrapi.com/llms.txt) |
| Any docs page as Markdown | the page URL plus `.md`, e.g. [/docs/reference/search.md](https://www.twittrapi.com/docs/reference/search.md), or `Accept: text/markdown` |

### Getting a key

There are three ways. The first always works; the other two let an agent get access without the website sign-up.

1. **A person gives the agent a key.** A person signs up on the [get started page](https://www.twittrapi.com/start). New accounts get $0.10 of test credit, valid for 14 days, at 3 requests per second. They copy the API key from the dashboard (or create a separate [agent key](https://www.twittrapi.com/docs/api-keys) with its own budget) and give it to the agent, normally as the environment variable `TWITTRAPI_API_KEY`. Do not paste keys into prompts, logs or files that are committed.
2. **The agent asks, a person approves** ([agent sign-up](#agent-sign-up)). Available.
3. **The agent buys access with crypto**, no person and no third party involved: it gets its own deposit address, pays USDC, USDT or LTC and picks up its key ([buy access with crypto](#buy-access-with-crypto-no-human)). Available.

The agent sends the key in the `X-API-KEY` header on every request to `https://api.twittrapi.com`.

A RapidAPI key works too: the same paths on `twitter-aio.p.rapidapi.com` with the `x-rapidapi-key` and `x-rapidapi-host` headers. See [Authentication](https://www.twittrapi.com/docs/helpful-infos/authentication).

Pay per request without any account, via JojAPI: search only, $0.01 per request. JojAPI offers this for AI agents on `GET /search/{searchTerm}`; see [JojAPI](https://jojapi.com/hub/api/twitter-x-aio).

The "Try it" box on the homepage is for people: it is protected by a captcha and only shows one profile lookup. Agents should call the API with a key instead.

### Agent sign-up

Available. The agent asks for a key in the name of a person; the person approves it by email and chooses a budget. It works like the device login of a TV app ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)):

1. The agent calls `POST https://app.twittrapi.com/api/agent/signup` with the person's email, its own name and what it needs the key for.
2. We email the person a link (it works once) and a short code. The codes expire after 15 minutes.
3. The person signs in or creates an account, sees the agent's name, purpose and the country the request came from, chooses a budget (a monthly cap from their balance, or a fixed amount) and approves or denies.
4. The agent polls `POST https://app.twittrapi.com/api/agent/token` with its `device_code` every `interval` seconds and receives its own key **once**.

The key is an "agent" key under the person's account: billed to their balance, limited to the budget they chose, without account actions (`/client/*`, `/premium/*`). The person sees it under **API keys** and can pause or delete it at any time.

```bash
# 1. Ask for a key (the person gets an email)
curl -s https://app.twittrapi.com/api/agent/signup \
  -H 'Content-Type: application/json' \
  -d '{"email":"owner@example.com","agent_name":"Research agent","purpose":"Collect public posts about our launch"}' \
  | tee signup.json
# -> {"device_code":"…","user_code":"BCDF-GHJK","verification_uri":"https://app.twittrapi.com/agent",
#     "verification_uri_complete":"https://app.twittrapi.com/agent?code=BCDF-GHJK","expires_in":900,"interval":5}

# 2. Tell the person: "Approve me via the email from TwittrAPI, or enter code BCDF-GHJK at https://app.twittrapi.com/agent"

# 3. Poll until they decide
DEVICE_CODE=$(jq -r .device_code signup.json); INTERVAL=$(jq -r .interval signup.json)
while true; do
  sleep "$INTERVAL"
  RES=$(curl -s https://app.twittrapi.com/api/agent/token -d "device_code=$DEVICE_CODE")
  case "$(echo "$RES" | jq -r '.error // "ok"')" in
    ok) echo "$RES" | jq -r .api_key > ~/.twittrapi_key; break ;;
    authorization_pending) ;;
    slow_down) INTERVAL=$((INTERVAL + 5)) ;;
    *) echo "$RES"; exit 1 ;;   # access_denied, expired_token, invalid_grant
  esac
done
```

| Poll answer | What to do |
| --- | --- |
| `authorization_pending` (400) | Keep polling every `interval` seconds. |
| `slow_down` (400) | Add 5 seconds to the interval. |
| `access_denied` (400) | The person said no. Stop. |
| `expired_token` (400) | Too late. Start a new sign-up. |
| `invalid_grant` (400) | Unknown code, or the key was already picked up. Keys are handed out once. |
| 200 | `api_key` (store it securely), `budget`, `base_url`, `balance_url`. |

Sign-ups are limited per address and per email address. Disposable email addresses cannot approve agents.

### Buy access with crypto (no human)

Available. An agent that holds crypto can buy its own access without a person, an email or any third party. It asks for an amount ($5, $20, $50) and a chain, gets an anonymous account with **its own deposit address**, pays, and collects its API key once the payment is credited. It is the same deposit system customers use to top up; nobody else touches the money.

1. `POST https://app.twittrapi.com/api/agent/purchase` with `{"amount_usd": 5, "chain": "polygon", "token": "USDC"}`. The answer has `purchase_id`, `claim_secret` (shown **once**, store it), `address`, `chain`, `token`, `amount`, `expires_at` (24 hours) and `poll_url`.
2. Send the amount to `address` on that chain before `expires_at`.
3. Poll `POST https://app.twittrapi.com/api/agent/purchase/status` with `{"purchase_id": …, "claim_secret": …}` every 15 seconds: `pending` (with confirmation progress), then `credited` with the `api_key` **exactly once**; later polls only say `credited`. `expired` means nothing arrived in time.

| Chain (`chain`) | Tokens (`token`) | Minimum per deposit | Network fee |
| --- | --- | --- | --- |
| `polygon` (default) | `USDC` (default), `USDT` | $5 | none |
| `tron` | `USDT` | $50 (smaller amounts are refused) | $3, deducted from the credit |
| `ltc` | none, send LTC worth the amount | $5 | none |

```bash
# 1. Create the purchase (store the answer: claim_secret is shown once)
curl -s https://app.twittrapi.com/api/agent/purchase \
  -H 'Content-Type: application/json' \
  -d '{"amount_usd":5,"chain":"polygon","token":"USDC"}' | tee purchase.json
# -> {"purchase_id":"pur_…","claim_secret":"cs_…","address":"0x…","chain":"polygon","token":"USDC",
#     "amount":"5","expires_at":"…","poll_url":"https://app.twittrapi.com/api/agent/purchase/status",…}

# 2. Pay: send 5 USDC on Polygon to .address (with your own wallet)

# 3. Poll until credited, then keep the key
while true; do
  RES=$(curl -s https://app.twittrapi.com/api/agent/purchase/status -H 'Content-Type: application/json' \
    -d "$(jq '{purchase_id, claim_secret}' purchase.json)")
  case "$(echo "$RES" | jq -r .status)" in
    credited) echo "$RES" | jq -r .api_key > ~/.twittrapi_key; break ;;
    pending) sleep 15 ;;
    *) echo "$RES"; exit 1 ;;   # expired, or an error
  esac
done
```

```python
import json, time, urllib.request

APP = "https://app.twittrapi.com"

def post(path, body):
    req = urllib.request.Request(APP + path, data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req) as r:
        return json.load(r)

p = post("/api/agent/purchase", {"amount_usd": 5, "chain": "polygon", "token": "USDC"})
# Store p["claim_secret"] now. Then pay p["amount"] p["token"] on p["chain"] to p["address"].
print(f'Send {p["amount"]} {p["token"]} on {p["network"]} to {p["address"]} before {p["expires_at"]}')

while True:
    s = post("/api/agent/purchase/status",
             {"purchase_id": p["purchase_id"], "claim_secret": p["claim_secret"]})
    if s["status"] == "credited":
        api_key = s["api_key"]  # shown once: store it securely
        break
    if s["status"] == "expired":
        raise SystemExit("expired: nothing arrived in time")
    time.sleep(15)
```

The account: 3 requests per second, at most $100 balance, no account actions (`/client/*`, `/premium/*`), the Standard price ($0.09 per 1,000 calls) and a review by us. To top it up, pay again to the same address: it stays with the account for good. For more, a person can sign up normally.

**What gets credited.** What arrives is credited, whatever the amount: an underpayment is credited as it is, an overpayment too, both minus the chain's network fee (Tron $3). A payment below the chain minimum waits until the address has received the minimum in total. A payment that would push the balance over $100 is not credited automatically; it is held for review (the status says `held_for_review`), write to support with the `purchase_id`. Payments are credited after the chain's confirmations; send only the token and chain you chose.

**Expiry.** An unpaid purchase expires after 24 hours and its account is switched off. The address is never given to anyone else: if a payment still arrives later, it is credited to that account and the status turns `credited`. Do not count on that, start a new purchase instead.

| Answer | Meaning |
| --- | --- |
| 201 | Purchase created: address, amount, `claim_secret` (once), `expires_at`. |
| 200 `pending` | Waiting for the payment, or confirming it: `payment.state` (`waiting`, `confirming`, `below_minimum`, `held_for_review`), `confirmations` of `confirmations_required`. |
| 200 `credited` | Paid: `api_key` the first time, then only the status. |
| 200 `expired` | Nothing arrived before `expires_at`. |
| 400 | Invalid amount, chain or token, or an amount below the chain minimum (Tron: $50). |
| 404 | Unknown `purchase_id` or wrong `claim_secret`. |
| 429 | Too many purchases from this address, or too many unpaid ones open. |
| 503 | Temporarily unavailable. Try again; nothing was reserved. |

### Connecting through MCP

The MCP server [twittrapi-mcp](https://github.com/twittrapi/twittrapi-mcp) gives Claude, Cursor and any other MCP client read-only tools for profiles, tweets, followers, search, lists, communities and trends. It needs Node.js 20 or newer. Every tool call is one billed API call.

Claude Code:

```bash
claude mcp add twittrapi --env TWITTRAPI_API_KEY=your-key -- npx -y twittrapi-mcp
```

Claude Desktop (`claude_desktop_config.json`) and Cursor (`~/.cursor/mcp.json` or `.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "twittrapi": {
      "command": "npx",
      "args": ["-y", "twittrapi-mcp"],
      "env": { "TWITTRAPI_API_KEY": "your-key" }
    }
  }
}
```

Other clients: any client that can start a stdio server works with the command `npx -y twittrapi-mcp`. For a shared server, `npx -y twittrapi-mcp --http --port 3333` serves Streamable HTTP at `/mcp`; each request then carries the caller's own key in `X-API-KEY` (or `Authorization: Bearer`).

| Variable | Meaning |
| --- | --- |
| `TWITTRAPI_API_KEY` | Your direct key (sent as `X-API-KEY`) |
| `RAPIDAPI_KEY` | RapidAPI key, used when `TWITTRAPI_API_KEY` is not set |
| `TWITTRAPI_RESPONSE_FORMAT` | `simple` (default) or `raw` |

Tool output is capped by `max_chars` (default 50,000); paginated tools print `next_cursor` above the JSON so truncation never removes it. LangChain and LlamaIndex toolkits are listed under [SDKs and integrations](https://www.twittrapi.com/docs/integrations).

#### Hosted MCP server

Agents whose app supports remote MCP servers can connect by URL instead of running the package: `https://mcp.twittrapi.com/mcp` (Streamable HTTP, same tools, simple format by default). Two ways to authenticate:

- **OAuth** for Claude.ai, Claude Desktop, ChatGPT, Claude Code and VS Code: the app registers itself, the person logs in to TwittrAPI and clicks **Allow access**. The agent never sees the API key; calls are billed to the person's balance.
- **API key header** for Cursor, scripts and any client that can set headers: `Authorization: Bearer YOUR_API_KEY` or `X-API-KEY: YOUR_API_KEY`.

```bash
# Claude Code, OAuth
claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp
# Claude Code, API key
claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp --header "Authorization: Bearer $TWITTRAPI_API_KEY"
```

Claude.ai: Settings > Connectors > Add custom connector with the URL. ChatGPT: developer mode, then create a connector with the URL and OAuth. Cursor: `"url"` plus `"headers": { "X-API-KEY": "${env:TWITTRAPI_API_KEY}" }` in `mcp.json`. VS Code: `"type": "http"` with the URL in `.vscode/mcp.json`. Discovery for MCP clients: `https://mcp.twittrapi.com/.well-known/oauth-protected-resource/mcp` (RFC 9728). Details, limits and how to disconnect an app: [Hosted MCP server](https://www.twittrapi.com/docs/hosted-mcp).

### Calling the API directly

Without MCP, any HTTP client works. Read endpoints are `GET` with path and query parameters:

```bash
curl 'https://api.twittrapi.com/search?searchItem=from%3AXDevelopers&category=Latest&format=simple' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

The [OpenAPI spec](https://www.twittrapi.com/openapi.json) lists every endpoint with its parameters, limits and error responses; it can be loaded as a tool definition by agent frameworks that read OpenAPI. The [endpoint reference](https://www.twittrapi.com/docs/reference) has the same information per page.

### Keep responses small: the simple format

X's raw JSON is large and deeply nested. Add `format=simple` (or the header `X-Response-Format: simple`) and every read endpoint returns flat camelCase JSON with a fixed schema and the same envelope:

```json
{ "data": [ ... ], "nextCursor": "…" | null, "previousCursor": "…" | null, "hasNextPage": true | false }
```

That is about 60 % fewer input tokens, at the same price. Stop paginating when `hasNextPage` is `false`. The MCP server uses the simple format by default. Details and field lists: [Simple format](https://www.twittrapi.com/docs/simple-format).

### Budget control

The balance is prepaid, so an agent can never spend more than what was topped up. To keep tighter control:

- Top up only the amount the task should cost. Calls cost $0.09 per 1,000 at the Standard tier, less at Volume and Scale.
- Read the `X-Remaining-Balance` and `X-Price-Per-Call` headers on every billed response, and stop below your own threshold.
- `GET https://api.twittrapi.com/balance` returns the balance; it is free and has its own limit.
- When the balance is used up, calls return **402** and are not charged. Do not retry a 402; ask the person to top up.
- One call returns one page. Paginating a timeline with 1,000 items at about 20 per page is about 50 calls. Set a page limit before you loop.

Prices and tiers: [Pricing and billing](https://www.twittrapi.com/docs/billing) and [/pricing.json](https://www.twittrapi.com/pricing.json).

### Errors and retries

Use the status code, not the message text.

| Status | What the agent should do |
| --- | --- |
| 401, 403 | Stop. The key is missing, wrong, deactivated or not allowed for this endpoint. |
| 402 | Stop and ask for a top-up. Not charged. |
| 404 | The user, tweet or object does not exist (or is private/suspended). Do not retry. |
| 422 | A parameter is invalid (non-numeric ID, bad handle, unsupported search operator). Fix the input. |
| 400 | Retry once at most, then check the parameters. Refunded. |
| 408, 409, 500, 502, 503, 504 | Retry with exponential backoff (0.5 s, 1 s, 2 s, a few tries). Refunded. |
| 429 | Rate limit of the key. Back off and slow down; there is no `Retry-After` header. Not charged. |

Calls that fail with 400, 408, 409 or 5xx are refunded at once (`X-Refunded: true`). Empty timelines are not errors: X answers 200 with no entries, for example for likes, which X made private. Full table: [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling). Rate limits per tier: [Rate limits](https://www.twittrapi.com/docs/helpful-infos/rate-limits).

### Rules for agents

- The API is read-only for public data. [Account actions](https://www.twittrapi.com/docs/account-actions) (posting, liking, following) need the session cookie of the person's own X account and their own proxy; do not use them unless the person explicitly set that up.
- Respect X's rules and the [acceptable use policy](https://www.twittrapi.com/aup). Protected accounts and private data are not available.
- TwittrAPI is not affiliated with X Corp.

---

## Teams, resellers and agent keys

Source: https://www.twittrapi.com/docs/api-keys

> Give team members, your own clients and AI agents separate API keys under one account, each with its own budget, limits, usage and CSV export. Billed to your balance at your price.

{/* Dashboard: TwitterDash app/(customer)/api-key, lib/api-keys.ts. Gateway: TwitterPayment subkeys.go (sub-keys with parent_user_id). */}

Available in the dashboard under API keys.

Your account has one **main key**: the owner key with your full balance and rate limit. Next to it you can create up to 25 **additional keys** under **API keys** in the dashboard (more on request). Use them for:

| Type | For |
| --- | --- |
| Team member | A colleague or one of your own services. Revoke it when they leave, without touching the main key. |
| Client (reseller) | A customer of yours. They never see your balance or your other keys. |
| AI agent | An agent or automation with a hard budget. Agents can also ask for a key themselves; see [For AI agents](https://www.twittrapi.com/docs/for-agents#agent-sign-up). |

### Budget per key

Each key gets one of two budgets when you create it:

- **Monthly spending cap.** Calls are paid from your balance, up to the cap each calendar month (UTC). When the cap is reached, the key gets **402** until the next month or until you raise the cap. Leave the cap empty to draw from the balance without a limit.
- **Fixed budget.** The amount moves from your balance to the key at once. When it is used up, the key gets **402**. You can add more later; when you delete the key, what is left goes back to your balance.

Calls are always billed to your account **at your price** (Standard, Volume or Scale). There is no markup on our side: resellers set their own prices to their clients and bill them themselves (the CSV export below helps). Reselling X data? See the [Resellers](https://www.twittrapi.com/resellers) page.

The `X-Remaining-Balance` header of a key with a monthly cap shows what is left of the cap, never your account balance.

### Limits per key

| Setting | Default | |
| --- | --- | --- |
| Rate limit | your account's limit | At most your account's limit. All keys together share your account's limit, so more keys do not add throughput. |
| Allowed endpoints | every read endpoint | Path prefixes such as `/search/` or `/user/`. |
| Expiry | never | The key stops working at the end of that day (UTC). |
| Account actions | off | `/client/*` and `/premium/*` (posting, liking, following with X accounts) are blocked unless you allow them for that key. |

A key also stops whenever your account cannot call the API (trial ended, account blocked).

### Manage keys

The **API keys** page lists every key with its type, creation date, last use, calls and spend this month and in the last 30 days, a bar for its budget and its status. Per key you can:

- **Rotate**: a new key is shown once; the old key stops working at once, like the main key rotation.
- **Pause / resume**: a paused key gets **403** until you resume it.
- **Edit limits**: name, cap or extra budget, rate limit, endpoints, expiry, account actions.
- **Delete**: the key stops at once; a fixed budget that is left goes back to your balance. Its usage stays available for the export.

A new or rotated key is shown **once**. Copy it then; we cannot show it again.

### Usage and CSV export

Each key has a detail page with calls and spend per day for the last 30 days, the top endpoint groups, and the error rate.

**Export for invoicing** downloads one CSV per key and month (UTC):

```csv
date,endpoint_group,calls,cost_usd
2026-10-01,search,1250,0.081250
2026-10-01,user,310,0.020150
total,,1560,0.101400
```

`calls` are billed calls (refunded failures are not counted), `cost_usd` is what they cost at your price.

### Errors of an additional key

| Status | Meaning |
| --- | --- |
| 401 | The key is wrong, rotated or deleted. |
| 402 | Monthly cap reached, fixed budget used up, or your account balance is empty. |
| 403 | The key is paused or expired, or the endpoint is not allowed for it (for example `/client/*`). |
| 429 | The key's own rate limit or your account's shared limit. |

Resellers and teams with special needs: write to [support@twittrapi.com](mailto:support@twittrapi.com).

---

## Hosted MCP server

Source: https://www.twittrapi.com/docs/hosted-mcp

> Connect Claude, ChatGPT, Claude Code, Cursor or VS Code to TwittrAPI by URL. Nothing to install; sign in with your TwittrAPI account (OAuth) or send your API key in a header.

{/* Server: twittrapi-mcp --http (Streamable HTTP, stateless). OAuth authorization server: TwitterDash /oauth/* and /.well-known/oauth-authorization-server. */}

The hosted MCP server gives AI apps the same read-only tools as the [twittrapi-mcp](https://github.com/twittrapi/twittrapi-mcp) package (profiles, tweets, followers, search, lists, communities, trends), without installing anything:

```text
https://mcp.twittrapi.com/mcp
```

Use the URL exactly like this, **with `/mcp` at the end**. `https://mcp.twittrapi.com` on its own is not the MCP endpoint; apps report "404 Not found" or cannot find the sign-in for it.

It speaks MCP over Streamable HTTP. Every tool call is one billed API call on your TwittrAPI balance, at your normal price and rate limit. Results use the [simple format](https://www.twittrapi.com/docs/simple-format) by default.

Video: [Connect your AI agent: hosted MCP server and agent skill](https://www.youtube.com/watch?v=sI4Ma2LQGug)

There are two ways to sign in:

| | How | For |
| --- | --- | --- |
| **OAuth** | The app opens a TwittrAPI page, you log in and click **Allow access**. The app never sees your API key. | Claude.ai, Claude Desktop, ChatGPT, Claude Code, VS Code |
| **API key header** | The app sends `Authorization: Bearer YOUR_API_KEY` or `X-API-KEY: YOUR_API_KEY` on every request. | Cursor, Claude Code, scripts, any client that lets you set headers |

### Claude.ai and Claude Desktop

1. Open **Settings > Connectors** and choose **Add custom connector**. On Team and Enterprise plans an owner adds it under the organization's connector settings first.
2. Name: `TwittrAPI`, URL: `https://mcp.twittrapi.com/mcp` (with `/mcp` at the end). Leave the advanced OAuth fields empty.
3. Click **Connect**, log in to your TwittrAPI account and click **Allow access**.

Claude Desktop uses the connectors of your Claude account, so the connector shows up there too.

### ChatGPT

1. Open **Settings > Apps & Connectors > Advanced settings** and turn on **Developer mode** (available on the plans that offer custom connectors).
2. Choose **Create**, enter the name `TwittrAPI`, the MCP server URL `https://mcp.twittrapi.com/mcp` and authentication **OAuth**.
3. Log in to your TwittrAPI account and click **Allow access**.

### Claude Code

With OAuth (Claude Code opens the browser when you authenticate in `/mcp`):

```bash
claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp
```

With your API key instead:

```bash
claude mcp add --transport http twittrapi https://mcp.twittrapi.com/mcp \
  --header "Authorization: Bearer $TWITTRAPI_API_KEY"
```

### Cursor

Add this to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project). Cursor reads the key from your environment:

```json
{
  "mcpServers": {
    "twittrapi": {
      "url": "https://mcp.twittrapi.com/mcp",
      "headers": { "X-API-KEY": "${env:TWITTRAPI_API_KEY}" }
    }
  }
}
```

### VS Code (GitHub Copilot agent mode)

Add `.vscode/mcp.json` to the workspace. VS Code signs you in with OAuth when the server starts:

```json
{
  "servers": {
    "twittrapi": {
      "type": "http",
      "url": "https://mcp.twittrapi.com/mcp"
    }
  }
}
```

To use your API key instead, add an input so the key is not stored in the file:

```json
{
  "inputs": [
    { "type": "promptString", "id": "twittrapi-key", "description": "TwittrAPI API key", "password": true }
  ],
  "servers": {
    "twittrapi": {
      "type": "http",
      "url": "https://mcp.twittrapi.com/mcp",
      "headers": { "X-API-KEY": "${input:twittrapi-key}" }
    }
  }
}
```

### Other clients

Any MCP client with Streamable HTTP support works. Send JSON-RPC as `POST` to `https://mcp.twittrapi.com/mcp` with your key in `X-API-KEY` or `Authorization: Bearer`. The server is stateless: there are no sessions and no server-sent event stream (`GET` returns 405), and the old SSE transport is not offered. Clients that only support stdio can use the [npm package](https://github.com/twittrapi/twittrapi-mcp) instead (`npx -y twittrapi-mcp`).

### What you approve, and how to disconnect

The consent page shows the app's name as the app registered it, the address the app receives the sign-in at, and that its calls are billed to your balance. Only continue if you started the connection yourself.

- Your apps are listed under **Connected apps** in the dashboard, with when they were connected and last used. **Disconnect** ends an app's access within a minute.
- Regenerating your API key disconnects every app. Reconnect them afterwards.
- Access tokens last one hour and are renewed by the app with a refresh token, which works once and expires after 30 days without use.
- The server only accepts tokens issued for it. Apps can register themselves (OAuth dynamic client registration) and must use PKCE; sign-in addresses must be `https` or a local address on your own computer.

### Limits and errors

- The same [rate limit](https://www.twittrapi.com/docs/helpful-infos/rate-limits) as your key applies, plus at most 8 requests in flight per key on the MCP server.
- A request may take up to 90 seconds and carry up to 256 KB.
- Tool errors come back as readable tool results: **402** means the balance is used up (top up, do not retry), **429** means slow down. Details: [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling).

### Troubleshooting

| Message | Fix |
| --- | --- |
| "Connecting to the server: 404 Not found" or "Couldn't determine how this server signs in" | The URL is missing `/mcp`. Remove the connector and add it again with `https://mcp.twittrapi.com/mcp`. Do not continue with manual configuration. |
| The login page says "Could not reach the server" | Reload the login page and log in again. |
| The app says it is not authorized after it worked before | The app was disconnected or your API key was regenerated. Remove the connector and connect it again. |

To run your own instance (for example inside a company network), see "HTTP mode" in the [twittrapi-mcp README](https://github.com/twittrapi/twittrapi-mcp).

---

## Authentication

Source: https://www.twittrapi.com/docs/helpful-infos/authentication

> Send your TwittrAPI key in the X-API-KEY header. What happens when the key is missing, unknown, deactivated or out of balance.

Every request needs your API key in the `X-API-KEY` header. There is no OAuth flow and no X developer account involved.

```bash
curl 'https://api.twittrapi.com/simpletweet/20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

The key comes from the dashboard after you sign up on the [get started page](https://www.twittrapi.com/start). RapidAPI keys use different headers and a different host; see [First request](https://www.twittrapi.com/docs/getting-started#using-rapidapi-instead).

### Keep the key secret

The key spends your balance. Keep it on the server side:

- Read it from an environment variable or a secret store. The SDKs read `TWITTRAPI_API_KEY`.
- Do not put it in browser or mobile app code, where anyone can extract it. Call the API from your backend instead.
- If a key leaks, use "Regenerate key" on the API key page of the dashboard. The old key stops working at once; balance and settings stay.

### Auth errors

These are answered by the gateway before anything is charged:

| Status | Body (`error`) | Meaning |
| --- | --- | --- |
| 401 | `missing api key` | No `X-API-KEY` header. |
| 401 | `user not found` | The key is not known. Check for typos and stray spaces. |
| 402 | `No balance left. Please top up to continue using the API.` | Your balance cannot cover the call. |
| 402 | `Your trial has expired. Please upgrade to continue using the API.` | The trial period ended before a top-up. |
| 403 | `This API key is deactivated. Please contact support.` | The key was turned off. |
| 403 | `This endpoint is not included in your plan.` | The key may not call this path. Trial keys cannot call `/client/*` and `/premium/*`. |

All error bodies are JSON objects with an `error` string. The full list is on [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling).

### Account actions need more than the key

The [account actions](https://www.twittrapi.com/docs/account-actions) (`/client/*`, `/premium/*`) act on your own X account. Besides the API key they need the cookie of your X session and your own proxy in the JSON body. Read endpoints need only the key.

---

## Errors

Source: https://www.twittrapi.com/docs/helpful-infos/error-handling

> TwittrAPI status codes, error bodies, which errors are charged and refunded, and which ones to retry.

Errors come back as JSON with an `error` string:

```json
{ "error": "Invalid tweet ID" }
```

Some errors add fields: `errorInfo` (X's raw reply, for example on `/username/to/id`), `attempts` (search retries) or `errorCode`. Do not parse the message text; it can change. Use the status code.

### Status codes

"Charged" means the call costs one call's price. "Refunded" means it was charged and the amount was put back right away; the response then has the header `X-Refunded: true`. "Not charged" means the request was rejected before billing.

| Status | When | Billing |
| --- | --- | --- |
| 200 | Success. | Charged |
| 400 | A required parameter is missing or malformed (for example no `listId`, or more than 100 IDs), or X answered with an error the API could not classify. | Refunded |
| 401 | Gateway: Missing or unknown API key. | Not charged |
| 402 | Gateway: No balance left, or the trial expired. | Not charged |
| 403 | Gateway: Key deactivated, or the endpoint is not in your plan (trial keys and account actions). | Not charged |
| 403 | X refused the request for this region. | Charged |
| 404 | X says the user, tweet or other object does not exist, or the path does not exist. | Charged |
| 404 | Gateway: The path is an internal route that is closed to customers. | Not charged |
| 407 | Account actions only: your proxy is unreachable or timed out. | Charged |
| 408 | The request took longer than 60 seconds, or X did not finish processing an uploaded video. | Refunded |
| 409 | Gateway: The API answered 400 on every attempt (see retries below). | Refunded |
| 413 | Gateway: Request body larger than 100 MB. | Not charged |
| 422 | A parameter has an invalid value (non-numeric ID, bad username, unsupported search operator) or X rejected the input. | Charged |
| 429 | Gateway: Rate limit for your key exceeded. See [Rate limits](https://www.twittrapi.com/docs/helpful-infos/rate-limits). | Not charged |
| 500, 502, 503, 504 | Something failed on our side or between us and X. 502: an API server could not be reached, or search got 9 empty pages from X in a row. 503: no API server available. 504: the call to X timed out. | Refunded |
| X's status | Account actions pass X's own HTTP status through (for example 401 or 403 when X rejects the session). | Charged unless it is 400, 408 or 5xx |

Endpoint pages list the validation errors of each endpoint under "Errors".

### Retries the gateway does for you

- A read request that gets a 400 from the API is sent to up to two more API servers. If every attempt returns 400, you get **409** and the call is refunded. A response that needed more than one attempt has the header `X-Retried: true`.
- A network error between the gateway and an API server is retried the same way. If nothing answers, you get 502; if no API server is available at all, 503. Both are refunded.
- Account actions (`/client/*`, `/premium/*`) are never retried, because the action may already have happened on X.

You are billed once per request, no matter how many attempts happened behind it.

### What to retry

| Status | Retry? |
| --- | --- |
| 408, 409, 429, 500, 502, 503, 504 | Yes, with exponential backoff (for example 0.5 s, 1 s, 2 s, up to a few tries). |
| 400 | Once at most. If it repeats, check the parameters. |
| 401, 402, 403, 404, 413, 422 | No. Fix the key, balance or input first. |
| Account actions | Only after checking on X that the action did not happen. |

The official SDKs retry 429, 5xx and network errors with backoff by default, and raise typed errors (for example `NotFoundError`, `RateLimitError`, `InsufficientBalanceError` in Python).

```python
from twittrapi import TwittrAPI, InsufficientBalanceError, NotFoundError, TwittrAPIError

client = TwittrAPI()
try:
    user = client.users.get_by_username("someone")
except NotFoundError:
    print("No such user")
except InsufficientBalanceError:
    print("Top up your balance")
except TwittrAPIError as exc:
    print(f"API error {exc}")
```

### Empty results are not errors

X often answers with 200 and an empty timeline instead of an error: for example for likes, which X made private. `/user/by/username/{username}` answers 200 with `{"data": {}, "userExists": false}` for a handle that does not exist.

---

## Rate limits

Source: https://www.twittrapi.com/docs/helpful-infos/rate-limits

> TwittrAPI rate limits per API key in requests per second, the tiers for direct keys, the 429 response and how to back off.

Each API key has a limit in requests per second. It applies to all endpoints together. Going over it returns **429**, which is not charged.

### Tiers for direct keys

| Tier | Requests per second | Price per month |
| --- | --- | --- |
| Trial | 3 | 14 days, $0.10 test credit |
| Standard | 30 | Included after the first top-up |
| Plus | 50 | $29 |
| Pro | 100 | $59 |
| Business | 300 | $149 |
| Scale | 1,000 | $349 |
| Enterprise | 3,000 | from $899, on request |

Standard is included once you have topped up. Plus, Pro, Business and Scale are booked under Plan & limits in the dashboard; the monthly fee comes out of your prepaid balance and you can cancel any time. Enterprise is set up with you: write to [support@twittrapi.com](mailto:support@twittrapi.com). The same table is on the [pricing page](https://www.twittrapi.com/pricing#rate-limits).

RapidAPI and JojAPI keys have the limits of the plan you bought there, shown on the marketplace listing.

### How the limit is counted

The limit is a token bucket per key. The bucket holds as many requests as your per-second limit and refills at that rate. A burst of up to one second's worth of requests goes through at once; after that, requests are accepted at the steady rate.

- Every request to an API endpoint counts, including ones that end in an error.
- `GET /balance` has its own limit of 10 requests per second (burst 20) and does not use your API limit.
- There are no rate-limit headers and no `Retry-After` header. Pace your requests on the client side.

### The 429 response

```json
{ "error": "Rate limit exceeded. Max 30 requests per second" }
```

The number is your current limit.

### Handling 429

Wait and retry. Because the window is one second, a short exponential backoff is enough:

```python
import time
import httpx

def get(url, params=None, tries=5):
    for attempt in range(tries):
        resp = httpx.get(url, params=params, headers={"X-API-KEY": "YOUR_API_KEY"}, timeout=60)
        if resp.status_code != 429:
            return resp
        time.sleep(0.25 * 2 ** attempt)  # 0.25 s, 0.5 s, 1 s, ...
    return resp
```

The official SDKs do this for you: they retry 429 and 5xx responses with backoff (2 retries by default, configurable with `max_retries` / `maxRetries`).

For steady high volume, limit concurrency instead of reacting to 429s. With a limit of 30 requests per second and responses that take about one second, around 25 parallel workers keep you under the limit. If you hit 429 regularly, book a higher tier.

### Ways to need fewer calls

- `/user/users/by/ids` returns up to 100 profiles per call, `/user/usernames/to/ids` resolves up to 100 handles, and `/user/usernames/status` checks up to 500 handles. Each is one call.
- Pass the numeric user ID instead of `username` on user timelines. It is the same price but skips a lookup on our side and is faster.
- Cache profiles and tweets you have already fetched.

---

## Pagination

Source: https://www.twittrapi.com/docs/helpful-infos/cursor

> How TwittrAPI paginates with X's cursors, where to find the bottom cursor in the raw timeline JSON, and when to stop.

Timelines, followers, search and similar endpoints return one page per call. To get the next page, send the **bottom cursor** of the current page as the `cursor` query parameter. Each page is one billed call.

Endpoints that paginate say so on their reference page. They take `count` (default 20) and `cursor`.

### Where the cursor is

The API passes X's JSON through, so the cursor sits where X puts it: in the timeline's `instructions`, as an entry whose `entryId` starts with `cursor-bottom-`:

```json
{
  "type": "TimelineAddEntries",
  "entries": [
    { "entryId": "tweet-1790000000000000000", "content": { "...": "..." } },
    {
      "entryId": "cursor-bottom-1790000000000000000",
      "content": {
        "entryType": "TimelineTimelineCursor",
        "cursorType": "Bottom",
        "value": "DAABCgABGc..."
      }
    }
  ]
}
```

Where the `instructions` array is depends on the endpoint. For example:

- User tweets: `data.user.result.timeline.timeline.instructions`
- Tweet replies: `data.threaded_conversation_with_injections_v2.instructions`
- Search: the API already unwraps it. `/search` returns `{"category": ..., "entries": [...]}` and `entries` is the instructions array.

On later pages X often moves the cursor into a `TimelineReplaceEntry` instruction (`"entry": {...}`) instead of the entries list. There is also a `cursor-top-` entry; ignore it unless you want to poll for newer items.

Because the paths differ and X changes them, the simplest robust approach is to search the whole response for an object with `"cursorType": "Bottom"` and take its `value`.

### Find the cursor yourself

```python
def bottom_cursor(node):
    """Returns the bottom cursor anywhere in X's JSON, or None."""
    if isinstance(node, dict):
        if node.get("cursorType") == "Bottom" and isinstance(node.get("value"), str):
            return node["value"]
        for value in node.values():
            found = bottom_cursor(value)
            if found:
                return found
    elif isinstance(node, list):
        for item in node:
            found = bottom_cursor(item)
            if found:
                return found
    return None
```

```bash
curl 'https://api.twittrapi.com/user/12/followers?count=20&cursor=DAABCgABGc...' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

### Let the SDK do it

The SDKs have a paginator that finds the cursor and keeps calling:

```python
from twittrapi import TwittrAPI

client = TwittrAPI()
for page in client.paginate(client.users.followers, "12", count=100, max_pages=5):
    ...  # page is the raw JSON of one call
```

```js
for await (const page of client.paginate(client.users.followers, { userId: "12", count: 100 }, { maxPages: 5 })) {
  // page.data is the raw JSON of one call, page.nextCursor the cursor for the next
}
```

`extract_bottom_cursor` (Python) and `extractBottomCursor` (Node.js) give you just the cursor if you page by hand.

### When to stop

Stop when any of these is true:

- No bottom cursor in the response.
- The cursor is the same as the one you sent.
- The page has no entries other than cursors. X keeps returning a cursor at the end of a timeline, so this check matters.
- You have as many items or pages as you need. Always set a limit; every page costs a call.

### Notes

- `count` is passed to X as is. X decides the real page size and often returns a few more or fewer items.
- Cursors are opaque strings from X. Store them as they are; they can stop working after a while.
- `/tweet/{tweetId}` uses `cursor` to load more replies of the conversation. Its page size is fixed by X.
- Coming from `has_next_page`, `next_cursor` or `meta.next_token`? The migration guides show the differences: [from twitterapi.io](https://www.twittrapi.com/guides/migrate-from-twitterapi-io), [from SocialData](https://www.twittrapi.com/guides/migrate-from-socialdata), [from the official X API](https://www.twittrapi.com/guides/migrate-from-official-x-api).

---

## Pricing and billing

Source: https://www.twittrapi.com/docs/billing

> How TwittrAPI bills direct keys per API call from a prepaid balance, the price tiers, refunds, balance headers and the /balance endpoint.

Direct keys pay per API call from a prepaid balance. There is no subscription for API calls, and the balance does not expire. RapidAPI and JojAPI keys are billed by the marketplace instead; this page is about direct keys.

Video: [Top up your balance with crypto](https://www.youtube.com/watch?v=y6XmCZiwFFE)

### Price per call

| Price tier | Per 1,000 calls | Per call | When it applies |
| --- | --- | --- | --- |
| Standard | $0.09 | $0.00009 | Any top-up amount |
| Volume | $0.075 | $0.000075 | For 30 days after a top-up of $100 or more |
| Scale | $0.065 | $0.000065 | While your top-ups of the last 30 days add up to $500 or more |

The tier follows your top-ups of the last 30 days: one top-up of $100 or more gives Volume, top-ups that add up to $500 or more give Scale. It is recomputed after every top-up and once a day. The price drops as soon as a top-up qualifies and goes back up when 30 days pass without a new qualifying top-up; you get an email 5 days before. Accounts with an individually agreed price keep it. The [pricing page](https://www.twittrapi.com/pricing) has the same table.

New accounts get $0.10 of test credit, valid for 14 days, at 3 requests per second.

### What counts as a call

- One HTTP request to an API endpoint is one call, whatever it returns.
- A paginated endpoint costs one call per page.
- Batch endpoints are one call per request: up to 100 profiles with `/user/users/by/ids`, 100 handles with `/user/usernames/to/ids`, 500 handles with `/user/usernames/status`.
- Retries we do behind the scenes are free: search retries when X returns an empty page, and the gateway retries a 400 on other servers. You pay once.
- Account actions cost one call each, like read endpoints.

### Refunds and what is not charged

| Outcome | Billing |
| --- | --- |
| 2xx, 404, 407, 422 and other 4xx from the API | Charged |
| 400, 408, 409, 5xx | Charged, then refunded at once (`X-Refunded: true`) |
| 401, 402, 403 from the gateway, 413, 429, closed internal paths | Not charged |

Details per status are on [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling).

### Balance in every response

Billed responses carry these headers:

| Header | Meaning |
| --- | --- |
| `X-Price-Per-Call` | What this call cost, in USD, e.g. `0.000090`. |
| `X-Remaining-Balance` | Your balance after this call (and after a refund, if any). |
| `X-Refunded` | `true` when the call was refunded. |
| `X-Retried` | `true` when the gateway needed more than one attempt. |

### GET /balance

Returns the current balance of your key. It is free and has its own limit of 10 requests per second.

```bash
curl 'https://api.twittrapi.com/balance' -H 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "user_id": 123,
  "balance": 4.82,
  "price_per_call": 0.00009,
  "is_active": true
}
```

There are a few more fields that the dashboard uses. A missing key returns 401, an unknown key 404.

### When the balance runs out

Calls return **402** with `No balance left. Please top up to continue using the API.` and are not charged. Top up in the dashboard. Payment options: Crypto (USDT or USDC on Polygon, USDT on Tron, or Litecoin) with no surcharge, the cheapest way to pay. Card for one-time top-ups from $20 (9% card fee). USDT on Tron: minimum $50 and a $3 network fee per deposit, deducted from the credit. Auto top-up and invoices with VAT ID (reverse charge) for business customers on request.

### Paying with crypto

Crypto top-ups accept USDT or USDC on Polygon, USDT on Tron (TRC-20) and Litecoin. USDT on Tron: minimum $50 and a $3 network fee per deposit, deducted from the credit. Smaller Tron deposits are kept and credited together once they reach the minimum. The dashboard shows the network next to the deposit address. Send only on that network:

- The Polygon address is an EVM address. The same address exists on other EVM chains, but tokens sent there (for example on Ethereum) are not credited automatically.
- Send USDT on Tron as a TRC-20 transfer, and Litecoin only to the Litecoin address.

If you sent funds on another network or another token, [contact support](mailto:support@twittrapi.com) with the transaction hash instead of sending the amount again.

Whether top-ups on a network are currently delayed is shown on the [status page](https://www.twittrapi.com/status).

Booked rate-limit tiers are paid from the same balance once a month. See [Rate limits](https://www.twittrapi.com/docs/helpful-infos/rate-limits). Card payments with invoice, VAT ID and auto top-up for companies: [Enterprise](https://www.twittrapi.com/enterprise).

---

## Response format

Source: https://www.twittrapi.com/docs/response-format

> TwittrAPI returns X's raw GraphQL JSON unchanged. The exceptions (search, lookups, account actions) and how to read timelines, tweets and users.

Most endpoints return exactly the JSON that X's web app receives for the same request: X's internal GraphQL API, passed through without changes. That gives you every field X sends, but the shape is X's, not ours, and X changes it from time to time.

Each reference page names the X query behind the endpoint (for example `UserByScreenName` or `UserTweets`) and says where the data is.

### Reading X's JSON

**Single objects.** A user lookup returns `data.user.result`, with the numeric ID in `rest_id`. A single tweet returns `data.tweetResult.result`.

**Timelines** (user tweets, followers, lists, communities, search) return a list of `instructions`. Content is in the `TimelineAddEntries` instruction:

```json
{
  "type": "TimelineAddEntries",
  "entries": [
    {
      "entryId": "tweet-1790000000000000000",
      "content": {
        "entryType": "TimelineTimelineItem",
        "itemContent": {
          "itemType": "TimelineTweet",
          "tweet_results": { "result": { "rest_id": "1790000000000000000", "...": "..." } }
        }
      }
    },
    { "entryId": "cursor-bottom-...", "content": { "cursorType": "Bottom", "value": "..." } }
  ]
}
```

- `entryId` prefixes tell you what an entry is: `tweet-`, `user-`, `profile-conversation-` and `conversationthread-` (threads), `cursor-top-` and `cursor-bottom-`.
- Users come as `user_results.result`, tweets as `tweet_results.result`. A tweet's author is nested inside the tweet.
- Some results are wrapped in a `TweetWithVisibilityResults` object with the tweet under `tweet`.
- A pinned tweet comes in a separate `TimelinePinEntry` instruction.

Write parsers that search for these markers instead of following one fixed path, and treat every field as optional. The [examples repository](https://github.com/twittrapi/examples) has small extractors for Python, Node.js, Go and jq.

### Exceptions

These endpoints return something other than X's raw GraphQL response:

| Endpoint | Response |
| --- | --- |
| `GET /search` | `{"category": "Latest", "entries": [...]}`: the tab you searched and X's instructions array. |
| `GET /username/to/id/{username}` | `{"userId": "12"}` |
| `GET /user/usernames/to/ids` | `{"userIds": {"jack": "12"}}` (unresolved handles are left out) |
| `GET /user/usernames/status` | `{"usernamesStatus": {"jack": "active"}}` |
| `GET /user/by/username/{username}` | X's JSON, or `{"data": {}, "userExists": false}` when the handle does not exist |
| `GET /trends/{locationId}`, `GET /misc/locations`, `GET /user/friendships/show`, `GET /misc/emojis` | X's older v1.1 JSON (not GraphQL) |
| Account actions (`/client/*`) | `{"status_code": 200, "response": "<X body as a string>"}`. Parse `response` again to get X's JSON. |
| Direct messages (`/client/dm/*`) | JSON built by the API (conversations, decrypted messages, settings) with X's field names; see each [endpoint page](https://www.twittrapi.com/docs/account-actions#direct-messages). |

### Errors

Errors are always `{"error": "..."}` with a non-2xx status. See [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling).

### Example responses

Reference pages show a captured example response where one is available. Until then they show a placeholder. The SDKs return the same JSON, unmodified.

### Coming from another API

If your code expects flat objects or the v2 `data` / `includes` shape, the migration guides map those fields to X's JSON and include adapters: [from twitterapi.io](https://www.twittrapi.com/guides/migrate-from-twitterapi-io), [from SocialData](https://www.twittrapi.com/guides/migrate-from-socialdata) and [from the official X API](https://www.twittrapi.com/guides/migrate-from-official-x-api).

---

## Simple format

Source: https://www.twittrapi.com/docs/simple-format

> Opt-in compact JSON for every read endpoint. Add format=simple and get flat, camelCase tweets, users, lists, communities and jobs with a fixed schema and a cursor that is null on the last page.

By default every endpoint returns X's raw JSON (see [Response format](https://www.twittrapi.com/docs/response-format)). That does not change. If you would rather work with small, flat objects, add `format=simple` to the query string, or send the header `X-Response-Format: simple`:

```bash
curl 'https://api.twittrapi.com/user/2244994945/tweets?count=20&format=simple' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use … We’re so back. http://developer.x.com",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": { "id": "2244994945", "username": "XDevelopers", "name": "Developers", "...": "..." },
      "counts": { "likes": 5599, "retweets": 896, "replies": 791, "quotes": 596, "bookmarks": 2803, "views": 2552260 },
      "media": [],
      "urls": ["http://developer.x.com"],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": true,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAAHCgABHUS5pXT__-wLAAIAAAATMjEwMjQ0NzU2NzA0MDgyMzc2MAgAAwAAAAIAAA",
  "previousCursor": "DAAHCgABHUS5pXUAJxELAAIAAAATMjEwODk2ODM2MDY5MjQwNDI2MQgAAwAAAAEAAA",
  "hasNextPage": true
}
```

- Same price, same parameters, same errors. Only the body of a successful (200) response changes.
- The response carries the header `X-Response-Format: simple`, so you can tell which format you got.
- Every reference page has a **Raw / Simple** toggle on its example response.
- [Account actions](https://www.twittrapi.com/docs/account-actions) (`/client/*`) support it too: instead of the `{"status_code", "response"}` wrapper you get the new tweet, the user, the media ID, or `{"data": {"success": true}}` for actions such as like or bookmark. When X reports an error inside the wrapper, the wrapper is returned unchanged. [Direct messages](https://www.twittrapi.com/docs/account-actions#direct-messages) (`/client/dm/*`) return conversations, messages and settings in this format. `/premium/*` ignores the option.

### Envelope

Lists and timelines:

```json
{ "data": [ ... ], "nextCursor": "…" | null, "previousCursor": "…" | null, "hasNextPage": true | false }
```

Single objects:

```json
{ "data": { ... } }
```

`data` is `null` when X returns nothing for the ID or handle (for example `GET /user/by/username/{username}` for a handle that does not exist).

### Pagination

Pass `nextCursor` as `cursor` to get the next page. `nextCursor` is `null`, and `hasNextPage` is `false`, when there is nothing more to fetch:

- the page has no items,
- X returns the same cursor you sent,
- X's end marker (follower and following lists answer `0|…` on the last page).

So this loop always ends:

```python
import requests

cursor, tweets = None, []
while True:
    params = {"count": 20, "format": "simple", **({"cursor": cursor} if cursor else {})}
    page = requests.get("https://api.twittrapi.com/user/12/tweets", params=params,
                        headers={"X-API-KEY": "YOUR_API_KEY"}).json()
    tweets += page["data"]
    if not page["hasNextPage"]:
        break
    cursor = page["nextCursor"]
```

`previousCursor` is X's top cursor: use it to poll for newer items.

### Rules

- Field names are camelCase and **always present**. A value X did not send is `null`; a list X did not send is `[]`. Counts X does not send are `0`.
- IDs are strings. Times are ISO 8601 in UTC (`2026-02-06T21:09:45Z`).
- Promoted tweets are dropped. Conversation threads (modules) are flattened in display order. A tweet that appears twice on a page is listed once.
- Tweets X does not return (deleted, suspended author, withheld) appear as `{"id": "…", "unavailable": true}` when their ID is known, and are left out otherwise. The same placeholder is used for `quoted`.
- Error responses (any status other than 200) are unchanged, see [Errors](https://www.twittrapi.com/docs/helpful-infos/error-handling). A 200 response that only holds X's `errors` (no `data`) is also returned raw, so you see X's message.
- New fields may be added. Existing fields are not renamed or removed.

### Tweet

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | |
| `url` | string | `https://x.com/<username>/status/<id>` |
| `text` | string | Full text: the long version of long posts (note tweets), t.co links replaced by their targets, the trailing media link removed, `&amp;` `&lt;` `&gt;` decoded. Retweets: `RT @user: ` plus the full original text. |
| `createdAt` | string \| null | ISO 8601 |
| `lang` | string \| null | X's language code (`en`, `und`, …) |
| `author` | [User](#user) \| null | |
| `counts` | object | `likes`, `retweets`, `replies`, `quotes`, `bookmarks`, `views` (`views` is `null` when X hides it) |
| `media` | [Media](#media)[] | |
| `urls` | string[] | Expanded URLs in the text |
| `hashtags` | string[] | Without `#` |
| `mentions` | `{id, username}`[] | |
| `replyTo` | object \| null | `{tweetId, userId, username}` when the tweet is a reply |
| `quoted` | Tweet \| `{id, unavailable}` \| null | One level: the quoted tweet's own `quoted` is `null` |
| `retweetOf` | Tweet \| null | The original tweet of a retweet (with its `quoted`) |
| `conversationId` | string \| null | ID of the first tweet of the thread |
| `isPinned` | boolean | `true` for the pinned tweet at the top of a profile timeline |
| `source` | string \| null | App name, when X sends it (`Twitter Web App`) |
| `article` | object \| null | X Article: `{id, title, previewText, coverImageUrl, publishedAt, text}` |

### Media

| Field | Type | Notes |
| --- | --- | --- |
| `type` | string | `photo`, `video` or `gif` |
| `url` | string | Photos: the image. Videos and GIFs: the MP4 with the highest bitrate. |
| `previewUrl` | string | The image (poster frame for videos) |
| `width`, `height` | number | Original size |
| `altText` | string \| null | |
| `durationMs` | number \| null | Videos only |

### User

| Field | Type | Notes |
| --- | --- | --- |
| `id` | string | |
| `username` | string | Handle without `@` |
| `name` | string | Display name |
| `description` | string | Bio, t.co links expanded |
| `location` | string | As written by the user |
| `url` | string \| null | Website, expanded |
| `createdAt` | string \| null | ISO 8601 |
| `avatarUrl` | string \| null | Full size (not the 48 px `_normal` version) |
| `bannerUrl` | string \| null | |
| `verified` | boolean | Blue check |
| `verifiedType` | string \| null | e.g. `Business` or `Government`; `null` for none |
| `protected` | boolean | Private account |
| `counts` | object | `followers`, `following`, `tweets`, `likes`, `media`, `listed` |
| `pinnedTweetIds` | string[] | |
| `isAutomated` | boolean | Account carries X's "Automated" label |

`GET /user/about/{username}` adds `about`: `accountBasedIn`, `locationAccurate`, `createdCountryAccurate`, `source`, `usernameChanges`, `lastUsernameChangeAt`, `affiliateUsername`, `identityVerified`, `verifiedSince`.

### List

| Field | Type | Notes |
| --- | --- | --- |
| `id`, `name`, `description` | string | |
| `createdAt` | string \| null | ISO 8601 |
| `memberCount`, `followerCount` | number | |
| `private` | boolean | |
| `bannerUrl` | string \| null | |
| `owner` | `{id, username}` \| null | |

### Community

| Field | Type | Notes |
| --- | --- | --- |
| `id`, `name`, `description` | string | `description` is empty in search results |
| `createdAt` | string \| null | ISO 8601 |
| `memberCount`, `moderatorCount` | number | |
| `joinPolicy` | string \| null | As X sends it, e.g. `Open` |
| `nsfw` | boolean | |
| `topic` | string \| null | |
| `bannerUrl` | string \| null | |
| `creator` | `{id, username}` \| null | |
| `rules` | `{id, name, description}`[] | |

### Job

| Field | Type | Notes |
| --- | --- | --- |
| `id`, `title` | string | |
| `location`, `url`, `description` | string \| null | `url` is the employer's application page |
| `company` | `{id, name, logoUrl}` \| null | |
| `postedBy` | `{id, username}` \| null | |

`GET /jobs/find-location/{term}` returns `{id, name}` items to use as the job search location.

### Trends

`GET /trends/{locationId}`: items `{name, query, url, tweetVolume}`; `query` is URL-decoded (`#GoLang`), `tweetVolume` is `null` when X has no count. `GET /misc/locations`: items `{woeid, name, country, countryCode, placeType, parentId}`.

### Endpoints

| Endpoint | `data` |
| --- | --- |
| `/search` (and its aliases), `/tweet/{tweetId}`, `/tweet/{tweetId}/article`, `/tweet/{tweetId}/quotes`, `/user/{userId}/tweets`, `/tweetsAndReplies`, `/media`, `/likes`, `/articles`, `/communities/{communityId}/tweets`, `/lists/tweets` | Tweet[] (search with `category=People`: User[]) |
| `/simpletweet/{tweetId}` | Tweet |
| `/tweet/{tweetId}/favorites`, `/retweets`, `/user/{userId}/followers`, `/followings`, `/verifiedFollowers`, `/user/users/by/ids`, `/communities/{communityId}/members`, `/members/search`, `/moderators`, `/lists/members`, `/lists/followers` | User[] |
| `/user/by/username/{username}`, `/user/{userId}` | User |
| `/user/about/{username}` | User with `about` |
| `/user/friendships/show` | `{source, target, following, followedBy, canDm, blocking, blockedBy, muting}` |
| `/communities/search/{searchTerm}` | Community[] |
| `/communities/{communityId}` | Community |
| `/lists/details` | List |
| `/jobs/search/{searchTerm}` | Job[] |
| `/jobs/{jobId}` | Job |
| `/jobs/find-location/{term}` | `{id, name}`[] |
| `/trends/{locationId}` | Trend[] |
| `/misc/locations` | TrendLocation[] |
| `/username/to/id/{username}`, `/user/usernames/to/ids`, `/user/usernames/status`, `/misc/emojis` | The usual response, unchanged, inside `{"data": ...}`. These are already small (or, for hashflags, a flat list), so there is nothing to simplify. |
| `POST /client/create`, `/client/reply` | Tweet (a scheduled `/client/create`: `{success: true, scheduledTweetId}`) |
| `POST /client/home-timeline`, `/client/bookmarks` | Tweet[] |
| `POST /client/schedule-overview` | `{id, text, executeAt, state, mediaIds}`[] |
| `POST /client/follow`, `/client/profile`, `/client/profile/image`, `/client/profile/banner` | User |
| `POST /client/media`, `/client/media/video` | `{mediaId, mediaKey, expiresAfterSecs}` |
| `POST /client/communities/create` | Community |
| `POST /client/retweet` | `{success: true, retweetId}` |
| `POST /client/like`, `/client/deleteTweet`, `/client/bookmark`, `/client/unbookmark`, `/client/schedule-delete`, `/client/report`, `/client/communities/remove` | `{success: true}` |
| `POST /client/dm/*` | Conversations, messages and DM settings; the fields are on each [endpoint page](https://www.twittrapi.com/docs/account-actions#direct-messages). |

### Size and speed

Across the 9 captured endpoints the simple format is **64 % smaller in bytes** (median, range 58–70 %) and uses **61 % fewer input tokens** (median, range 52–68 %). Tokens shrink a little less than bytes because the fields that are dropped are mostly long, repetitive keys and IDs, which tokenize efficiently.

| Endpoint | Raw bytes | Simple bytes | Bytes saved | Raw tokens | Simple tokens | Tokens saved |
| --- | --- | --- | --- | --- | --- | --- |
| `/user/{userId}/tweetsAndReplies` | 81.7 KB | 24.2 KB | 70 % | 23,009 | 7,274 | 68 % |
| `/user/{userId}/tweets` | 72.2 KB | 22.8 KB | 68 % | 20,484 | 6,866 | 66 % |
| `/tweet/{tweetId}/quotes` | 51.7 KB | 21.1 KB | 59 % | 14,008 | 6,444 | 54 % |
| `/user/{userId}/media` | 49.3 KB | 16.3 KB | 67 % | 14,883 | 4,919 | 67 % |
| `/communities/{communityId}/tweets` | 40.4 KB | 14.5 KB | 64 % | 11,296 | 4,392 | 61 % |
| `/tweet/{tweetId}` | 38.7 KB | 15.0 KB | 61 % | 10,601 | 4,669 | 56 % |
| `/search` | 34.3 KB | 14.4 KB | 58 % | 9,582 | 4,556 | 52 % |
| `/user/{userId}/followers` | 9.1 KB | 2.8 KB | 70 % | 2,272 | 801 | 65 % |
| `/user/{userId}/articles` | 8.6 KB | 3.3 KB | 62 % | 2,263 | 1,009 | 55 % |

How this was measured (2026-10-11): one real captured response per endpoint (anonymized and shortened to a few items, so a full page with the default `count` is several times larger in both formats), converted with the same code the API runs. Both formats are minified JSON. Tokens are counted with OpenAI's `o200k_base` tokenizer (`tiktoken`, used by GPT-4o and later); with `cl100k_base` the median is 61 %. Claude and Gemini use their own tokenizers, so exact counts differ, but the reduction is of the same size because it comes from the fields that are left out.

The conversion runs on our side in about half a millisecond for a 300 KB timeline, so it does not add noticeable latency.

What the smaller responses mean in practice: context windows on [Twitter/X data for AI agents](https://www.twittrapi.com/use-cases/ai-agents), and storage for datasets on [collecting X data for research](https://www.twittrapi.com/use-cases/research).

### When to use which

Use the simple format when you need the common fields and want code that does not break when X reshuffles its JSON. Use the raw format when you need something the simple format leaves out (cards, polls, edit history, community notes, X's ranking metadata): it has every field X sends.

---

## Account monitoring

Source: https://www.twittrapi.com/docs/monitoring

> Watch any public X account, with no follower minimum, and get its new tweets pushed to a signed webhook or pulled from an event feed within 60 s, 5 s or 1 s depending on the plan. Monthly plans, managed under /monitors.

{/* Plans and prices: TwitterDash config/monitor-plans.ts. Behaviour: twittrapi-monitor (internal/api, internal/delivery) and the gateway route TwitterPayment/monitor.go. */}

Account monitoring watches X accounts for you and sends every new tweet they post to your webhook as soon as it is detected. You add the accounts once; there is no polling on your side and no charge per request. Each plan is a flat monthly fee for a number of watched accounts and a speed class.

Video: [Account monitoring: new tweets by webhook](https://www.youtube.com/watch?v=d7bCXQV-uIw)

### How it works

1. You book a plan in the dashboard (Monitors). The monthly fee comes out of your prepaid balance.
2. You add the accounts to watch with `POST /monitors/accounts`.
3. You set a webhook URL with `PUT /monitors/webhook`, or read new tweets from `GET /monitors/events`, or both.
4. We check the accounts' timelines at the interval of your speed class. Every new tweet becomes one event, signed and sent to your webhook, and kept for 7 days in the event feed.

Any public account can be watched, whatever its size: there is no follower minimum, and an account with 50 followers is checked as often as one with 5 million. Tweets that already existed when an account was added are not sent; delivery starts with the first tweet after monitoring begins. An account watched by several customers is checked once and fanned out to all of them, so a popular account is not slower than a niche one.

### Speed classes

| Class | Guarantee | Typical |
| --- | --- | --- |
| Standard | We check at least every 60 s | 15–60 s |
| Fast | We check at least every 5 s | 2–5 s |
| Real-time | We check at least every 1 s | 0.5–1 s |

The guarantee is about how often we look: an account in the Real-time class is never checked less often than once per second, even when that costs us more than the plan brings in. When there is spare capacity we check more often than the guarantee.

**Detection latency** is the time from the tweet being posted to the event being created. It has two parts:

- **X's own delay**: the time X takes before a new tweet shows up in its timelines. It is usually short, but it is outside our control and we cannot guarantee it.
- **Our polling interval**: at most the guarantee of your class (60 s, 5 s or 1 s), on average about half of it.

Webhook delivery adds the round trip to your server. Every event carries `tweetCreatedAt` (from X) and `detectedAt` (when we saw it), so you can measure the latency yourself.

### Plans and prices

Monthly prices in USD, charged from your balance:

| Accounts | Standard (60 s) | Fast (5 s) | Real-time (1 s) |
| --- | --- | --- | --- |
| 10 | $5 | $9 | $19 |
| 50 | $15 | $29 | $59 |
| 250 | $49 | $99 | $199 |
| 1,000 | $149 | $299 | $599 |
| 5,000 | $499 | $999 | $1,999 |

- The plan renews every month from your balance. If the balance does not cover the renewal, the plan is **paused**: your watched accounts are kept, no events are created and adding accounts returns `402`. Top up and renew in the dashboard to resume.
- An upgrade (more accounts or a faster class) applies at once; you pay the prorated difference for the rest of the period. A downgrade applies at the end of the period.
- Requests to `/monitors/*` are not billed per call. They count toward your normal [rate limit](https://www.twittrapi.com/docs/helpful-infos/rate-limits).

### API

Base URL `https://api.twittrapi.com`, authenticated with your normal `X-API-KEY` header. Request and response bodies are JSON. Errors have the form `{"error": "..."}`.

| Method | Path | |
| --- | --- | --- |
| `GET` | `/monitors` | Plan and webhook |
| `GET` | `/monitors/accounts` | Watched accounts and plan usage |
| `POST` | `/monitors/accounts` | Watch accounts |
| `DELETE` | `/monitors/accounts/{usernameOrId}` | Stop watching an account |
| `GET` | `/monitors/webhook` | Current webhook |
| `PUT` | `/monitors/webhook` | Set or change the webhook (`POST` works too) |
| `DELETE` | `/monitors/webhook` | Remove the webhook |
| `POST` | `/monitors/webhook/test` | Send a test event |
| `GET` | `/monitors/events` | Pull API: events after a given event |
| `GET` | `/monitors/deliveries` | The latest events with their delivery status (`limit` up to 200, default 50) |

#### Watch accounts

```bash
curl -X POST 'https://api.twittrapi.com/monitors/accounts' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"usernames": ["XDevelopers", "elonmusk"]}'
```

The body takes `username`, `userId`, `usernames` or `userIds` (with or without `@`), up to 100 accounts per request. Each account gets its own result:

```json
{
  "data": [
    { "input": "XDevelopers", "userId": "2244994945", "username": "XDevelopers", "status": "added" },
    { "input": "elonmusk", "userId": "44196397", "username": "elonmusk", "status": "already_watching" }
  ],
  "plan": {
    "planId": "m50-realtime",
    "maxAccounts": 50,
    "usedAccounts": 12,
    "latencyClass": "realtime",
    "status": "active"
  }
}
```

A failed account has an `error` instead of a `status`: not found on X, protected, not a valid username, or the plan's account limit reached. With a single account the HTTP status reflects the result (`201` added, `404`, `422` protected, `402` limit reached); with several it is `200` and you read the results one by one. Without a plan, or with a paused plan, the whole request returns `402`.

`GET /monitors/accounts` lists the watched accounts with `userId`, `username`, `state`, `latencyClass`, `subscribedAt` and `monitoringSince`. A new account has the state `pending` until monitoring of it starts, then `active`; `monitoringSince` is set from then on.

#### Set the webhook

```bash
curl -X PUT 'https://api.twittrapi.com/monitors/webhook' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"url": "https://example.com/hooks/twittrapi"}'
```

```json
{
  "webhook": {
    "id": 7,
    "url": "https://example.com/hooks/twittrapi",
    "secret": "whsec_3f1c...",
    "active": true,
    "failureCount": 0,
    "disabledAt": null,
    "disabledReason": null,
    "lastSuccessAt": null,
    "lastFailureAt": null
  }
}
```

The first `PUT` creates a signing secret (`whsec_` followed by 48 hex characters). Changing the URL keeps the secret; send `"rotateSecret": true` to get a new one. Setting the webhook again also re-enables it after it was disabled.

The URL must be `https`, without credentials, on a public host: private, loopback and link-local addresses (and host names that resolve to them) are refused, and redirects are not followed.

`POST /monitors/webhook/test` sends a signed `test` event and answers with `{"ok": true, "statusCode": 200, "latencyMs": 84}` (or `ok: false` and an `error`).

### Webhook events

Each new tweet is one `POST` to your URL with this JSON body:

```json
{
  "event": "tweet",
  "id": "1849203",
  "monitoredUserId": "2244994945",
  "tweetId": "2019881223666233717",
  "tweetCreatedAt": "2026-02-06T21:09:45Z",
  "detectedAt": "2026-02-06T21:09:46.212Z",
  "tweet": {
    "id": "2019881223666233717",
    "url": "https://x.com/XDevelopers/status/2019881223666233717",
    "text": "Officially launching X API Pay-Per-Use …",
    "createdAt": "2026-02-06T21:09:45Z",
    "lang": "en",
    "author": { "id": "2244994945", "username": "XDevelopers", "name": "Developers", "...": "..." },
    "counts": { "likes": 0, "retweets": 0, "replies": 0, "quotes": 0, "bookmarks": 0, "views": 0 },
    "media": [],
    "replyTo": null,
    "quoted": null,
    "retweetOf": null,
    "conversationId": "2019881223666233717"
  }
}
```

- `tweet` is a tweet in the [simple format](https://www.twittrapi.com/docs/simple-format), the same object you get from the read endpoints with `format=simple`. A retweet has `retweetOf`, a reply has `replyTo`.
- `id` is the event ID. It increases with every event and is what you pass as `since` to the pull API.
- `monitoredUserId` is the watched account the event belongs to.

Headers:

| Header | |
| --- | --- |
| `X-TwittrAPI-Event` | `tweet` (or `test` for the test event) |
| `X-TwittrAPI-Delivery` | Event ID, the same in every retry. Use it to drop duplicates. |
| `X-TwittrAPI-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` |
| `Content-Type` | `application/json` |
| `User-Agent` | `TwittrAPI-Monitor/1.0` |

Answer with any `2xx` status within 5 seconds. Do the work after answering (put the event on a queue), not before.

### Verify the signature

`v1` is HMAC-SHA256 with your webhook secret as key over the string `<t>.<raw body>`: the timestamp from the header, a dot, and the request body exactly as received. Compute it on the raw bytes, before any JSON parsing, compare it in constant time, and reject timestamps more than 5 minutes away from your clock so a captured request cannot be replayed.

#### Node.js

```js
import crypto from "node:crypto";
import express from "express";

const secret = process.env.TWITTRAPI_WEBHOOK_SECRET;
const app = express();

function verify(header, rawBody, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("=", 2)),
  );
  const t = Number(parts.t);
  if (!t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.`)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/hooks/twittrapi", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.get("X-TwittrAPI-Signature") ?? "", req.body)) {
    return res.sendStatus(401);
  }
  res.sendStatus(204);
  const event = JSON.parse(req.body);
  // queue event.tweet for processing; dedupe on req.get("X-TwittrAPI-Delivery")
});

app.listen(3000);
```

#### Python

```python
import hashlib, hmac, os, time

from flask import Flask, abort, request

SECRET = os.environ["TWITTRAPI_WEBHOOK_SECRET"].encode()
app = Flask(__name__)

def verify(header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    t, sig = parts.get("t", ""), parts.get("v1", "")
    if not t.isdigit() or not sig or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(SECRET, t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

@app.post("/hooks/twittrapi")
def hook():
    if not verify(request.headers.get("X-TwittrAPI-Signature", ""), request.get_data()):
        abort(401)
    event = request.get_json()
    # queue event["tweet"]; dedupe on request.headers["X-TwittrAPI-Delivery"]
    return "", 204
```

#### Go

```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

var secret = []byte(os.Getenv("TWITTRAPI_WEBHOOK_SECRET"))

func verify(header string, body []byte, tolerance time.Duration) bool {
	var t, sig string
	for _, part := range strings.Split(header, ",") {
		k, v, _ := strings.Cut(strings.TrimSpace(part), "=")
		switch k {
		case "t":
			t = v
		case "v1":
			sig = v
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || sig == "" {
		return false
	}
	if d := time.Since(time.Unix(ts, 0)); d > tolerance || d < -tolerance {
		return false
	}
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(t + "."))
	mac.Write(body)
	return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(sig))
}

func main() {
	http.HandleFunc("POST /hooks/twittrapi", func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
		if err != nil || !verify(r.Header.Get("X-TwittrAPI-Signature"), body, 5*time.Minute) {
			http.Error(w, "invalid signature", http.StatusUnauthorized)
			return
		}
		w.WriteHeader(http.StatusNoContent)
		// queue body for processing; dedupe on r.Header.Get("X-TwittrAPI-Delivery")
	})
	http.ListenAndServe(":3000", nil)
}
```

### Retries and auto-disable

A delivery fails when your server does not answer with `2xx` within 5 seconds (timeouts, connection errors, `3xx`, `4xx` and `5xx` all count). Failed deliveries are retried with exponential backoff: after 10 s, 20 s, 40 s and so on, doubling up to at most 2 hours between attempts, with ±20 % jitter. A delivery that has not succeeded 24 hours after the tweet was detected is given up and marked `failed`.

Retries can arrive out of order and, rarely, more than once. Use `X-TwittrAPI-Delivery` to drop duplicates and `tweetCreatedAt` to order.

After **20 failed deliveries in a row** the webhook is disabled (`active: false`, with `disabledAt` and `disabledReason`) and we email you. Monitoring continues: new events are still created and stay available through the pull API. Fix your endpoint, then send `PUT /monitors/webhook` again to re-enable it.

### Pull API

If you cannot receive webhooks, or to catch up after downtime, read the events directly:

```bash
curl 'https://api.twittrapi.com/monitors/events?since=1849203&limit=100' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "data": [
    {
      "event": "tweet",
      "id": "1849204",
      "monitoredUserId": "2244994945",
      "tweetId": "2019881223666233718",
      "tweetCreatedAt": "2026-02-06T21:12:03Z",
      "detectedAt": "2026-02-06T21:12:04.017Z",
      "tweet": { "id": "2019881223666233718", "...": "..." },
      "status": "delivered",
      "attempts": 1,
      "deliveredAt": "2026-02-06T21:12:04.231Z",
      "lastError": null
    }
  ],
  "nextSince": "1849204",
  "hasMore": false
}
```

- `since` is an event ID (exclusive) or an RFC 3339 time. Without it you get the oldest events still kept.
- `limit` is 1–500, default 100. Events come oldest first.
- Store `nextSince` and pass it as `since` on the next call. When `hasMore` is `true`, call again right away.
- `status` is `stored` (no webhook set), `pending` (webhook delivery still being tried), `delivered` or `failed`.

Events are kept for **7 days**, then deleted. We do not keep an archive of tweets beyond that.

### Limits

| | |
| --- | --- |
| Accounts per plan | 10, 50, 250, 1,000 or 5,000 |
| Accounts per `POST /monitors/accounts` | 100 |
| Accounts that can be watched | Any public account, no follower minimum (protected accounts return `422`) |
| Webhooks | One per API account, `https` only, public host |
| Webhook timeout | 5 s |
| Retries | Up to 24 h per event, backoff up to 2 h |
| Auto-disable | After 20 failed deliveries in a row |
| Event retention (pull API) | 7 days |
| Pull API page size | 1–500 events (default 100) |
| Request body | 64 KB |
| Requests to `/monitors/*` | Your normal rate limit, no per-call charge |

### FAQ

**Are replies and retweets included?**
We do not filter: every tweet of the account that X returns to us is sent. Retweets carry `retweetOf`, replies `replyTo` and quotes `quoted`, so you can filter on your side if you only want original tweets.

**Do small accounts get the same speed?**
Yes. There is no follower threshold and no separate slow lane: every public account you add is checked at the guaranteed interval of your class, whether it has 50 followers or 5 million.

**Can I watch protected accounts?**
No. Only public accounts can be watched.

**Do I get old tweets when I add an account?**
No. Delivery starts with the first tweet after monitoring of the account begins. Use the [user tweets endpoint](https://www.twittrapi.com/docs/reference) for history.

**What happens to deleted tweets?**
An event is sent when the tweet is detected. Deletions are not sent as separate events.

**Is the 1 second a guarantee of end-to-end latency?**
No. It guarantees how often we check (at least once per second for Real-time). The time X needs before a new tweet appears is added on top and is outside our control. Compare `tweetCreatedAt` and `detectedAt` to see the actual latency.

**What if my balance runs out?**
The plan is paused at renewal: watched accounts are kept, no events are created. Top up and renew in the dashboard and monitoring continues.

**Does the same account cost more if many people watch it?**
No. Price depends only on the number of accounts in your plan and the speed class.

**How do I test my endpoint?**
`POST /monitors/webhook/test` sends a signed test event with `X-TwittrAPI-Event: test`. Your handler should verify and accept it like a real one.

---

## Recipes: mentions, polling and datasets

Source: https://www.twittrapi.com/docs/recipes

> Ready-to-run examples for brand mentions, cashtag and contract searches, polling without duplicates, following a set of accounts through a list, and collecting a search window to JSON Lines in parallel.

Short, complete programs for the jobs people use TwittrAPI for most. They use `curl` or the [Python SDK](https://www.twittrapi.com/docs/integrations#sdks) with the [simple format](https://www.twittrapi.com/docs/simple-format), and read the key from `TWITTRAPI_API_KEY`. Every page of results is one billed call; calls that fail on our side are refunded and 429 is not charged ([billing](https://www.twittrapi.com/docs/billing)).

### Brand mentions

Mentions come in three shapes: the handle, the name and the domain. Catch all three, drop your own account and plain retweets:

```text
(@acme OR "acme app" OR url:acme.com) -from:acme -filter:nativeretweets
```

Mentions of the last day, English, without job posts and giveaways. The `filters` object adds operators for you:

```bash
curl -G 'https://api.twittrapi.com/search' \
  -H 'X-API-KEY: YOUR_API_KEY' \
  --data-urlencode 'searchItem=(@acme OR "acme app" OR url:acme.com) -from:acme' \
  --data-urlencode 'category=Latest' \
  --data-urlencode 'filters={"lang":"en","noneOfTheseWords":["hiring","giveaway"],"withinTime":"1d"}' \
  --data-urlencode 'format=simple'
```

Common words need context: "acme" alone matches everything, "acme" plus your product words or domain does not. For a campaign hashtag with traction only, use `filters={"hashtags":["AcmeSummer"],"minimumLikesCount":20,"removeReplies":true}`. All operators and filter keys: [search reference](https://www.twittrapi.com/docs/reference/search).

### Cashtags and contract addresses

Latest `$BTC` or `$ETH` tweets with some engagement, no replies:

```bash
curl -G 'https://api.twittrapi.com/search' \
  -H 'X-API-KEY: YOUR_API_KEY' \
  --data-urlencode 'searchItem=$BTC OR $ETH' \
  --data-urlencode 'category=Latest' \
  --data-urlencode 'filters={"minimumLikesCount":5,"removeReplies":true,"noneOfTheseWords":["giveaway","airdrop"]}' \
  --data-urlencode 'format=simple'
```

- Cashtags are plain text in the query: `$BTC`, `$SOL OR $JUP`. The `$` must be URL-encoded (`%24`); `--data-urlencode` and the SDKs do that for you.
- Contract addresses are matched as text. Quote them, `"0x6982508145454ce325ddbe47a25d4ec3d2311933"`, and also search the project handle and domain (`url:example.xyz`).
- Query plus filters can be up to 512 characters. Square brackets are rejected with 422.

### Poll a search without duplicates

Ask for the newest tweets every 30 seconds and pass the highest ID you have as `since_id:` (or `sinceId` in `filters`). Each tweet arrives once, oldest first. The SDK retries 429 and 5xx with backoff. To start from a point in time instead of a tweet you have, turn the date into an ID with the [tweet ID to date converter](https://www.twittrapi.com/tools/tweet-id-to-date).

```python
import time

from twittrapi import TwittrAPI, TwittrAPIError

client = TwittrAPI(response_format="simple")  # key from TWITTRAPI_API_KEY
QUERY = "($BTC OR $ETH) -filter:replies"
newest = None  # highest tweet ID seen so far

while True:
    query = f"{QUERY} since_id:{newest}" if newest else QUERY
    try:
        page = client.search.tweets(query, category="Latest")
    except TwittrAPIError as err:  # 429 and 5xx are already retried by the SDK
        print("error:", err)
        time.sleep(30)
        continue
    tweets = [t for t in page["data"] if not t.get("unavailable")]
    for t in sorted(tweets, key=lambda t: int(t["id"])):  # oldest first
        author = t["author"]["username"] if t["author"] else "?"
        print(t["createdAt"], author, t["text"][:140])
    if tweets:
        newest = max((t["id"] for t in tweets), key=int)
    time.sleep(30)
```

Every poll is one call whether or not it finds something new, so the interval sets the cost. For new tweets from a fixed set of accounts within seconds, without running the loop yourself, see [account monitoring](https://www.twittrapi.com/docs/monitoring).

### Follow a set of accounts with one call

Put the accounts you follow on a public X list. [List tweets](https://www.twittrapi.com/docs/reference/lists-tweets) returns the newest tweets of all members, so one call per poll covers the whole list, however many accounts are on it. The first run prints the current page; after that only new tweets.

```python
import time

from twittrapi import TwittrAPI

client = TwittrAPI(response_format="simple")
LIST_ID = "1234567890"  # a public X list with the accounts you follow
seen: set[str] = set()

while True:
    page = client.lists.tweets(LIST_ID, count=20)
    fresh = [t for t in page["data"] if not t.get("unavailable") and t["id"] not in seen]
    for t in sorted(fresh, key=lambda t: int(t["id"])):
        author = t["author"]["username"] if t["author"] else "?"
        print(t["createdAt"], author, t["text"][:140], t["url"])
    seen.update(t["id"] for t in fresh)  # keep the newest few thousand in production
    time.sleep(15)
```

### Collect a search window to JSON Lines

With the simple format every page carries `nextCursor` and `hasNextPage`, so the loop has a clear end. The SDK's `paginate` follows the cursor for you and retries 429 and 5xx with backoff.

```python
import json

from twittrapi import TwittrAPI

client = TwittrAPI(response_format="simple", max_retries=5)  # key from TWITTRAPI_API_KEY

def collect(query: str, day: str, next_day: str, path: str) -> int:
    """Write every tweet of one day to JSON Lines. Returns the pages fetched (= calls)."""
    pages = 0
    with open(path, "w", encoding="utf-8") as out:
        for page in client.paginate(
            client.search.tweets,
            f"{query} since:{day} until:{next_day}",
            category="Latest",
            count=100,
            max_pages=None,  # follow nextCursor until hasNextPage is false
        ):
            pages += 1
            for t in page["data"]:
                if t.get("unavailable"):
                    continue
                row = {"id": t["id"], "createdAt": t["createdAt"], "lang": t["lang"],
                       "authorId": (t["author"] or {}).get("id"), "text": t["text"]}
                out.write(json.dumps(row, ensure_ascii=False) + "\n")
    return pages
```

- `count` is a hint (1 to 100); X decides the real page size.
- Cursors are X's and can expire after a while. If a run can be interrupted, store the last cursor with your progress and resume soon.

#### Going faster: split the time range

Pagination is sequential: page 2 needs the cursor from page 1. At roughly a second per response, one cursor chain gives about one page per second, however high your rate limit is. Split the range into windows (a day, an hour) and run the windows in parallel:

```python
from concurrent.futures import ThreadPoolExecutor
from datetime import date, timedelta

QUERY = "(climate OR klima) lang:de"
days = [date(2026, 9, 1) + timedelta(d) for d in range(30)]

def run(day: date) -> None:
    path = f"climate_de_{day}.jsonl"
    pages = collect(QUERY, str(day), str(day + timedelta(1)), path)
    print(f"{day}  {pages} pages  → {path}")

with ThreadPoolExecutor(max_workers=25) as pool:  # stays under the included rate limit
    list(pool.map(run, days))
```

Keep the number of workers below your [rate limit](https://www.twittrapi.com/docs/helpful-infos/rate-limits) in requests per second. Higher limits are monthly add-ons you can book for the month of a crawl.

---

## No-code: n8n and Make

Source: https://www.twittrapi.com/docs/no-code

> Step-by-step setup for the TwittrAPI community node in n8n and the HTTP module in Make, with workflows for mentions to Google Sheets, follower exports and keyword alerts in Slack or Telegram.

TwittrAPI has a community node for n8n and works in Make through the HTTP module. You need a TwittrAPI key (n8n also accepts a RapidAPI key), no X account and no X developer app. Every run of a search or lookup step is one billed API call per page.

| | n8n | Make |
| --- | --- | --- |
| How | TwittrAPI community node (`n8n-nodes-twittrapi`) | HTTP module, Make a request |
| Key | TwittrAPI or RapidAPI key | TwittrAPI key |
| Response | Output Format Raw or Simple | Add `format=simple` to the query string |

### n8n

Video: [Twitter/X data in n8n with the TwittrAPI community node](https://www.youtube.com/watch?v=BhSNu0Cs-Ng)

The node covers users, timelines, followers, tweets, search, lists, communities and trends. Every operation is one call per page. Ready-made workflows are in the [examples folder](https://github.com/twittrapi/n8n-nodes-twittrapi/tree/main/examples) of the repository.

#### 1. Install the node

Settings > Community Nodes > Install, enter `n8n-nodes-twittrapi` and confirm. For queue mode or Docker, n8n's guide on installing community nodes covers the manual install with npm.

#### 2. Add the credential

Create a **TwittrAPI API** credential, Connection Type **Direct API Key**, and paste the key from your dashboard. Saving tests it by looking up the profile @X, which is one normal call.

#### 3. New mentions to Google Sheets

Schedule Trigger every 15 minutes → TwittrAPI node, Resource Search, query `@yourbrand -from:yourbrand`, category Latest, Output Format **Simple** → Split Out on `data` → Google Sheets, Append Row with `createdAt`, `author.username`, `text`, `url`.

To pass on only tweets you have not seen, import [new-mentions-to-google-sheets.json](https://github.com/twittrapi/n8n-nodes-twittrapi/blob/main/examples/new-mentions-to-google-sheets.json) (Workflows > Import from File): it stores the newest tweet ID in workflow static data. Static data is saved only in active (production) runs; manual test runs start fresh.

#### Export followers to CSV

[export-followers-to-csv.json](https://github.com/twittrapi/n8n-nodes-twittrapi/blob/main/examples/export-followers-to-csv.json): User > Get Followers, Return All with Max Pages 20, then Convert to File. Every page is one call, so this workflow makes at most 20 calls.

#### Keyword alerts in Slack or Telegram

[keyword-monitor-to-slack.json](https://github.com/twittrapi/n8n-nodes-twittrapi/blob/main/examples/keyword-monitor-to-slack.json) searches a keyword every 10 minutes, remembers the newest tweet ID and posts only new tweets to a channel.

For a Telegram alert, for example on crypto cashtags:

1. Import the keyword monitor (Workflows > Import from File).
2. Set the query to your cashtags, for example `($SOL OR $JUP) min_faves:5 -filter:replies`, and the schedule to the interval you want to pay for. Every run is one call.
3. Replace the Slack node with Telegram > Send Message, map the tweet text and link, and activate the workflow.

#### Use it as an AI Agent tool

Attach the TwittrAPI node to an AI Agent node as a tool; the agent can then search X or read a profile when a question needs it. On self-hosted n8n set `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true` first. Other agent setups: [SDKs and integrations](https://www.twittrapi.com/docs/integrations#ai-agents).

#### Errors in n8n

- `402`: balance empty. Top up and run again; the call is not charged.
- `429`: rate limit. Turn on Retry On Fail.
- `401`: wrong key or connection type.

Turn on Continue On Fail if one bad username should not stop the run. With Output Format Raw (the default) the node returns X's raw JSON, one item per page; with Simple plus a Split Out node on `data` you get one item per tweet or user with flat fields such as `text`, `author.username` and `counts.likes`.

### Make

With `format=simple` you need no parsing functions: each tweet already has flat fields you can map straight into the next module. This scenario writes new mentions to Google Sheets.

#### 1. HTTP > Make a request

| Field | Value |
| --- | --- |
| URL | `https://api.twittrapi.com/search` |
| Method | `GET` |
| Headers | `X-API-KEY`: your key |
| Query string | `searchItem` = `@yourbrand -from:yourbrand` |
| | `category` = `Latest` |
| | `format` = `simple` |
| Parse response | Yes |

Each run of the HTTP module is one API call plus your Make operations; the iterator and the sheet rows count as Make operations, not API calls. All search parameters: [search reference](https://www.twittrapi.com/docs/reference/search).

#### 2. Iterator

Iterate over `data`, the list of tweets.

#### 3. Google Sheets > Add a row

Map `createdAt`, `author.username`, `text`, `url`, `counts.likes`, `counts.retweets`.

#### 4. Only new tweets

Keep the newest tweet `id` in a Data store. Read it before the HTTP module and add `filters` = `{"sinceId":"<stored id>"}` to the query string; after the iterator, save the highest `id`.

The Make custom app in [twittrapi-make](https://github.com/twittrapi/twittrapi-make) is an alternative to the HTTP module; its README has the setup.

---

## API reference

Read endpoints. All are GET on https://api.twittrapi.com with the X-API-KEY header; each call is billed once. Source: https://www.twittrapi.com/docs/reference

### Users

Profiles, timelines, followers, following and ID lookups.

#### User by username

`GET https://api.twittrapi.com/user/by/username/{username}`

Profile of one account by handle, including its numeric user ID.

Reference page: https://www.twittrapi.com/docs/reference/user-by-username-username

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `username` | path | string | yes |  | Handle, with or without a leading @. 2 to 15 characters: letters, digits and underscore. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's UserByScreenName query (data.user.result, with rest_id and legacy profile fields). When X returns no user, the API answers 200 with {"data": {}, "userExists": false}.

With `format=simple`: {data: User}, or {data: null} when the handle does not exist.

##### Errors

| Status | When |
| --- | --- |
| 422 | The username is not 2 to 15 letters, digits or underscores. |
| 400 | The username contains characters X blocks, or X returned an error. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/by/username/jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "affiliates_highlighted_label": {
          "label": {
            "badge": {
              "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
            },
            "description": "X",
            "url": {
              "url": "https://twitter.com/X",
              "urlType": "DeepLink"
            },
            "userLabelDisplayType": "Badge",
            "userLabelType": "BusinessLabel"
          }
        },
        "avatar": {
          "image_url": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw_normal.jpg"
        },
        "business_account": {},
        "core": {
          "created_at": "Sat Dec 14 04:35:55 +0000 2013",
          "name": "Developers",
          "screen_name": "XDevelopers"
        },
        "creator_subscriptions_count": 0,
        "dm_permissions": {
          "can_dm": true
        },
        "follow_request_sent": false,
        "has_graduated_access": true,
        "has_hidden_subscriptions_on_profile": false,
        "highlights_info": {
          "can_highlight_tweets": true,
          "highlighted_tweets": "4"
        },
        "id": "VXNlcjoyMjQ0OTk0OTQ1",
        "is_blue_verified": true,
        "is_profile_translatable": false,
        "legacy": {
          "default_profile": false,
          "default_profile_image": false,
          "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
          "entities": {
            "description": {},
            "url": {
              "urls": [
                {
                  "display_url": "docs.x.com",
                  "expanded_url": "https://docs.x.com/",
                  "indices": [
                    0,
                    23
                  ],
                  "url": "https://t.co/NIwHVIVyrN"
                }
              ]
            }
          },
          "fast_followers_count": 0,
          "favourites_count": 2758,
          "follow_request_sent": false,
          "followers_count": 698074,
          "friends_count": 770,
          "has_custom_timelines": true,
          "is_translator": false,
          "listed_count": 2977,
          "media_count": 832,
          "needs_phone_verification": false,
          "normal_followers_count": 698074,
          "notifications": false,
          "pinned_tweet_ids_str": [
            "2019881223666233717"
          ],
          "possibly_sensitive": false,
          "profile_banner_url": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
          "profile_interstitial_type": "",
          "statuses_count": 4376,
          "time_zone": "",
          "translator_type": "regular",
          "url": "https://t.co/NIwHVIVyrN",
          "utc_offset": 0,
          "want_retweets": false,
          "withheld_description": "",
          "withheld_scope": ""
        },
        "legacy_extended_profile": {},
… (68 more lines)
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "2244994945",
    "username": "XDevelopers",
    "name": "Developers",
    "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
    "location": "127.0.0.1",
    "url": "https://docs.x.com/",
    "createdAt": "2013-12-14T04:35:55Z",
    "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
    "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
    "verified": true,
    "verifiedType": "Business",
    "protected": false,
    "counts": {
      "followers": 698074,
      "following": 770,
      "tweets": 4376,
      "likes": 2758,
      "media": 832,
      "listed": 2977
    },
    "pinnedTweetIds": [
      "2019881223666233717"
    ],
    "isAutomated": false
  }
}
```

#### User by ID

`GET https://api.twittrapi.com/user/{userId}`

Profile of one account by numeric user ID.

Reference page: https://www.twittrapi.com/docs/reference/user-userid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's UserByRestId query (data.user.result).

With `format=simple`: {data: User}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |
| 404 | X reports the user as not found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "affiliates_highlighted_label": {
          "label": {
            "badge": {
              "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
            },
            "description": "X",
            "url": {
              "url": "https://twitter.com/X",
              "urlType": "DeepLink"
            },
            "userLabelDisplayType": "Badge",
            "userLabelType": "BusinessLabel"
          }
        },
        "business_account": {},
        "creator_subscriptions_count": 0,
        "has_graduated_access": true,
        "has_hidden_subscriptions_on_profile": false,
        "highlights_info": {
          "can_highlight_tweets": true,
          "highlighted_tweets": "4"
        },
        "id": "VXNlcjoyMjQ0OTk0OTQ1",
        "is_blue_verified": true,
        "legacy": {
          "blocked_by": false,
          "blocking": false,
          "can_dm": true,
          "can_media_tag": true,
          "created_at": "Sat Dec 14 04:35:55 +0000 2013",
          "default_profile": false,
          "default_profile_image": false,
          "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
          "entities": {
            "description": {},
            "url": {
              "urls": [
                {
                  "display_url": "docs.x.com",
                  "expanded_url": "https://docs.x.com/",
                  "indices": [
                    0,
                    23
                  ],
                  "url": "https://t.co/NIwHVIVyrN"
                }
              ]
            }
          },
          "fast_followers_count": 0,
          "favourites_count": 2758,
          "follow_request_sent": false,
          "followed_by": false,
          "followers_count": 698057,
          "following": false,
          "friends_count": 770,
          "has_custom_timelines": true,
          "is_translator": false,
          "listed_count": 2977,
          "location": "127.0.0.1",
          "media_count": 832,
          "muting": false,
          "name": "Developers",
          "needs_phone_verification": false,
          "normal_followers_count": 698057,
          "notifications": false,
          "pinned_tweet_ids_str": [
            "2019881223666233717"
          ],
          "possibly_sensitive": false,
          "profile_banner_url": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
          "profile_image_url_https": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw_normal.jpg",
          "profile_interstitial_type": "",
          "protected": false,
          "screen_name": "XDevelopers",
          "statuses_count": 4376,
          "time_zone": "",
          "translator_type": "regular",
          "url": "https://t.co/NIwHVIVyrN",
          "utc_offset": 0,
          "verified": false,
          "verified_type": "Business",
          "want_retweets": false,
          "withheld_description": "",
          "withheld_scope": ""
… (25 more lines)
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "2244994945",
    "username": "XDevelopers",
    "name": "Developers",
    "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
    "location": "127.0.0.1",
    "url": "https://docs.x.com/",
    "createdAt": "2013-12-14T04:35:55Z",
    "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
    "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
    "verified": true,
    "verifiedType": "Business",
    "protected": false,
    "counts": {
      "followers": 698057,
      "following": 770,
      "tweets": 4376,
      "likes": 2758,
      "media": 832,
      "listed": 2977
    },
    "pinnedTweetIds": [
      "2019881223666233717"
    ],
    "isAutomated": false
  }
}
```

#### Users by IDs

`GET https://api.twittrapi.com/user/users/by/ids`

Profiles of up to 100 accounts in one call.

Reference page: https://www.twittrapi.com/docs/reference/user-users-by-ids

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `ids` | query | string (comma-separated) | yes |  | Numeric user IDs separated by commas. At most 100. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's UsersByRestIds query (data.users, one result per ID).

With `format=simple`: Collection of User objects without cursors (hasNextPage false). Accounts X does not return are left out.

##### Errors

| Status | When |
| --- | --- |
| 400 | ids is missing, or has more than 100 IDs. |
| 422 | One of the IDs is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/users/by/ids?ids=12,783214' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "users": [
      {
        "result": {
          "__typename": "User",
          "affiliates_highlighted_label": {
            "label": {
              "badge": {
                "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
              },
              "description": "X",
              "url": {
                "url": "https://twitter.com/X",
                "urlType": "DeepLink"
              },
              "userLabelDisplayType": "Badge",
              "userLabelType": "BusinessLabel"
            }
          },
          "has_graduated_access": true,
          "id": "VXNlcjoyMjQ0OTk0OTQ1",
          "is_blue_verified": true,
          "legacy": {
            "blocked_by": false,
            "blocking": false,
            "can_dm": true,
            "can_media_tag": true,
            "created_at": "Sat Dec 14 04:35:55 +0000 2013",
            "default_profile": false,
            "default_profile_image": false,
            "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
            "entities": {
              "description": {},
              "url": {
                "urls": [
                  {
                    "display_url": "docs.x.com",
                    "expanded_url": "https://docs.x.com/",
                    "indices": [
                      0,
                      23
                    ],
                    "url": "https://t.co/NIwHVIVyrN"
                  }
                ]
              }
            },
            "fast_followers_count": 0,
            "favourites_count": 2758,
            "follow_request_sent": false,
            "followed_by": false,
            "followers_count": 698074,
            "following": false,
            "friends_count": 770,
            "has_custom_timelines": true,
            "is_translator": false,
            "listed_count": 2977,
            "location": "127.0.0.1",
            "media_count": 832,
            "muting": false,
            "name": "Developers",
            "needs_phone_verification": false,
            "normal_followers_count": 698074,
            "notifications": false,
            "pinned_tweet_ids_str": [
              "2019881223666233717"
            ],
            "possibly_sensitive": false,
            "profile_banner_url": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
            "profile_image_url_https": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw_normal.jpg",
            "profile_interstitial_type": "",
            "protected": false,
            "screen_name": "XDevelopers",
            "statuses_count": 4376,
            "time_zone": "",
            "translator_type": "regular",
            "url": "https://t.co/NIwHVIVyrN",
            "utc_offset": 0,
            "verified": false,
            "verified_type": "Business",
            "want_retweets": false,
            "withheld_description": "",
            "withheld_scope": ""
          },
          "parody_commentary_fan_label": "None",
          "professional": {
            "category": [
              {
                "icon_name": "IconBriefcaseStroke",
… (19 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2244994945",
      "username": "XDevelopers",
      "name": "Developers",
      "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
      "location": "127.0.0.1",
      "url": "https://docs.x.com/",
      "createdAt": "2013-12-14T04:35:55Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
      "verified": true,
      "verifiedType": "Business",
      "protected": false,
      "counts": {
        "followers": 698074,
        "following": 770,
        "tweets": 4376,
        "likes": 2758,
        "media": 832,
        "listed": 2977
      },
      "pinnedTweetIds": [
        "2019881223666233717"
      ],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": null,
  "previousCursor": null,
  "hasNextPage": false
}
```

#### Usernames to IDs

`GET https://api.twittrapi.com/user/usernames/to/ids`

Numeric user IDs for up to 100 handles in one call.

Reference page: https://www.twittrapi.com/docs/reference/user-usernames-to-ids

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `usernames` | query | string (comma-separated) | yes |  | Handles separated by commas, with or without @. At most 100. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Built by the API: {"userIds": {"jack": "12", ...}}. Only resolved handles are keys of userIds. A handle that could not be resolved is left out of userIds and listed in errors: [{"username": "...", "error": "not_found" | "suspended" | "upstream_error", "message": "..."}]. errors is left out when every handle resolved. upstream_error means X could not be asked (retry later), not that the account is missing.

With `format=simple`: Unchanged inside {data: ...}: {userIds, errors} is already small, so the simple format only adds the envelope.

##### Errors

| Status | When |
| --- | --- |
| 400 | usernames is missing, has more than 100 entries, or a handle contains characters X blocks. |
| 422 | A handle is not 2 to 15 letters, digits or underscores. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/usernames/to/ids?usernames=jack,XDevelopers' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "userIds": {
    "XDevelopers": "2244994945",
    "jack": "12"
  }
}
```

Simple (`format=simple`):

```json
{
  "data": {
    "userIds": {
      "XDevelopers": "2244994945",
      "jack": "12"
    }
  }
}
```

#### Username to ID

`GET https://api.twittrapi.com/username/to/id/{username}`

Numeric user ID for one handle. Smaller response than the full profile.

Reference page: https://www.twittrapi.com/docs/reference/username-to-id-username

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `username` | path | string | yes |  | Handle, with or without a leading @. 2 to 15 characters: letters, digits and underscore. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Built by the API: {"userId": "12"}.

With `format=simple`: Unchanged inside {data: ...}: {userId} is already small, so the simple format only adds the envelope.

##### Errors

| Status | When |
| --- | --- |
| 422 | The username is invalid, or no user was found (body has errorInfo with X's response). |
| 400 | The username contains characters X blocks, or X returned an error. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/username/to/id/jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "userId": "2244994945"
}
```

Simple (`format=simple`):

```json
{
  "data": {
    "userId": "2244994945"
  }
}
```

#### About this account

`GET https://api.twittrapi.com/user/about/{username}`

The data behind X's "About this account" panel (account location, join date, username changes and similar).

Reference page: https://www.twittrapi.com/docs/reference/user-about-username

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `username` | path | string | yes |  | Handle, with or without a leading @. 2 to 15 characters: letters, digits and underscore. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's AboutAccountQuery.

With `format=simple`: {data: User with an about object}: accountBasedIn, locationAccurate, createdCountryAccurate, source, usernameChanges, lastUsernameChangeAt, affiliateUsername, identityVerified, verifiedSince. X sends no counts here, so counts are 0.

##### Errors

| Status | When |
| --- | --- |
| 422 | The username is not 2 to 15 letters, digits or underscores. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/about/jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user_result_by_screen_name": {
      "id": "VXNlclJlc3VsdHM6MjI0NDk5NDk0NQ==",
      "result": {
        "__typename": "User",
        "about_profile": {
          "account_based_in": "United States",
          "affiliate_username": "X",
          "created_country_accurate": true,
          "learn_more_url": "https://help.twitter.com/managing-your-account/about-twitter-verified-accounts",
          "location_accurate": true,
          "source": "Web",
          "username_changes": {
            "count": "1",
            "last_changed_at_msec": "1690335192280"
          }
        },
        "affiliates_highlighted_label": {
          "label": {
            "badge": {
              "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
            },
            "description": "X",
            "url": {
              "url": "https://twitter.com/X",
              "urlType": "DeepLink"
            },
            "userLabelDisplayType": "Badge",
            "userLabelType": "BusinessLabel"
          }
        },
        "avatar": {
          "image_url": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw_normal.jpg"
        },
        "core": {
          "created_at": "Sat Dec 14 04:35:55 +0000 2013",
          "name": "Developers",
          "screen_name": "XDevelopers"
        },
        "id": "VXNlcjoyMjQ0OTk0OTQ1",
        "identity_profile_labels_highlighted_label": {
          "label": {
            "badge": {
              "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
            },
            "description": "X",
            "url": {
              "url": "https://twitter.com/X",
              "urlType": "DeepLink"
            },
            "userLabelDisplayType": "Badge",
            "userLabelType": "BusinessLabel"
          }
        },
        "is_blue_verified": true,
        "privacy": {
          "protected": false
        },
        "profile_image_shape": "Square",
        "rest_id": "2244994945",
        "verification": {
          "verified": false,
          "verified_type": "Business"
        },
        "verification_info": {
          "id": "VXNlclZlcmlmaWNhdGlvbkluZm86MjI0NDk5NDk0NQ==",
          "is_identity_verified": false,
          "reason": {
            "verified_since_msec": "1395425700472"
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "2244994945",
    "username": "XDevelopers",
    "name": "Developers",
    "description": "",
    "location": "",
    "url": null,
    "createdAt": "2013-12-14T04:35:55Z",
    "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
    "bannerUrl": null,
    "verified": true,
    "verifiedType": "Business",
    "protected": false,
    "counts": {
      "followers": 0,
      "following": 0,
      "tweets": 0,
      "likes": 0,
      "media": 0,
      "listed": 0
    },
    "pinnedTweetIds": [],
    "isAutomated": false,
    "about": {
      "accountBasedIn": "United States",
      "locationAccurate": true,
      "createdCountryAccurate": true,
      "source": "Web",
      "usernameChanges": 1,
      "lastUsernameChangeAt": "2023-07-26T01:33:12Z",
      "affiliateUsername": "X",
      "identityVerified": false,
      "verifiedSince": "2014-03-21T18:15:00Z"
    }
  }
}
```

#### User tweets

`GET https://api.twittrapi.com/user/{userId}/tweets`

Tweets from a profile timeline (without replies), newest first.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-tweets

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `includePromotedContent` | query | boolean | no | false | Ask X to include promoted tweets. Accepts true/false, 1/0. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's UserTweets query. Entries are in data.user.result.timeline.timeline.instructions.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. The pinned tweet comes first with isPinned: true. "Who to follow" modules are dropped.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/tweets?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "entry": {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "pinned_tweets",
                      "element": "tweet"
                    },
                    "entryType": "TimelineTimelineItem",
                    "itemContent": {
                      "__typename": "TimelineTweet",
                      "itemType": "TimelineTweet",
                      "socialContext": {
                        "contextType": "Pin",
                        "text": "Example text.",
                        "type": "TimelineGeneralContext"
                      },
                      "tweetDisplayType": "Tweet",
                      "tweet_results": {
                        "result": {
                          "__typename": "Tweet",
                          "card": {
                            "legacy": {
                              "…": "…"
                            },
                            "rest_id": "https://t.co/FS66GuIYJD"
                          },
                          "cashtag_attachments": [],
                          "core": {
                            "user_results": {
                              "…": "…"
                            }
                          },
                          "edit_control": {
                            "edit_tweet_ids": [
                              "…"
                            ],
                            "editable_until_msecs": "1770415785000",
                            "edits_remaining": "5",
                            "is_edit_eligible": false
                          },
                          "grok_analysis_button": true,
                          "grok_annotations": {
                            "is_image_editable_by_grok": true
                          },
                          "grok_translated_post_with_availability": {
                            "is_available": false
                          },
                          "is_translatable": false,
                          "legacy": {
                            "bookmark_count": 2803,
                            "bookmarked": false,
                            "conversation_id_str": "2019881223666233717",
                            "created_at": "Fri Feb 06 21:09:45 +0000 2026",
                            "display_text_range": [
                              "…"
                            ],
                            "entities": {
                              "…": "…"
                            },
                            "favorite_count": 5599,
                            "favorited": false,
                            "full_text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and…",
                            "id_str": "2019881223666233717",
                            "is_quote_status": false,
                            "lang": "en",
                            "possibly_sensitive": false,
                            "possibly_sensitive_editable": true,
                            "quote_count": 596,
                            "reply_count": 791,
                            "retweet_count": 896,
                            "retweeted": false,
                            "user_id_str": "2244994945"
                          },
                          "quick_promote_eligibility": {
                            "eligibility": "IneligibleNotVerified"
                          },
                          "rest_id": "2019881223666233717",
                          "unmention_data": {},
… (76 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698074,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 0
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 5599,
        "retweets": 896,
        "replies": 791,
        "quotes": 596,
        "bookmarks": 2803,
        "views": 2552260
      },
      "media": [],
      "urls": [
        "http://developer.x.com"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": true,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAAHCgABHUS5pXT__-wLAAIAAAATMjEwMjQ0NzU2NzA0MDgyMzc2MAgAAwAAAAIAAA",
  "previousCursor": "DAAHCgABHUS5pXUAJxELAAIAAAATMjEwODk2ODM2MDY5MjQwNDI2MQgAAwAAAAEAAA",
  "hasNextPage": true
}
```

#### User tweets and replies

`GET https://api.twittrapi.com/user/{userId}/tweetsAndReplies`

Tweets and replies from a profile, newest first.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-tweetsandreplies

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's UserTweetsAndReplies query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/tweetsAndReplies?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "entry": {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "pinned_tweets",
                      "element": "tweet"
                    },
                    "entryType": "TimelineTimelineItem",
                    "itemContent": {
                      "__typename": "TimelineTweet",
                      "itemType": "TimelineTweet",
                      "socialContext": {
                        "contextType": "Pin",
                        "text": "Example text.",
                        "type": "TimelineGeneralContext"
                      },
                      "tweetDisplayType": "Tweet",
                      "tweet_results": {
                        "result": {
                          "__typename": "Tweet",
                          "card": {
                            "legacy": {
                              "…": "…"
                            },
                            "rest_id": "https://t.co/FS66GuIYJD"
                          },
                          "core": {
                            "user_results": {
                              "…": "…"
                            }
                          },
                          "edit_control": {
                            "edit_tweet_ids": [
                              "…"
                            ],
                            "editable_until_msecs": "1770415785000",
                            "edits_remaining": "5",
                            "is_edit_eligible": false
                          },
                          "grok_analysis_button": true,
                          "is_translatable": false,
                          "legacy": {
                            "bookmark_count": 2803,
                            "bookmarked": false,
                            "conversation_id_str": "2019881223666233717",
                            "created_at": "Fri Feb 06 21:09:45 +0000 2026",
                            "display_text_range": [
                              "…"
                            ],
                            "entities": {
                              "…": "…"
                            },
                            "favorite_count": 5599,
                            "favorited": false,
                            "full_text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and…",
                            "id_str": "2019881223666233717",
                            "is_quote_status": false,
                            "lang": "en",
                            "possibly_sensitive": false,
                            "possibly_sensitive_editable": true,
                            "quote_count": 596,
                            "reply_count": 791,
                            "retweet_count": 896,
                            "retweeted": false,
                            "user_id_str": "2244994945"
                          },
                          "rest_id": "2019881223666233717",
                          "unmention_data": {},
                          "views": {
                            "count": "2552260",
                            "state": "EnabledWithCount"
                          }
                        }
                      }
                    }
                  },
                  "entryId": "tweet-2019881223666233717",
                  "sortIndex": "2109014652313665536"
… (67 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698074,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 2977
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 5599,
        "retweets": 896,
        "replies": 791,
        "quotes": 596,
        "bookmarks": 2803,
        "views": 2552260
      },
      "media": [],
      "urls": [
        "http://developer.x.com"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": true,
      "source": null,
      "article": null
    },
    "… 1 more"
  ],
  "nextCursor": "DAAHCgABHUS5pvk__-sLAAIAAAATMjEwMjU0ODUxMzE4NzgyNzc3MwgAAwAAAAIAAA",
  "previousCursor": "DAAHCgABHUS5pvlAJxELAAIAAAATMjEwODk2ODM2MDY5MjQwNDI2MQgAAwAAAAEAAA",
  "hasNextPage": true
}
```

#### User media

`GET https://api.twittrapi.com/user/{userId}/media`

Tweets with photos or videos from a profile.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-media

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's UserMedia query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/media?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "direction": "Top",
                "type": "TimelineTerminateTimeline"
              },
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineModule",
                      "clientEventInfo": {
                        "component": "profile-media"
                      },
                      "displayType": "VerticalGrid",
                      "entryType": "TimelineTimelineModule",
                      "items": [
                        {
                          "entryId": "profile-grid-0-tweet-2107952086193508716",
                          "item": {
                            "itemContent": {
                              "…": "…"
                            }
                          }
                        }
                      ]
                    },
                    "entryId": "profile-grid-0",
                    "sortIndex": "2109014658881945600"
                  },
                  "… 2 more"
                ],
                "type": "TimelineAddEntries"
              }
            ],
            "metadata": {
              "scribeConfig": {
                "page": "media"
              }
            }
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2107952086193508716",
      "url": "https://x.com/XDevelopers/status/2107952086193508716",
      "text": "The Developer E𝕏hibit just got a major upgrade. 🚀\n\nRebuilt from the ground up to help you showcase your X API app and get discovered:\n\n• All-new UI with more ways to customize your listing\n• Submit …",
      "createdAt": "2026-10-07T21:51:55Z",
      "lang": "en",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698074,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 2977
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 230,
        "retweets": 31,
        "replies": 16,
        "quotes": 4,
        "bookmarks": 76,
        "views": 23589
      },
      "media": [
        {
          "type": "video",
          "url": "https://video.twimg.com/amplify_video/2107951577118171136/vid/avc1/484x270/grINTBwk-Ni0oJL_.mp4?tag=29",
          "previewUrl": "https://pbs.twimg.com/amplify_video_thumb/2107951577118171136/img/DnRVpyjYuF1QWEVz.jpg",
          "width": 2940,
          "height": 1640,
          "altText": null,
          "durationMs": 7133
        }
      ],
      "urls": [
        "https://developer.x.com/exhibit"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2107952086193508716",
      "isPinned": false,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAABCgABHUS5qIC___0KAAIbOL6IfJeQgwgAAwAAAAIAAA",
  "previousCursor": "DAABCgABHUS5qIDAJxEKAAIbOL6IfJeQgwgAAwAAAAEAAA",
  "hasNextPage": true
}
```

#### User likes

`GET https://api.twittrapi.com/user/{userId}/likes`

Tweets a user has liked.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-likes

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Notes

- X made likes private in June 2024 and shows them only to the account owner. The API does not read as the owner, so the result is empty: {"data": {"user": {"result": {"__typename": "User", "timeline": {}}}}}, without instructions or cursor.

##### Response

Raw JSON of X's Likes query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. Empty since X made likes private: {data: [], nextCursor: null}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/likes?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {}
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [],
  "nextCursor": null,
  "previousCursor": null,
  "hasNextPage": false
}
```

#### Followers

`GET https://api.twittrapi.com/user/{userId}/followers`

Accounts that follow a user, newest first.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-followers

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's Followers query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/followers?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "direction": "Top",
                "type": "TimelineTerminateTimeline"
              },
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineItem",
                      "clientEventInfo": {
                        "component": "FollowersSgs",
                        "element": "user"
                      },
                      "entryType": "TimelineTimelineItem",
                      "itemContent": {
                        "__typename": "TimelineUser",
                        "itemType": "TimelineUser",
                        "userDisplayType": "User",
                        "user_results": {
                          "result": {
                            "__typename": "User",
                            "affiliates_highlighted_label": {
                              "…": "…"
                            },
                            "has_graduated_access": true,
                            "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                            "is_blue_verified": true,
                            "legacy": {
                              "…": "…"
                            },
                            "parody_commentary_fan_label": "None",
                            "profile_image_shape": "Circle",
                            "rest_id": "1000000000000000001",
                            "super_follow_eligible": false,
                            "super_followed_by": false,
                            "super_following": false,
                            "tipjar_settings": {
                              "…": "…"
                            }
                          }
                        }
                      }
                    },
                    "entryId": "user-1000000000000000001",
                    "sortIndex": "2109014666083565568"
                  },
                  "… 3 more"
                ],
                "type": "TimelineAddEntries"
              },
              "… 1 more"
            ]
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "Example",
      "location": "",
      "url": null,
      "createdAt": "2011-01-10T02:06:34Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
      "verified": true,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 129088,
        "following": 3807,
        "tweets": 11761,
        "likes": 15964,
        "media": 1217,
        "listed": 890
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "1000000000000000000|2109014666083565496",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Following

`GET https://api.twittrapi.com/user/{userId}/followings`

Accounts a user follows.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-followings

The same endpoint also answers on:

- `GET /user/{userId}/following` (same handler)

Use the main path in new code; the aliases are kept for existing clients.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's Following query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/followings?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "direction": "Top",
                "type": "TimelineTerminateTimeline"
              },
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineItem",
                      "clientEventInfo": {
                        "component": "FollowingSgs",
                        "element": "user"
                      },
                      "entryType": "TimelineTimelineItem",
                      "itemContent": {
                        "__typename": "TimelineUser",
                        "itemType": "TimelineUser",
                        "userDisplayType": "User",
                        "user_results": {
                          "result": {
                            "__typename": "User",
                            "affiliates_highlighted_label": {
                              "…": "…"
                            },
                            "has_graduated_access": true,
                            "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                            "is_blue_verified": true,
                            "legacy": {
                              "…": "…"
                            },
                            "parody_commentary_fan_label": "None",
                            "profile_image_shape": "Circle",
                            "rest_id": "1000000000000000001",
                            "super_follow_eligible": false,
                            "super_followed_by": false,
                            "super_following": false,
                            "tipjar_settings": {
                              "…": "…"
                            }
                          }
                        }
                      }
                    },
                    "entryId": "user-1000000000000000001",
                    "sortIndex": "2109014676376387584"
                  },
                  "… 3 more"
                ],
                "type": "TimelineAddEntries"
              },
              "… 1 more"
            ]
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "Example",
      "location": "",
      "url": null,
      "createdAt": "2011-01-10T02:06:34Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
      "verified": true,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 129088,
        "following": 3807,
        "tweets": 11761,
        "likes": 15964,
        "media": 1217,
        "listed": 890
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "1000000000000000000|2109014676376387522",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Verified followers

`GET https://api.twittrapi.com/user/{userId}/verifiedFollowers`

Followers of a user that have a verified (blue) badge.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-verifiedfollowers

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's BlueVerifiedFollowers query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/verifiedFollowers?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "direction": "Top",
                "type": "TimelineTerminateTimeline"
              },
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineItem",
                      "clientEventInfo": {
                        "component": "BlueVerifiedFollowersSgs",
                        "element": "user"
                      },
                      "entryType": "TimelineTimelineItem",
                      "itemContent": {
                        "__typename": "TimelineUser",
                        "itemType": "TimelineUser",
                        "userDisplayType": "User",
                        "user_results": {
                          "result": {
                            "__typename": "User",
                            "affiliates_highlighted_label": {
                              "…": "…"
                            },
                            "avatar": {
                              "…": "…"
                            },
                            "core": {
                              "…": "…"
                            },
                            "dm_permissions": {
                              "…": "…"
                            },
                            "has_graduated_access": true,
                            "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                            "is_blue_verified": true,
                            "legacy": {
                              "…": "…"
                            },
                            "location": {
                              "…": "…"
                            },
                            "media_permissions": {
                              "…": "…"
                            },
                            "parody_commentary_fan_label": "None",
                            "privacy": {
                              "…": "…"
                            },
                            "profile_image_shape": "Circle",
                            "relationship_perspectives": {
                              "…": "…"
                            },
                            "rest_id": "1000000000000000001",
                            "super_follow_eligible": false,
                            "super_followed_by": false,
                            "super_following": false,
                            "tipjar_settings": {
                              "…": "…"
                            },
                            "verification": {
                              "…": "…"
                            }
                          }
                        }
                      }
                    },
                    "entryId": "user-1000000000000000001",
                    "sortIndex": "2109014682063863805"
                  },
                  "… 3 more"
                ],
                "type": "TimelineAddEntries"
              },
              "… 1 more"
            ]
          }
        }
      }
… (3 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "",
      "location": "",
      "url": "https://example.com",
      "createdAt": "2017-08-24T11:24:32Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
      "verified": true,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 56,
        "following": 236,
        "tweets": 12,
        "likes": 1418,
        "media": 0,
        "listed": 0
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "1000000000000000000|2109014682063863786",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### User articles

`GET https://api.twittrapi.com/user/{userId}/articles`

X Articles (long-form posts) published by a user.

Reference page: https://www.twittrapi.com/docs/reference/user-userid-articles

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `userId` | path | string | yes |  | Numeric user ID (digits only, up to 30). To pass a handle instead, use -1 here and set username. |
| `username` | query | string | no |  | Handle, with or without @. When set, it is used instead of userId: the API looks up the ID first, which makes the call a little slower. Still one billed call. |
| `count` | query | integer | no | 20 | Number of items to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `includePromotedContent` | query | boolean | no | true | Ask X to include promoted tweets. Accepts true/false, 1/0. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Notes

- An entry can have an empty tweet_results ({}) when X no longer returns that article (deleted or restricted). Skip such entries.

##### Response

Raw JSON of X's UserArticlesTweets query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. Each item carries its article (title, previewText, coverImageUrl, publishedAt).

##### Errors

| Status | When |
| --- | --- |
| 422 | userId is not numeric, or the username lookup failed. |
| 400 | userId is -1 and username is missing, the username contains characters X blocks, or the user could not be found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/12/articles?count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "user": {
      "result": {
        "__typename": "User",
        "timeline": {
          "timeline": {
            "instructions": [
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineItem",
                      "entryType": "TimelineTimelineItem",
                      "itemContent": {
                        "__typename": "TimelineTweet",
                        "itemType": "TimelineTweet",
                        "tweetDisplayType": "Tweet",
                        "tweet_results": {
                          "result": {
                            "__typename": "Tweet",
                            "article": {
                              "…": "…"
                            },
                            "card": {
                              "…": "…"
                            },
                            "core": {
                              "…": "…"
                            },
                            "edit_control": {
                              "…": "…"
                            },
                            "grok_analysis_button": true,
                            "grok_annotations": {
                              "…": "…"
                            },
                            "is_translatable": false,
                            "legacy": {
                              "…": "…"
                            },
                            "rest_id": "2041295840325636551",
                            "unmention_data": {
                              "…": "…"
                            },
                            "views": {
                              "…": "…"
                            }
                          }
                        }
                      }
                    },
                    "entryId": "tweet-2041295840325636551",
                    "sortIndex": "2109014686744707071"
                  },
                  "… 2 more"
                ],
                "type": "TimelineAddEntries"
              }
            ]
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2041295840325636551",
      "url": "https://x.com/XDevelopers/status/2041295840325636551",
      "text": "http://x.com/i/article/2041225663047151616",
      "createdAt": "2026-04-06T23:23:48Z",
      "lang": "zxx",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698074,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 2977
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 519,
        "retweets": 83,
        "replies": 60,
        "quotes": 21,
        "bookmarks": 202,
        "views": 85361
      },
      "media": [],
      "urls": [
        "http://x.com/i/article/2041225663047151616"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2041295840325636551",
      "isPinned": false,
      "source": null,
      "article": {
        "id": "2041225663047151616",
        "title": "X API Major Upgrades ",
        "previewText": "We've been busy...\n \n \n \nMajor Upgrades to X API (and much more coming 🚀)\n \nPay-Per-Use API is Here!\nWe’ve officially launched our new Pay-Per-Use pricing model to all developers worldwide. Unlock",
        "coverImageUrl": "https://pbs.twimg.com/media/HFP2rhVaIAEfQkI.jpg",
        "publishedAt": "2026-04-06T23:23:48Z",
        "text": null
      }
    }
  ],
  "nextCursor": "DAABCgABHUS5rv1___sKAAIAAAAAAAAAAAgAAwAAAAIAAA",
  "previousCursor": "DAAJAAA",
  "hasNextPage": true
}
```

#### Account status for many handles

`GET https://api.twittrapi.com/user/usernames/status`

Whether each handle is active, suspended, protected or not found. Up to 500 handles per call.

Reference page: https://www.twittrapi.com/docs/reference/user-usernames-status

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `usernames` | query | string (comma-separated) | yes |  | Handles separated by commas, with or without @. At most 500. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Built by the API: {"usernamesStatus": {"jack": "active", ...}}. Values: active, suspended, protected, not-found, or error when X could not be asked. For each error there is also an entry in errors: [{"username": "...", "error": "upstream_error", "message": "..."}]; errors is left out when there are none.

With `format=simple`: Unchanged inside {data: ...}: {usernamesStatus, errors} is already small, so the simple format only adds the envelope.

##### Errors

| Status | When |
| --- | --- |
| 400 | usernames is missing or has more than 500 entries. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/usernames/status?usernames=jack,XDevelopers' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "usernamesStatus": {
    "XDevelopers": "active",
    "jack": "active"
  }
}
```

Simple (`format=simple`):

```json
{
  "data": {
    "usernamesStatus": {
      "XDevelopers": "active",
      "jack": "active"
    }
  }
}
```

#### Relationship between two accounts

`GET https://api.twittrapi.com/user/friendships/show`

Whether one account follows another, and the other way round.

Reference page: https://www.twittrapi.com/docs/reference/user-friendships-show

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `source_screen_name` | query | string | yes |  | First handle, with or without @. |
| `target_screen_name` | query | string | yes |  | Second handle, with or without @. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's v1.1 friendships/show endpoint: relationship.source and relationship.target with following and followed_by.

With `format=simple`: {data: {source: {id, username}, target: {id, username}, following, followedBy, canDm, blocking, blockedBy, muting}}. following: source follows target.

##### Errors

| Status | When |
| --- | --- |
| 400 | One of the two handles is missing. |
| 422 | One of the two handles is not 2 to 15 letters, digits or underscores. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/user/friendships/show?source_screen_name=jack&target_screen_name=XDevelopers' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "relationship": {
    "source": {
      "id": 2244994945,
      "id_str": "2244994945",
      "screen_name": "XDevelopers",
      "following": true,
      "followed_by": false,
      "live_following": false,
      "following_received": null,
      "following_requested": null,
      "notifications_enabled": null,
      "can_dm": true,
      "can_media_tag": null,
      "blocking": null,
      "blocked_by": null,
      "muting": null,
      "want_retweets": null,
      "all_replies": null,
      "marked_spam": null
    },
    "target": {
      "id": 12,
      "id_str": "12",
      "screen_name": "jack",
      "following": false,
      "followed_by": true,
      "following_received": null,
      "following_requested": null
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": {
    "source": {
      "id": "2244994945",
      "username": "XDevelopers"
    },
    "target": {
      "id": "12",
      "username": "jack"
    },
    "following": true,
    "followedBy": false,
    "canDm": true,
    "blocking": null,
    "blockedBy": null,
    "muting": null
  }
}
```

### Tweets

A tweet with replies, likers, retweeters and quotes.

#### Tweet with replies

`GET https://api.twittrapi.com/tweet/{tweetId}`

A tweet with its author, media, counts and the reply thread.

Reference page: https://www.twittrapi.com/docs/reference/tweet-tweetid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |
| `rankingMode` | query | string | no | Relevance | Order of the replies, passed to X: Relevance, Recency or Likes. |
| `includePromotedContent` | query | boolean | no | false | Ask X to include promoted tweets. Accepts true/false, 1/0. |
| `cursor` | query | string | no |  | Cursor from the previous page to load more replies. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's TweetDetail query. The focal tweet and replies are entries in data.threaded_conversation_with_injections_v2.instructions. X sends about 20 to 35 reply threads (conversationthread entries) per page; the page size is fixed. The bottom cursor is an entry with cursorType "Bottom" at the end of the entries.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. The conversation in display order: ancestors, the tweet itself, then replies; threads (modules) are flattened.

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |
| 404 | X reports the tweet as not found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/tweet/20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "threaded_conversation_with_injections_v2": {
      "instructions": [
        {
          "type": "TimelineClearCache"
        },
        {
          "entries": [
            {
              "content": {
                "__typename": "TimelineTimelineItem",
                "clientEventInfo": {
                  "component": "tweet",
                  "element": "tweet"
                },
                "entryType": "TimelineTimelineItem",
                "itemContent": {
                  "__typename": "TimelineTweet",
                  "itemType": "TimelineTweet",
                  "tweetDisplayType": "SelfThread",
                  "tweet_results": {
                    "result": {
                      "__typename": "Tweet",
                      "card": {
                        "legacy": {
                          "binding_values": [
                            {
                              "…": "…"
                            },
                            "… 2 more"
                          ],
                          "card_platform": {
                            "platform": {
                              "…": "…"
                            }
                          },
                          "name": "summary_large_image",
                          "url": "https://t.co/FS66GuIYJD",
                          "user_refs_results": []
                        },
                        "rest_id": "https://t.co/FS66GuIYJD"
                      },
                      "core": {
                        "user_results": {
                          "result": {
                            "__typename": "User",
                            "affiliates_highlighted_label": {
                              "…": "…"
                            },
                            "has_graduated_access": true,
                            "id": "VXNlcjoyMjQ0OTk0OTQ1",
                            "is_blue_verified": true,
                            "legacy": {
                              "…": "…"
                            },
                            "parody_commentary_fan_label": "None",
                            "professional": {
                              "…": "…"
                            },
                            "profile_image_shape": "Square",
                            "rest_id": "2244994945",
                            "super_follow_eligible": false,
                            "super_followed_by": false,
                            "super_following": false,
                            "tipjar_settings": {
                              "…": "…"
                            }
                          }
                        }
                      },
                      "edit_control": {
                        "edit_tweet_ids": [
                          "2019881223666233717"
                        ],
                        "editable_until_msecs": "1770415785000",
                        "edits_remaining": "5",
                        "is_edit_eligible": false
                      },
                      "grok_analysis_button": true,
                      "has_birdwatch_notes": false,
                      "is_translatable": false,
                      "legacy": {
                        "bookmark_count": 2802,
                        "bookmarked": false,
                        "conversation_id_str": "2019881223666233717",
                        "created_at": "Fri Feb 06 21:09:45 +0000 2026",
                        "display_text_range": [
                          0,
                          261
… (58 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698057,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 2977
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 5599,
        "retweets": 896,
        "replies": 791,
        "quotes": 596,
        "bookmarks": 2802,
        "views": 2552259
      },
      "media": [],
      "urls": [
        "http://developer.x.com"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": false,
      "source": null,
      "article": null
    },
    "… 1 more"
  ],
  "nextCursor": "DAAKCgABHUS5u-s__qkLAAIAAAGoRW1QQzZ3QUFBZlEvZ0dKTjB2R3AvQUFBQUNVY0NSME1jcHB3Ymh3SVJYV2dsc0JPSEFpQW5USWFNTFVjQ0E5R2NWcHgxeHdJRDBZb1ZvRDBIQWdlT0QrYWNQSWNDQ3VkYk5weFBCd0lMZDFXMXBDSUhBaUdnTTRhY0dRY0NHYVhW…",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Single tweet

`GET https://api.twittrapi.com/simpletweet/{tweetId}`

One tweet by ID, without the reply thread. Smaller than /tweet/{tweetId}.

Reference page: https://www.twittrapi.com/docs/reference/simpletweet-tweetid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's TweetResultByRestId query (data.tweetResult.result).

With `format=simple`: {data: Tweet}, or {data: {id, unavailable: true}} when X does not return the tweet.

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |
| 404 | X reports the tweet as not found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/simpletweet/20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "tweetResult": {
      "result": {
        "__typename": "Tweet",
        "card": {
          "legacy": {
            "binding_values": [
              {
                "key": "photo_image_full_size_large",
                "value": {
                  "image_value": {
                    "height": 419,
                    "url": "https://pbs.twimg.com/card_img/2107952823128182784/RyT0hE6P?format=jpg&name=800x419",
                    "width": 800
                  },
                  "type": "IMAGE"
                }
              },
              "… 2 more"
            ],
            "card_platform": {
              "platform": {
                "audience": {
                  "name": "production"
                },
                "device": {
                  "name": "Swift",
                  "version": "12"
                }
              }
            },
            "name": "summary_large_image",
            "url": "https://t.co/FS66GuIYJD",
            "user_refs_results": []
          },
          "rest_id": "https://t.co/FS66GuIYJD"
        },
        "core": {
          "user_results": {
            "result": {
              "__typename": "User",
              "affiliates_highlighted_label": {
                "label": {
                  "badge": {
                    "url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_bigger.jpg"
                  },
                  "description": "X",
                  "url": {
                    "url": "https://twitter.com/X",
                    "urlType": "DeepLink"
                  },
                  "userLabelDisplayType": "Badge",
                  "userLabelType": "BusinessLabel"
                }
              },
              "has_graduated_access": true,
              "id": "VXNlcjoyMjQ0OTk0OTQ1",
              "is_blue_verified": true,
              "legacy": {
                "blocked_by": false,
                "blocking": false,
                "can_dm": true,
                "can_media_tag": true,
                "created_at": "Sat Dec 14 04:35:55 +0000 2013",
                "default_profile": false,
                "default_profile_image": false,
                "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
                "entities": {
                  "description": {},
                  "url": {
                    "urls": [
                      {
                        "display_url": "docs.x.com",
                        "expanded_url": "https://docs.x.com/",
                        "indices": [
                          0,
                          23
                        ],
                        "url": "https://t.co/NIwHVIVyrN"
                      }
                    ]
                  }
                },
                "fast_followers_count": 0,
                "favourites_count": 2758,
                "follow_request_sent": false,
                "followed_by": false,
                "followers_count": 698074,
                "following": false,
… (108 more lines)
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "2019881223666233717",
    "url": "https://x.com/XDevelopers/status/2019881223666233717",
    "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
    "createdAt": "2026-02-06T21:09:45Z",
    "lang": "en",
    "author": {
      "id": "2244994945",
      "username": "XDevelopers",
      "name": "Developers",
      "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
      "location": "127.0.0.1",
      "url": "https://docs.x.com/",
      "createdAt": "2013-12-14T04:35:55Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
      "verified": true,
      "verifiedType": "Business",
      "protected": false,
      "counts": {
        "followers": 698074,
        "following": 770,
        "tweets": 4376,
        "likes": 2758,
        "media": 832,
        "listed": 2977
      },
      "pinnedTweetIds": [
        "2019881223666233717"
      ],
      "isAutomated": false
    },
    "counts": {
      "likes": 5599,
      "retweets": 896,
      "replies": 791,
      "quotes": 596,
      "bookmarks": 2803,
      "views": 2552260
    },
    "media": [],
    "urls": [
      "http://developer.x.com"
    ],
    "hashtags": [],
    "mentions": [],
    "replyTo": null,
    "quoted": null,
    "retweetOf": null,
    "conversationId": "2019881223666233717",
    "isPinned": false,
    "source": null,
    "article": null
  }
}
```

#### Tweet with article content

`GET https://api.twittrapi.com/tweet/{tweetId}/article`

Like /tweet/{tweetId}, but also asks X for the full content of an X Article attached to the tweet.

Reference page: https://www.twittrapi.com/docs/reference/tweet-tweetid-article

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |
| `rankingMode` | query | string | no | Relevance | Order of the replies, passed to X: Relevance, Recency or Likes. |
| `includePromotedContent` | query | boolean | no | false | Ask X to include promoted tweets. Accepts true/false, 1/0. |
| `cursor` | query | string | no |  | Cursor from the previous page to load more replies. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's TweetDetail query with the article fields turned on. For a tweet without an X Article the response is the same as /tweet/{tweetId}.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. Like the tweet detail; the article is in the tweet's article field (id, title, previewText, coverImageUrl, publishedAt, text).

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |
| 404 | X reports the tweet as not found. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/tweet/20/article' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "threaded_conversation_with_injections_v2": {
      "instructions": [
        {
          "type": "TimelineClearCache"
        },
        {
          "entries": [
            {
              "content": {
                "__typename": "TimelineTimelineItem",
                "clientEventInfo": {
                  "component": "tweet",
                  "element": "tweet"
                },
                "entryType": "TimelineTimelineItem",
                "itemContent": {
                  "__typename": "TimelineTweet",
                  "itemType": "TimelineTweet",
                  "tweetDisplayType": "SelfThread",
                  "tweet_results": {
                    "result": {
                      "__typename": "Tweet",
                      "card": {
                        "legacy": {
                          "binding_values": [
                            {
                              "…": "…"
                            },
                            "… 2 more"
                          ],
                          "card_platform": {
                            "platform": {
                              "…": "…"
                            }
                          },
                          "name": "summary_large_image",
                          "url": "https://t.co/FS66GuIYJD",
                          "user_refs_results": []
                        },
                        "rest_id": "https://t.co/FS66GuIYJD"
                      },
                      "core": {
                        "user_results": {
                          "result": {
                            "__typename": "User",
                            "affiliates_highlighted_label": {
                              "…": "…"
                            },
                            "has_graduated_access": true,
                            "id": "VXNlcjoyMjQ0OTk0OTQ1",
                            "is_blue_verified": true,
                            "legacy": {
                              "…": "…"
                            },
                            "parody_commentary_fan_label": "None",
                            "professional": {
                              "…": "…"
                            },
                            "profile_image_shape": "Square",
                            "rest_id": "2244994945",
                            "super_follow_eligible": false,
                            "super_followed_by": false,
                            "super_following": false,
                            "tipjar_settings": {
                              "…": "…"
                            }
                          }
                        }
                      },
                      "edit_control": {
                        "edit_tweet_ids": [
                          "2019881223666233717"
                        ],
                        "editable_until_msecs": "1770415785000",
                        "edits_remaining": "5",
                        "is_edit_eligible": false
                      },
                      "grok_analysis_button": true,
                      "has_birdwatch_notes": false,
                      "is_translatable": false,
                      "legacy": {
                        "bookmark_count": 2802,
                        "bookmarked": false,
                        "conversation_id_str": "2019881223666233717",
                        "created_at": "Fri Feb 06 21:09:45 +0000 2026",
                        "display_text_range": [
                          0,
                          261
… (58 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": {
        "id": "2244994945",
        "username": "XDevelopers",
        "name": "Developers",
        "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
        "location": "127.0.0.1",
        "url": "https://docs.x.com/",
        "createdAt": "2013-12-14T04:35:55Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 698057,
          "following": 770,
          "tweets": 4376,
          "likes": 2758,
          "media": 832,
          "listed": 2977
        },
        "pinnedTweetIds": [
          "2019881223666233717"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 5599,
        "retweets": 896,
        "replies": 791,
        "quotes": 596,
        "bookmarks": 2802,
        "views": 2552259
      },
      "media": [],
      "urls": [
        "http://developer.x.com"
      ],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": false,
      "source": null,
      "article": null
    },
    "… 1 more"
  ],
  "nextCursor": "DAAKCgABHUS5vqW__qkLAAIAAAGoRW1QQzZ3QUFBZlEvZ0dKTjB2R3AvQUFBQUNVY0NSME1jcHB3Ymh3SVJYV2dsc0JPSEFpQW5USWFNTFVjQ0E5R2NWcHgxeHdJRDBZb1ZvRDBIQWdlT0QrYWNQSWNDQ3VkYk5weFBCd0lMZDFXMXBDSUhBaUdnTTRhY0dRY0NHYVhW…",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Users who liked a tweet

`GET https://api.twittrapi.com/tweet/{tweetId}/favorites`

Accounts that liked a tweet.

Reference page: https://www.twittrapi.com/docs/reference/tweet-tweetid-favorites

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |
| `count` | query | integer | no | 20 | Number of users to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Notes

- X made likes private in June 2024: only the author of a tweet can see who liked it. The API does not read as the author, so the timeline is empty: the only instruction is TimelineTerminateTimeline, with no entries and no cursor. The like count is still in the tweet (favorite_count).

##### Response

Raw JSON of X's Favoriters query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/tweet/20/favorites' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "favoriters_timeline": {
      "timeline": {
        "instructions": [
          {
            "direction": "Bottom",
            "type": "TimelineTerminateTimeline"
          }
        ]
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [],
  "nextCursor": null,
  "previousCursor": null,
  "hasNextPage": false
}
```

#### Users who retweeted a tweet

`GET https://api.twittrapi.com/tweet/{tweetId}/retweets`

Accounts that retweeted a tweet.

Reference page: https://www.twittrapi.com/docs/reference/tweet-tweetid-retweets

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |
| `count` | query | integer | no | 20 | Number of users to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `rankingMode` | query | string | no |  | Recency turns X's ranking off. Any other value, or none, keeps it on. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's Retweeters query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/tweet/20/retweets' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "retweeters_timeline": {
      "timeline": {
        "instructions": [
          {
            "entries": [
              {
                "content": {
                  "__typename": "TimelineTimelineItem",
                  "entryType": "TimelineTimelineItem",
                  "itemContent": {
                    "__typename": "TimelineUser",
                    "itemType": "TimelineUser",
                    "userDisplayType": "User",
                    "user_results": {
                      "result": {
                        "__typename": "User",
                        "affiliates_highlighted_label": {},
                        "has_graduated_access": true,
                        "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                        "is_blue_verified": false,
                        "legacy": {
                          "blocked_by": false,
                          "blocking": false,
                          "can_dm": true,
                          "can_media_tag": true,
                          "created_at": "Sat Dec 25 21:19:29 +0000 2021",
                          "default_profile": true,
                          "default_profile_image": false,
                          "description": "Example",
                          "entities": {
                            "description": {
                              "…": "…"
                            }
                          },
                          "fast_followers_count": 0,
                          "favourites_count": 6190,
                          "follow_request_sent": false,
                          "followed_by": false,
                          "followers_count": 415,
                          "following": false,
                          "friends_count": 470,
                          "has_custom_timelines": true,
                          "is_translator": false,
                          "listed_count": 0,
                          "location": "Example",
                          "media_count": 102,
                          "muting": false,
                          "name": "Example User 1",
                          "needs_phone_verification": false,
                          "normal_followers_count": 415,
                          "notifications": false,
                          "possibly_sensitive": false,
                          "profile_banner_url": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
                          "profile_image_url_https": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
                          "profile_interstitial_type": "",
                          "protected": false,
                          "screen_name": "example_user_1",
                          "statuses_count": 1411,
                          "time_zone": "",
                          "translator_type": "none",
                          "url": "",
                          "utc_offset": 0,
                          "verified": false,
                          "want_retweets": false,
                          "withheld_description": "",
                          "withheld_scope": ""
                        },
                        "parody_commentary_fan_label": "None",
                        "profile_image_shape": "Circle",
                        "rest_id": "1000000000000000001",
                        "super_follow_eligible": false,
                        "super_followed_by": false,
                        "super_following": false,
                        "tipjar_settings": {}
                      }
                    }
                  }
                },
                "entryId": "user-1000000000000000001",
                "sortIndex": "1878501968610066432"
              },
              "… 3 more"
            ],
            "type": "TimelineAddEntries"
          }
        ],
        "responseObjects": {}
      }
… (3 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "Example",
      "location": "Example",
      "url": null,
      "createdAt": "2021-12-25T21:19:29Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
      "verified": false,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 415,
        "following": 470,
        "tweets": 1411,
        "likes": 6190,
        "media": 102,
        "listed": 0
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "HBaAgIC+kLf1iDQAAA==",
  "previousCursor": "HCaAgIDOzuXjkTQAAA==",
  "hasNextPage": true
}
```

#### Quote tweets

`GET https://api.twittrapi.com/tweet/{tweetId}/quotes`

Tweets that quote a tweet.

Reference page: https://www.twittrapi.com/docs/reference/tweet-tweetid-quotes

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `tweetId` | path | string | yes |  | Numeric tweet ID (digits only, up to 30), the number at the end of the tweet URL. |
| `count` | query | integer | no | 20 | Number of tweets to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `rankingMode` | query | string | no |  | Recency returns the newest quotes first (Latest tab). Anything else uses the Top tab. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's SearchTimeline for quoted_tweet_id:<tweetId>. Unlike /search, it is not wrapped in {category, entries}.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. Each item quotes the tweet (quoted).

##### Errors

| Status | When |
| --- | --- |
| 422 | tweetId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/tweet/20/quotes' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "search_by_raw_query": {
      "search_timeline": {
        "timeline": {
          "instructions": [
            {
              "entries": [
                {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "result",
                      "details": {
                        "timelinesDetails": {
                          "controllerData": "DAACDAAFDAABDAABDAABCgABAAAAAAACAAAKAARrZsZzAPh0SQAADAACCgABAAAAAAAAAAAKAAJqHGK7stjygwoABQaJTTBeTByxCAAGAAAAAAoAB0ufWcra…"
                        }
                      },
                      "element": "tweet"
                    },
                    "entryType": "TimelineTimelineItem",
                    "feedbackInfo": {
                      "feedbackKeys": [
                        "1060665035"
                      ]
                    },
                    "itemContent": {
                      "__typename": "TimelineTweet",
                      "itemType": "TimelineTweet",
                      "tweetDisplayType": "Tweet",
                      "tweet_results": {
                        "result": {
                          "__typename": "Tweet",
                          "cashtag_attachments": [],
                          "core": {
                            "user_results": {
                              "…": "…"
                            }
                          },
                          "edit_control": {
                            "edit_tweet_ids": [
                              "…"
                            ],
                            "editable_until_msecs": "1770472555000",
                            "edits_remaining": "5",
                            "is_edit_eligible": true
                          },
                          "grok_analysis_button": true,
                          "grok_annotations": {
                            "is_image_editable_by_grok": false
                          },
                          "grok_translated_post_with_availability": {
                            "is_available": false
                          },
                          "is_translatable": false,
                          "legacy": {
                            "bookmark_count": 3,
                            "bookmarked": false,
                            "conversation_id_str": "2000000000000000001",
                            "created_at": "Sat Feb 07 12:55:55 +0000 2026",
                            "display_text_range": [
                              "…"
                            ],
                            "entities": {
                              "…": "…"
                            },
                            "favorite_count": 13,
                            "favorited": false,
                            "full_text": "Example text.",
                            "id_str": "2000000000000000001",
                            "is_quote_status": true,
                            "lang": "en",
                            "quote_count": 0,
                            "quoted_status_id_str": "2019881223666233717",
                            "quoted_status_permalink": {
                              "…": "…"
                            },
                            "reply_count": 4,
                            "retweet_count": 0,
                            "retweeted": false,
                            "user_id_str": "1000000000000000001"
                          },
                          "quoted_status_result": {
                            "result": {
                              "…": "…"
                            }
                          },
                          "rest_id": "2000000000000000001",
                          "unmention_data": {},
                          "views": {
… (45 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2000000000000000001",
      "url": "https://x.com/example_user_1/status/2000000000000000001",
      "text": "Example text.",
      "createdAt": "2026-02-07T12:55:55Z",
      "lang": "en",
      "author": {
        "id": "1000000000000000001",
        "username": "example_user_1",
        "name": "Example User 1",
        "description": "Example",
        "location": "Example",
        "url": "https://example.com",
        "createdAt": "2013-02-06T11:52:24Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
        "verified": true,
        "verifiedType": null,
        "protected": false,
        "counts": {
          "followers": 16404,
          "following": 1172,
          "tweets": 24710,
          "likes": 26263,
          "media": 2185,
          "listed": 0
        },
        "pinnedTweetIds": [],
        "isAutomated": false
      },
      "counts": {
        "likes": 13,
        "retweets": 0,
        "replies": 4,
        "quotes": 0,
        "bookmarks": 3,
        "views": 3160
      },
      "media": [],
      "urls": [],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": {
        "id": "2019881223666233717",
        "url": "https://x.com/XDevelopers/status/2019881223666233717",
        "text": "Officially launching X API Pay-Per-Use\n\nThe core of X developers are indie builders, early stage products, startups, and hobbyists\n\nIt’s time to open up our X API ecosystem and instill a new wave of n…",
        "createdAt": "2026-02-06T21:09:45Z",
        "lang": "en",
        "author": {
          "id": "2244994945",
          "username": "XDevelopers",
          "name": "Developers",
          "description": "The voice of the X Dev team and your official source for updates, news, and events, related to the X API.",
          "location": "127.0.0.1",
          "url": "https://docs.x.com/",
          "createdAt": "2013-12-14T04:35:55Z",
          "avatarUrl": "https://pbs.twimg.com/profile_images/1683501992314798080/xl1POYLw.jpg",
          "bannerUrl": "https://pbs.twimg.com/profile_banners/2244994945/1690213128",
          "verified": true,
          "verifiedType": "Business",
          "protected": false,
          "counts": {
            "followers": 698074,
            "following": 770,
            "tweets": 4376,
            "likes": 2758,
            "media": 832,
            "listed": 0
          },
          "pinnedTweetIds": [
            "2019881223666233717"
          ],
          "isAutomated": false
        },
        "counts": {
          "likes": 5599,
          "retweets": 896,
          "replies": 791,
          "quotes": 596,
          "bookmarks": 2803,
          "views": 2552260
        },
        "media": [],
        "urls": [
          "http://developer.x.com"
        ],
        "hashtags": [],
… (20 more lines)
```

### Search

Search tweets and people with X's search operators.

#### Search tweets

`GET https://api.twittrapi.com/search`

Search tweets or people with the same query syntax as the X search box.

Reference page: https://www.twittrapi.com/docs/reference/search

This is the Search tab of X. The query supports X search operators, and the optional filters object adds operators for you. Results come one page at a time.

The same endpoint also answers on:

- `POST /search` (same handler; the request body is ignored, parameters go in the query string)
- `GET /search/{searchItem}` (query as a URL-encoded path segment instead of ?searchItem)
- `POST /search/{searchItem}`
- `GET /search_v2` (same, but skips the control-character check)
- `POST /search_v2`

Use the main path in new code; the aliases are kept for existing clients.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `searchItem` | query | string | yes |  | Search query, e.g. from:jack lang:en. Required unless filters is set. query is accepted as an alias. When the query contains operators, + is read as a space. Query plus filters may be at most 512 characters. |
| `category` | query | string | no | Top | Search tab: Top, Latest, People, Photos or Videos. Any other value is treated as Top. |
| `count` | query | integer | no | 20 | Number of results to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `filters` | query | JSON object (URL-encoded) | no |  | Extra conditions as JSON, e.g. {"lang":"en","minimumLikesCount":100}. Each key becomes an operator that is appended to the query. Keys are listed below. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Filter keys

Keys of the `filters` JSON object and the operator each one adds to the query. Values with the wrong type are ignored; values with the wrong format return 422.

| Key | Type | Adds |
| --- | --- | --- |
| `lang` | string | lang:<code> (must be a language code X knows, e.g. en, de, ja, und) |
| `since / until` | string | since:/until: (YYYY-MM-DD or YYYY-MM-DD_HH:MM:SS_UTC) |
| `sinceTime / untilTime` | string of digits | since_time:/until_time: (Unix seconds) |
| `withinTime` | string | within_time: (e.g. 2d, 3h, 5m, 30s) |
| `sinceId / maxId` | string of digits | since_id:/max_id: |
| `exactSentence` | string | "exact phrase" |
| `anyOfTheseWords` | string[] | (a OR b OR c) |
| `noneOfTheseWords` | string[] | -a -b |
| `hashtags` | string[] | (#a OR #b) |
| `fromTheseAccounts / toTheseAccounts / mentionsTheseAccounts` | string[] | (from:a OR from:b), (to:…), (@a OR @b) |
| `minimumRepliesCount / minimumRetweetsCount / minimumLikesCount` | number | min_replies:/min_retweets:/min_faves: |
| `maximumRepliesCount / maximumRetweetsCount / maximumLikesCount` | number | -min_replies:/-min_retweets:/-min_faves: |
| `removeReplies / onlyReplies` | boolean | -filter:replies / filter:replies |
| `removePostsWithLinks / onlyPostsWithLinks` | boolean | -filter:links / filter:links |
| `removePostsWithMedia / onlyPostsWithMedia` | boolean | -filter:media / filter:media |
| `removePostsWithVideo / onlyPostsWithVideo` | boolean | -filter:native_video / filter:native_video |
| `removePostsWithPhotos / onlyPostsWithPhotos` | boolean | -filter:images / filter:images |
| `linksInclude` | string | url:<text> |
| `includeRetweets` | boolean | include:nativeretweets |
| `onlyRetweets` | boolean | filter:nativeretweets |
| `onlyOldStyleRetweets / removeOldStyleRetweets` | boolean | filter:retweets / -filter:retweets |
| `onlyQuotes / onlySelfThreads` | boolean | filter:quote / filter:self_threads |
| `onlyVerified / onlyBlueVerified` | boolean | filter:verified / filter:blue_verified |
| `listId` | string | list:<id> |
| `conversationId / quotedTweetId / quotedUserId` | string of digits | conversation_id:/quoted_tweet_id:/quoted_user_id: |
| `hasEngagement` | boolean | filter:has_engagement |
| `onlyAllVideos / onlySpaces / onlyGifs` | boolean | filter:videos / filter:spaces / card_name:animated_gif |
| `onlyWithMentions / onlyWithHashtags / onlyNews / safeMode` | boolean | filter:mentions / filter:hashtags / filter:news / filter:safe |
| `near / within / geocode / place` | string | near:, within: (10km, 5mi), geocode: (lat,long,radius), place: |
| `source / cardName / cardDomain` | string | source:, card_name:, card_domain: |

Operators accepted in the query text: `from:`, `to:`, `filter:`, `include:`, `lang:`, `since:`, `until:`, `since_time:`, `until_time:`, `within_time:`, `since_id:`, `max_id:`, `min_replies:`, `min_retweets:`, `min_faves:`, `list:`, `url:`, `near:`, `within:`, `geocode:`, `source:`, `card_name:`, `card_domain:`, `card_url:`, `place:`, `place_country:`, `point_radius:`, `bounding_box:`, `entity:`, `conversation_id:`, `quoted_tweet_id:`, `quoted_user_id:`, `quotes_of_tweet_id:`, `context:`, `has:`, `is:`. Any other `word:` outside double quotes returns 422.

##### Notes

- An empty timeline from X is retried with other accounts, up to 9 upstream calls. You are billed once.
- Mutually exclusive filter pairs (for example removeReplies and onlyReplies) return 422 when both are true.

##### Response

Not the raw GraphQL object: {"category": "<tab>", "entries": [...]}, where entries is the instructions array of X's SearchTimeline (data.search_by_raw_query.search_timeline.timeline.instructions). Tweets and users are inside TimelineAddEntries; the bottom cursor is an entry with cursorType "Bottom".

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. With category=People: User objects.

##### Errors

| Status | When |
| --- | --- |
| 400 | Query and filters are both empty, or filters is not valid JSON. |
| 422 | The query has control characters (not checked on /search_v2), is longer than 512 characters, contains [ or ], has an unclosed double quote or unbalanced parentheses, uses an unsupported operator, or a filters value has the wrong format. X validation errors also return 422. |
| 502 | X returned an empty timeline 9 times in a row. Body: {"error": "...", "attempts": 9}. Refunded. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/search?searchItem=from%3Ajack%20lang%3Aen&category=Latest&count=20' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "category": "Latest",
  "entries": [
    {
      "entries": [
        {
          "content": {
            "__typename": "TimelineTimelineItem",
            "clientEventInfo": {
              "component": "latest_nightowl_recency",
              "details": {
                "timelinesDetails": {
                  "controllerData": "DAACDAAFDAABDAABDAABCgABAAAAAEAAAAAAAAwAAgoAAQAAAAAAAAABCgACAs0NDBLG4QoLAAMAAAAGb3BlbmFpCgAFPRJJmRFfu2sIAAYAAAABCgAH+7Bb…"
                }
              },
              "element": "tweet"
            },
            "entryType": "TimelineTimelineItem",
            "itemContent": {
              "__typename": "TimelineTweet",
              "highlights": {
                "textHighlights": [
                  {
                    "endIndex": 159,
                    "startIndex": 153
                  }
                ]
              },
              "itemType": "TimelineTweet",
              "tweetDisplayType": "Tweet",
              "tweet_results": {
                "result": {
                  "__typename": "Tweet",
                  "cashtag_attachments": [],
                  "core": {
                    "user_results": {
                      "result": {
                        "__typename": "User",
                        "action_counts": {
                          "favorites_count": 2972
                        },
                        "affiliates_highlighted_label": {},
                        "avatar": {
                          "image_url": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg"
                        },
                        "banner": {
                          "image_url": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000"
                        },
                        "core": {
                          "created_at": "Mon Aug 24 10:29:06 +0000 2026",
                          "name": "Example User 1",
                          "screen_name": "example_user_1"
                        },
                        "dm_permissions": {
                          "can_dm": false
                        },
                        "follow_request_sent": false,
                        "grok_translated_bio_with_availability": {
                          "is_available": false
                        },
                        "has_graduated_access": true,
                        "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                        "is_blue_verified": false,
                        "location": {
                          "location": "Example"
                        },
                        "media_permissions": {
                          "can_media_tag": true
                        },
                        "notifications_settings": {
                          "notifications_enabled": false
                        },
                        "parody_commentary_fan_label": "None",
                        "pinned_items": {},
                        "possibly_sensitive": false,
                        "privacy": {
                          "protected": false
                        },
                        "profile_bio": {
                          "description": "Example",
                          "entities": {
                            "description": {
                              "…": "…"
                            }
                          }
                        },
                        "profile_image_shape": "Circle",
                        "profile_metadata": {
                          "profile_interstitial_type": ""
                        },
… (102 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2000000000000000001",
      "url": "https://x.com/example_user_1/status/2000000000000000001",
      "text": "Example text.",
      "createdAt": "2026-10-10T20:13:36Z",
      "lang": "en",
      "author": {
        "id": "1000000000000000001",
        "username": "example_user_1",
        "name": "Example User 1",
        "description": "Example",
        "location": "Example",
        "url": null,
        "createdAt": "2026-08-24T10:29:06Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
        "verified": false,
        "verifiedType": null,
        "protected": false,
        "counts": {
          "followers": 1290,
          "following": 1091,
          "tweets": 2136,
          "likes": 2972,
          "media": 119,
          "listed": 0
        },
        "pinnedTweetIds": [],
        "isAutomated": false
      },
      "counts": {
        "likes": 0,
        "retweets": 0,
        "replies": 0,
        "quotes": 0,
        "bookmarks": 0,
        "views": 2
      },
      "media": [],
      "urls": [],
      "hashtags": [],
      "mentions": [
        {
          "id": "1000000000000000002",
          "username": "example_user_2"
        }
      ],
      "replyTo": {
        "tweetId": "2000000000000000002",
        "userId": "1000000000000000002",
        "username": "example_user_2"
      },
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2109008936460275850",
      "isPinned": false,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAADDAABCgABHUS5rRGX0YcKAAIdRLkhaRohfwAIAAIAAAACCAADAAAAAAgABAAAAAAKAAUdRLmzyoAnEAoABh1EubPKf9jwCwAHAAAA8EVtUEM2d0FBQWZRL2dHSk4wdkdwL0FBQUFCUWRSTGszZmRidzZCMUV1U2M0VnJGWkhVUzVJV2thSVg4ZFJMbHdOTmR3Zngx…",
  "previousCursor": "DAADDAABCgABHUS5rRGX0YcKAAIdRLkhaRohfwAIAAIAAAABCAADAAAAAAgABAAAAAAKAAUdRLmzyoAnEAoABh1EubPKf9jwCwAHAAAA8EVtUEM2d0FBQWZRL2dHSk4wdkdwL0FBQUFCUWRSTGszZmRidzZCMUV1U2M0VnJGWkhVUzVJV2thSVg4ZFJMbHdOTmR3Zngx…",
  "hasNextPage": true
}
```

### Lists

Details, tweets, members and followers of public lists.

#### List details

`GET https://api.twittrapi.com/lists/details`

Name, description, owner and member and subscriber counts of a list.

Reference page: https://www.twittrapi.com/docs/reference/lists-details

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `listId` | query | string | yes |  | Numeric list ID, the number in the list URL (x.com/i/lists/<listId>). |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's ListByRestId query (data.list). X may add an errors array next to complete data when it cannot decode one nested field, e.g. the default banner; the rest of the list is still there.

With `format=simple`: {data: List}.

##### Errors

| Status | When |
| --- | --- |
| 400 | listId is missing or not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/lists/details?listId=84839422' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "list": {
      "created_at": 1360088061000,
      "default_banner_media": {
        "media_info": {
          "original_img_height": 375,
          "original_img_url": "https://pbs.twimg.com/media/GkIQyCGWYAA4UdW.png",
          "original_img_width": 1125,
          "salient_rect": {
            "height": 1,
            "left": 562,
            "top": 187,
            "width": 1
          }
        }
      },
      "default_banner_media_results": {},
      "description": "Accounts managed by Twitter, Inc.",
      "facepile_urls": [
        "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
        "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
        "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg"
      ],
      "followers_context": "989 followers including @warriors",
      "following": false,
      "id": "TGlzdDo4NDgzOTQyMg==",
      "id_str": "84839422",
      "is_member": false,
      "member_count": 81,
      "members_context": "81 members",
      "mode": "Public",
      "muting": false,
      "name": "Official Twitter Accounts",
      "pinning": false,
      "subscriber_count": 989,
      "user_results": {
        "result": {
          "__typename": "User",
          "affiliates_highlighted_label": {},
          "avatar": {
            "image_url": "https://pbs.twimg.com/profile_images/1955359038532653056/OSHY3ewP_normal.jpg"
          },
          "core": {
            "created_at": "Tue Feb 20 14:35:54 +0000 2007",
            "name": "X",
            "screen_name": "X"
          },
          "dm_permissions": {
            "can_dm": false
          },
          "follow_request_sent": false,
          "has_graduated_access": true,
          "id": "VXNlcjo3ODMyMTQ=",
          "is_blue_verified": true,
          "legacy": {
            "default_profile": false,
            "default_profile_image": false,
            "description": "what's happening?!",
            "entities": {
              "description": {},
              "url": {
                "urls": [
                  {
                    "display_url": "x.com",
                    "expanded_url": "https://x.com",
                    "indices": [
                      0,
                      23
                    ],
                    "url": "https://t.co/Xp0xJlcROt"
                  }
                ]
              }
            },
            "fast_followers_count": 0,
            "favourites_count": 5969,
            "follow_request_sent": false,
            "followers_count": 60685971,
            "friends_count": 1,
            "has_custom_timelines": true,
            "is_translator": false,
            "listed_count": 85171,
            "media_count": 2492,
            "needs_phone_verification": false,
            "normal_followers_count": 60685971,
            "notifications": false,
            "possibly_sensitive": false,
            "profile_banner_url": "https://pbs.twimg.com/profile_banners/783214/1690175171",
            "profile_interstitial_type": "",
… (82 more lines)
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "84839422",
    "name": "Official Twitter Accounts",
    "description": "Accounts managed by Twitter, Inc.",
    "createdAt": "2013-02-05T18:14:21Z",
    "memberCount": 81,
    "followerCount": 989,
    "private": false,
    "bannerUrl": "https://pbs.twimg.com/media/GkIQyCGWYAA4UdW.png",
    "owner": {
      "id": "783214",
      "username": "X"
    }
  }
}
```

#### List tweets

`GET https://api.twittrapi.com/lists/tweets`

Tweets from the members of a list, newest first.

Reference page: https://www.twittrapi.com/docs/reference/lists-tweets

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `listId` | query | string | yes |  | Numeric list ID, the number in the list URL (x.com/i/lists/<listId>). |
| `count` | query | integer | no | 20 | Number of tweets to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's ListLatestTweetsTimeline query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 400 | listId is missing or not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/lists/tweets?listId=84839422' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "list": {
      "tweets_timeline": {
        "timeline": {
          "instructions": [
            {
              "entries": [
                {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "suggest_organic_list_tweet",
                      "details": {
                        "timelinesDetails": {
                          "injectionType": "OrganicListTweet"
                        }
                      },
                      "element": "tweet"
                    },
                    "entryType": "TimelineTimelineItem",
                    "itemContent": {
                      "__typename": "TimelineTweet",
                      "itemType": "TimelineTweet",
                      "tweetDisplayType": "Tweet",
                      "tweet_results": {
                        "result": {
                          "__typename": "Tweet",
                          "core": {
                            "user_results": {
                              "…": "…"
                            }
                          },
                          "edit_control": {
                            "edit_control_initial": {
                              "…": "…"
                            },
                            "initial_tweet_id": "2108944664455676146"
                          },
                          "grok_analysis_button": true,
                          "is_translatable": false,
                          "legacy": {
                            "bookmark_count": 1,
                            "bookmarked": false,
                            "conversation_id_str": "2000000000000000001",
                            "created_at": "Sat Oct 10 15:36:16 +0000 2026",
                            "display_text_range": [
                              "…"
                            ],
                            "entities": {
                              "…": "…"
                            },
                            "extended_entities": {
                              "…": "…"
                            },
                            "favorite_count": 22,
                            "favorited": false,
                            "full_text": "Example text.",
                            "id_str": "2000000000000000001",
                            "is_quote_status": false,
                            "lang": "en",
                            "possibly_sensitive": false,
                            "possibly_sensitive_editable": true,
                            "quote_count": 1,
                            "reply_count": 4,
                            "retweet_count": 6,
                            "retweeted": false,
                            "user_id_str": "1000000000000000001"
                          },
                          "previous_counts": {
                            "bookmark_count": 0,
                            "favorite_count": 0,
                            "quote_count": 0,
                            "reply_count": 0,
                            "retweet_count": 0
                          },
                          "rest_id": "2000000000000000001",
                          "unmention_data": {},
                          "views": {
                            "count": "7500",
                            "state": "EnabledWithCount"
                          }
                        }
                      }
                    }
                  },
                  "entryId": "tweet-2000000000000000001",
                  "sortIndex": "2109016349601693694"
                },
                "… 2 more"
… (14 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2000000000000000001",
      "url": "https://x.com/example_user_1/status/2000000000000000001",
      "text": "Example text.",
      "createdAt": "2026-10-10T15:36:16Z",
      "lang": "en",
      "author": {
        "id": "1000000000000000001",
        "username": "example_user_1",
        "name": "Example User 1",
        "description": "Example",
        "location": "",
        "url": null,
        "createdAt": "2011-05-17T17:54:29Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
        "verified": true,
        "verifiedType": "Business",
        "protected": false,
        "counts": {
          "followers": 13830347,
          "following": 2351,
          "tweets": 33558,
          "likes": 30446,
          "media": 6619,
          "listed": 8963
        },
        "pinnedTweetIds": [
          "2092752406081634501"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 22,
        "retweets": 6,
        "replies": 4,
        "quotes": 1,
        "bookmarks": 1,
        "views": 7500
      },
      "media": [
        {
          "type": "photo",
          "url": "https://pbs.twimg.com/media/EXAMPLE.jpg",
          "previewUrl": "https://pbs.twimg.com/media/EXAMPLE.jpg",
          "width": 982,
          "height": 1228,
          "altText": null,
          "durationMs": null
        }
      ],
      "urls": [],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2000000000000000001",
      "isPinned": false,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAABCgABHUS7Mid__58KAAIdQPHo7lqQcQgAAwAAAAIAAA",
  "previousCursor": "DAABCgABHUS7MieAJxEKAAIdRI-M3ZvQJQgAAwAAAAEAAA",
  "hasNextPage": true
}
```

#### List members

`GET https://api.twittrapi.com/lists/members`

Accounts that are members of a list.

Reference page: https://www.twittrapi.com/docs/reference/lists-members

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `listId` | query | string | yes |  | Numeric list ID, the number in the list URL (x.com/i/lists/<listId>). |
| `count` | query | integer | no | 20 | Number of members to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's ListMembers query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 400 | listId is missing or not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/lists/members?listId=84839422' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "list": {
      "members_timeline": {
        "timeline": {
          "instructions": [
            {
              "type": "TimelineClearCache"
            },
            {
              "direction": "Top",
              "type": "TimelineTerminateTimeline"
            },
            {
              "entries": [
                {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "MembersSgs",
                      "element": "user"
                    },
                    "entryType": "TimelineTimelineItem",
                    "itemContent": {
                      "__typename": "TimelineUser",
                      "itemType": "TimelineUser",
                      "userDisplayType": "User",
                      "user_results": {
                        "result": {
                          "__typename": "User",
                          "affiliates_highlighted_label": {},
                          "avatar": {
                            "image_url": "https://pbs.twimg.com/profile_images/1354486384329854984/BXv7J1j2_normal.jpg"
                          },
                          "core": {
                            "created_at": "Mon Mar 30 21:02:29 +0000 2020",
                            "name": "Twitter Thailand",
                            "screen_name": "TwitterThailand"
                          },
                          "dm_permissions": {
                            "can_dm": false
                          },
                          "follow_request_sent": false,
                          "has_graduated_access": true,
                          "id": "VXNlcjoxMjQ0NzMxNDkxMDg4ODA5OTg0",
                          "is_blue_verified": false,
                          "legacy": {
                            "default_profile": true,
                            "default_profile_image": false,
                            "description": "Please follow @X for updates.",
                            "entities": {
                              "…": "…"
                            },
                            "fast_followers_count": 0,
                            "favourites_count": 16,
                            "follow_request_sent": false,
                            "followers_count": 37828,
                            "friends_count": 16,
                            "has_custom_timelines": true,
                            "is_translator": false,
                            "listed_count": 39,
                            "media_count": 333,
                            "needs_phone_verification": false,
                            "normal_followers_count": 37828,
                            "notifications": false,
                            "pinned_tweet_ids_str": [
                              "…"
                            ],
                            "possibly_sensitive": false,
                            "profile_banner_url": "https://pbs.twimg.com/profile_banners/1244731491088809984/1611791443",
                            "profile_interstitial_type": "",
                            "statuses_count": 1263,
                            "time_zone": "",
                            "translator_type": "none",
                            "url": "",
                            "utc_offset": 0,
                            "want_retweets": false,
                            "withheld_description": "",
                            "withheld_scope": ""
                          },
                          "location": {
                            "location": ""
                          },
                          "media_permissions": {
                            "can_media_tag": false
                          },
                          "parody_commentary_fan_label": "None",
                          "privacy": {
                            "protected": true
                          },
… (36 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1244731491088809984",
      "username": "TwitterThailand",
      "name": "Twitter Thailand",
      "description": "Please follow @X for updates.",
      "location": "",
      "url": null,
      "createdAt": "2020-03-30T21:02:29Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1354486384329854984/BXv7J1j2.jpg",
      "bannerUrl": "https://pbs.twimg.com/profile_banners/1244731491088809984/1611791443",
      "verified": false,
      "verifiedType": null,
      "protected": true,
      "counts": {
        "followers": 37828,
        "following": 16,
        "tweets": 1263,
        "likes": 16,
        "media": 333,
        "listed": 39
      },
      "pinnedTweetIds": [
        "1580793959307436032"
      ],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "1000000000000000000|2109016359324090346",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### List followers

`GET https://api.twittrapi.com/lists/followers`

Accounts that follow (subscribe to) a list.

Reference page: https://www.twittrapi.com/docs/reference/lists-followers

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `listId` | query | string | yes |  | Numeric list ID, the number in the list URL (x.com/i/lists/<listId>). |
| `count` | query | integer | no | 20 | Number of followers to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's ListSubscribers query.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 400 | listId is missing or not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/lists/followers?listId=84839422' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "list": {
      "subscribers_timeline": {
        "timeline": {
          "instructions": [
            {
              "type": "TimelineClearCache"
            },
            {
              "direction": "Top",
              "type": "TimelineTerminateTimeline"
            },
            {
              "entries": [
                {
                  "content": {
                    "__typename": "TimelineTimelineItem",
                    "clientEventInfo": {
                      "component": "SubscribersSgs",
                      "element": "user"
                    },
                    "entryType": "TimelineTimelineItem",
                    "itemContent": {
                      "__typename": "TimelineUser",
                      "itemType": "TimelineUser",
                      "userDisplayType": "User",
                      "user_results": {
                        "result": {
                          "__typename": "User",
                          "affiliates_highlighted_label": {},
                          "avatar": {
                            "image_url": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg"
                          },
                          "core": {
                            "created_at": "Sat Oct 03 03:56:43 +0000 2026",
                            "name": "Example User 1",
                            "screen_name": "example_user_1"
                          },
                          "dm_permissions": {
                            "can_dm": true
                          },
                          "follow_request_sent": false,
                          "has_graduated_access": false,
                          "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                          "is_blue_verified": false,
                          "legacy": {
                            "default_profile": true,
                            "default_profile_image": false,
                            "description": "",
                            "entities": {
                              "…": "…"
                            },
                            "fast_followers_count": 0,
                            "favourites_count": 1,
                            "follow_request_sent": false,
                            "followers_count": 0,
                            "friends_count": 38,
                            "has_custom_timelines": false,
                            "is_translator": false,
                            "listed_count": 0,
                            "media_count": 1,
                            "needs_phone_verification": false,
                            "normal_followers_count": 0,
                            "notifications": false,
                            "possibly_sensitive": false,
                            "profile_banner_url": "",
                            "profile_interstitial_type": "",
                            "statuses_count": 6,
                            "time_zone": "",
                            "translator_type": "none",
                            "url": "",
                            "utc_offset": 0,
                            "want_retweets": false,
                            "withheld_description": "",
                            "withheld_scope": ""
                          },
                          "location": {
                            "location": ""
                          },
                          "media_permissions": {
                            "can_media_tag": false
                          },
                          "parody_commentary_fan_label": "None",
                          "privacy": {
                            "protected": true
                          },
                          "profile_bio": {
                            "description": ""
                          },
… (33 more lines)
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "",
      "location": "",
      "url": null,
      "createdAt": "2026-10-03T03:56:43Z",
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": null,
      "verified": false,
      "verifiedType": null,
      "protected": true,
      "counts": {
        "followers": 0,
        "following": 38,
        "tweets": 6,
        "likes": 1,
        "media": 1,
        "listed": 0
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "1000000000000000000|2109016363606474730",
  "previousCursor": null,
  "hasNextPage": true
}
```

### Communities

Search communities, read details, members, moderators and tweets.

#### Search communities

`GET https://api.twittrapi.com/communities/search/{searchTerm}`

Find X Communities by keyword.

Reference page: https://www.twittrapi.com/docs/reference/communities-search-searchterm

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `searchTerm` | path | string | yes |  | Keyword, URL-encoded. |
| `count` | query | integer | no | 20 | Number of communities to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `sort` | query | string | no | Relevance | Sort order, passed to X as is. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's CommunitiesSearchQuery. Communities are in data.communities_search_slice.items_results; the next-page cursor is data.communities_search_slice.slice_info.next_cursor (there are no cursor entries).

With `format=simple`: Collection of Community objects (description and rules are empty in search results).

##### Errors

| Status | When |
| --- | --- |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/search/python' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "communities_search_slice": {
      "items_results": [
        {
          "id": "Q29tbXVuaXR5UmVzdWx0czoxNzYyNDk0Mjc2NTY1NDI2NTky",
          "result": {
            "__typename": "Community",
            "custom_banner_media": {
              "id": "QXBpTWVkaWE6DAATCgABG9hLw10WsAAAAA==",
              "media_info": {
                "__typename": "ApiImage",
                "color_info": {
                  "palette": [
                    {
                      "percentage": 71.75,
                      "rgb": {
                        "blue": 74,
                        "green": 45,
                        "red": 21
                      }
                    },
                    "… 4 more"
                  ]
                },
                "original_img_height": 720,
                "original_img_url": "https://pbs.twimg.com/community_banner_img/2006436936446029824/n1OK-QV1?format=jpg&name=orig",
                "original_img_width": 1800,
                "salient_rect": {
                  "height": 1,
                  "left": 899,
                  "top": 360,
                  "width": 1
                }
              }
            },
            "default_banner_media": {
              "id": "QXBpTWVkaWE6DAABCgABFECTH+kUUAQKAAITiFWq8lUQBAAA",
              "media_info": {
                "__typename": "ApiImage",
                "color_info": {
                  "palette": [
                    {
                      "percentage": 57.34,
                      "rgb": {
                        "blue": 178,
                        "green": 171,
                        "red": 3
                      }
                    },
                    "… 4 more"
                  ]
                },
                "original_img_height": 480,
                "original_img_url": "https://pbs.twimg.com/media/FECTH-kUUAQMe8W.jpg",
                "original_img_width": 1200
              }
            },
            "id": "Q29tbXVuaXR5OjE3NjI0OTQyNzY1NjU0MjY1OTI=",
            "is_nsfw": false,
            "member_count": 30718,
            "members_facepile_results": [
              {
                "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
                "result": {
                  "__typename": "User",
                  "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                  "legacy": {
                    "profile_image_url_https": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg"
                  }
                }
              },
              "… 4 more"
            ],
            "name": "AI — Rumors & Insights",
            "primary_community_topic": {
              "topic_name": "Artificial Intelligence"
            },
            "rest_id": "1762494276565426592"
          }
        },
        "… 1 more"
      ],
      "slice_info": {
        "next_cursor": "WzExMy41ODc5MSwxODk3OTI3NDQxNDQxNTEzNzE4XQ=="
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1762494276565426592",
      "name": "AI — Rumors & Insights",
      "description": "",
      "createdAt": null,
      "memberCount": 30718,
      "moderatorCount": 0,
      "joinPolicy": null,
      "nsfw": false,
      "topic": "Artificial Intelligence",
      "bannerUrl": "https://pbs.twimg.com/community_banner_img/2006436936446029824/n1OK-QV1?format=jpg&name=orig",
      "creator": null,
      "rules": []
    },
    "… 1 more"
  ],
  "nextCursor": "WzExMy41ODc5MSwxODk3OTI3NDQxNDQxNTEzNzE4XQ==",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Community details

`GET https://api.twittrapi.com/communities/{communityId}`

Name, description, rules, member count and admins of a community.

Reference page: https://www.twittrapi.com/docs/reference/communities-communityid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `communityId` | path | string | yes |  | Numeric community ID, the number in the community URL. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's CommunityQuery.

With `format=simple`: {data: Community}.

##### Errors

| Status | When |
| --- | --- |
| 422 | communityId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/1500000000000000000' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "communityResults": {
      "id": "Q29tbXVuaXR5UmVzdWx0czoxNzYyNDk0Mjc2NTY1NDI2NTky",
      "result": {
        "__typename": "Community",
        "actions": {
          "id": "Q29tbXVuaXR5QWN0aW9uczoxNzYyNDk0Mjc2NTY1NDI2NTky",
          "join_action_result": {
            "__typename": "CommunityJoinAction"
          },
          "list_members_action_result": {
            "__typename": "CommunityListMembersAction"
          }
        },
        "created_at": 1709046355754,
        "creator_results": {
          "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
          "result": {
            "__typename": "User",
            "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
            "is_blue_verified": true,
            "legacy": {
              "screen_name": "example_user_1",
              "verified": false
            }
          }
        },
        "custom_banner_media": {
          "id": "QXBpTWVkaWE6DAATCgABG9hLw10WsAAAAA==",
          "media_info": {
            "__typename": "ApiImage",
            "color_info": {
              "palette": [
                {
                  "percentage": 71.75,
                  "rgb": {
                    "blue": 74,
                    "green": 45,
                    "red": 21
                  }
                },
                "… 4 more"
              ]
            },
            "original_img_height": 720,
            "original_img_url": "https://pbs.twimg.com/community_banner_img/2006436936446029824/n1OK-QV1?format=jpg&name=orig",
            "original_img_width": 1800
          }
        },
        "default_banner_media": {
          "id": "QXBpTWVkaWE6DAABCgABFECTH+kUUAQKAAITiFWq8lUQBAAA",
          "media_info": {
            "__typename": "ApiImage",
            "color_info": {
              "palette": [
                {
                  "percentage": 57.34,
                  "rgb": {
                    "blue": 178,
                    "green": 171,
                    "red": 3
                  }
                },
                "… 4 more"
              ]
            },
            "original_img_height": 480,
            "original_img_url": "https://pbs.twimg.com/media/FECTH-kUUAQMe8W.jpg",
            "original_img_width": 1200
          }
        },
        "description": "Discuss the state of AI from industry insiders and anons 🕵️‍♂️",
        "id": "Q29tbXVuaXR5OjE3NjI0OTQyNzY1NjU0MjY1OTI=",
        "invites_result": {
          "__typename": "CommunityInvitesUnavailable"
        },
        "is_member": false,
        "is_nsfw": false,
        "join_policy": "Open",
        "join_requests_result": {
          "__typename": "CommunityJoinRequestsUnavailable"
        },
        "member_count": 30718,
        "members_facepile_results": [
          {
            "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
            "result": {
              "__typename": "User",
              "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
… (30 more lines)
```

Simple (`format=simple`):

```json
{
  "data": {
    "id": "1762494276565426592",
    "name": "AI — Rumors & Insights",
    "description": "Discuss the state of AI from industry insiders and anons 🕵️‍♂️",
    "createdAt": "2024-02-27T15:05:55Z",
    "memberCount": 30718,
    "moderatorCount": 0,
    "joinPolicy": "Open",
    "nsfw": false,
    "topic": "Artificial Intelligence",
    "bannerUrl": "https://pbs.twimg.com/community_banner_img/2006436936446029824/n1OK-QV1?format=jpg&name=orig",
    "creator": null,
    "rules": [
      {
        "id": "1985766359352164786",
        "name": "Make relevant and useful posts 🙂",
        "description": "Post useful, relevant, informative AI insights"
      },
      "… 7 more"
    ]
  }
}
```

#### Community tweets

`GET https://api.twittrapi.com/communities/{communityId}/tweets`

Tweets posted in a community.

Reference page: https://www.twittrapi.com/docs/reference/communities-communityid-tweets

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `communityId` | path | string | yes |  | Numeric community ID, the number in the community URL. |
| `count` | query | integer | no | 20 | Number of tweets to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's CommunityTweetsTimeline query.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | communityId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/1500000000000000000/tweets' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "communityResults": {
      "result": {
        "__typename": "Community",
        "ranked_community_timeline": {
          "timeline": {
            "instructions": [
              {
                "type": "TimelineClearCache"
              },
              {
                "entries": [
                  {
                    "content": {
                      "__typename": "TimelineTimelineItem",
                      "clientEventInfo": {
                        "component": "suggest_community_tweet",
                        "details": {
                          "timelinesDetails": {
                            "injectionType": "CommunityTweet"
                          }
                        },
                        "element": "tweet"
                      },
                      "entryType": "TimelineTimelineItem",
                      "itemContent": {
                        "__typename": "TimelineTweet",
                        "itemType": "TimelineTweet",
                        "tweetDisplayType": "Tweet",
                        "tweet_results": {
                          "result": {
                            "__typename": "Tweet",
                            "community_results": {
                              "…": "…"
                            },
                            "core": {
                              "…": "…"
                            },
                            "edit_control": {
                              "…": "…"
                            },
                            "grok_analysis_button": true,
                            "is_translatable": false,
                            "legacy": {
                              "…": "…"
                            },
                            "rest_id": "2000000000000000001",
                            "unmention_data": {
                              "…": "…"
                            },
                            "views": {
                              "…": "…"
                            }
                          }
                        }
                      }
                    },
                    "entryId": "tweet-2000000000000000001",
                    "sortIndex": "2109014787424780286"
                  },
                  "… 2 more"
                ],
                "type": "TimelineAddEntries"
              }
            ],
            "metadata": {
              "scribeConfig": {
                "page": "top"
              }
            }
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "2000000000000000001",
      "url": "https://x.com/example_user_1/status/2000000000000000001",
      "text": "Example text.",
      "createdAt": "2026-10-10T17:30:33Z",
      "lang": "en",
      "author": {
        "id": "1000000000000000001",
        "username": "example_user_1",
        "name": "Example User 1",
        "description": "Example",
        "location": "Example",
        "url": null,
        "createdAt": "2017-11-23T00:48:01Z",
        "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
        "bannerUrl": "https://pbs.twimg.com/profile_banners/1000000000000000000/1700000000",
        "verified": true,
        "verifiedType": null,
        "protected": false,
        "counts": {
          "followers": 732,
          "following": 1918,
          "tweets": 577,
          "likes": 425,
          "media": 261,
          "listed": 0
        },
        "pinnedTweetIds": [
          "2105743229832528175"
        ],
        "isAutomated": false
      },
      "counts": {
        "likes": 1,
        "retweets": 0,
        "replies": 1,
        "quotes": 0,
        "bookmarks": 0,
        "views": 66
      },
      "media": [],
      "urls": [],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2000000000000000001",
      "isPinned": false,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAAFCgABHUS5xm5__-oLAAIAAADwRW1QQzZ3QUFBZlEvZ0dKTjB2R3AvQUFBQUJRZFExYXloNWJ3Y3gxQ0Y2MGtHbEJ5SFVNNVVEbFdvTmNkUWFwdWpWdkJVeDFFVHd1N0d0QUhIVUg1blJFWGtIc2RRZm1RZXBxQmh4MUVxcm9QbTlBNUhVU1VNMUVhNEdvZFFON1ds…",
  "previousCursor": "DAAJAAA",
  "hasNextPage": true
}
```

#### Community members

`GET https://api.twittrapi.com/communities/{communityId}/members`

Members of a community.

Reference page: https://www.twittrapi.com/docs/reference/communities-communityid-members

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `communityId` | path | string | yes |  | Numeric community ID, the number in the community URL. |
| `count` | query | integer | no | 20 | Number of members to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's membersSliceTimeline_Query. Members are in data.communityResults.result.members_slice.items_results; the next-page cursor is members_slice.slice_info.next_cursor.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | communityId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/1500000000000000000/members' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "communityResults": {
      "id": "Q29tbXVuaXR5UmVzdWx0czoxNzYyNDk0Mjc2NTY1NDI2NTky",
      "result": {
        "__typename": "Community",
        "id": "Q29tbXVuaXR5OjE3NjI0OTQyNzY1NjU0MjY1OTI=",
        "members_slice": {
          "items_results": [
            {
              "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
              "result": {
                "__typename": "User",
                "affiliates_highlighted_label": {},
                "community_role": "Admin",
                "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                "identity_profile_labels_highlighted_label": {},
                "is_blue_verified": true,
                "legacy": {
                  "blocking": false,
                  "follow_request_sent": false,
                  "followed_by": false,
                  "following": false,
                  "id_str": "1000000000000000001",
                  "name": "Example User 1",
                  "profile_image_url_https": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
                  "protected": false,
                  "screen_name": "example_user_1",
                  "verified": false
                },
                "rest_id": "1000000000000000001",
                "super_follow_eligible": false,
                "super_followed_by": false,
                "super_following": false
              }
            },
            "… 1 more"
          ],
          "slice_info": {
            "next_cursor": "CycRAAAAAAwABAwKmwzBVQo+vRYlx8zZ15ACCm07AAABjezzdesADJorCj69f/////////8KbTt//////////wAIP8AAAAACAAAA"
          }
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "",
      "location": "",
      "url": null,
      "createdAt": null,
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": null,
      "verified": true,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 0,
        "following": 0,
        "tweets": 0,
        "likes": 0,
        "media": 0,
        "listed": 0
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": "CycRAAAAAAwABAwKmwzBVQo+vRYlx8zZ15ACCm07AAABjezzdesADJorCj69f/////////8KbTt//////////wAIP8AAAAACAAAA",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Search community members

`GET https://api.twittrapi.com/communities/{communityId}/members/search`

Find members of a community whose handle matches a text.

Reference page: https://www.twittrapi.com/docs/reference/communities-communityid-members-search

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `communityId` | path | string | yes |  | Numeric community ID, the number in the community URL. |
| `username` | query | string | yes |  | Text to match, with or without @. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's CommunityMemberRelationshipTypeahead query. Not paginated.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | communityId is not numeric. |
| 400 | username is missing. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/1500000000000000000/members/search?username=jack' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

#### Community moderators

`GET https://api.twittrapi.com/communities/{communityId}/moderators`

Moderators and admins of a community.

Reference page: https://www.twittrapi.com/docs/reference/communities-communityid-moderators

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `communityId` | path | string | yes |  | Numeric community ID, the number in the community URL. |
| `count` | query | integer | no | 20 | Number of moderators to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

Raw JSON of X's moderatorsSliceTimeline_Query. Moderators are in data.communityResults.result.moderators_slice.items_results; slice_info has a next_cursor only when there is another page.

With `format=simple`: Collection of User objects: {data: [User], nextCursor, previousCursor, hasNextPage}.

##### Errors

| Status | When |
| --- | --- |
| 422 | communityId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/communities/1500000000000000000/moderators' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "communityResults": {
      "id": "Q29tbXVuaXR5UmVzdWx0czoxNzYyNDk0Mjc2NTY1NDI2NTky",
      "result": {
        "__typename": "Community",
        "id": "Q29tbXVuaXR5OjE3NjI0OTQyNzY1NjU0MjY1OTI=",
        "moderators_slice": {
          "items_results": [
            {
              "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
              "result": {
                "__typename": "User",
                "affiliates_highlighted_label": {},
                "community_role": "Admin",
                "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                "identity_profile_labels_highlighted_label": {},
                "is_blue_verified": true,
                "legacy": {
                  "blocking": false,
                  "follow_request_sent": false,
                  "followed_by": false,
                  "following": false,
                  "id_str": "1000000000000000001",
                  "name": "Example User 1",
                  "profile_image_url_https": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
                  "protected": false,
                  "screen_name": "example_user_1",
                  "verified": false
                },
                "rest_id": "1000000000000000001",
                "super_follow_eligible": false,
                "super_followed_by": false,
                "super_following": false
              }
            },
            "… 1 more"
          ],
          "slice_info": {}
        }
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1000000000000000001",
      "username": "example_user_1",
      "name": "Example User 1",
      "description": "",
      "location": "",
      "url": null,
      "createdAt": null,
      "avatarUrl": "https://pbs.twimg.com/profile_images/1000000000000000000/example.jpg",
      "bannerUrl": null,
      "verified": true,
      "verifiedType": null,
      "protected": false,
      "counts": {
        "followers": 0,
        "following": 0,
        "tweets": 0,
        "likes": 0,
        "media": 0,
        "listed": 0
      },
      "pinnedTweetIds": [],
      "isAutomated": false
    },
    "… 1 more"
  ],
  "nextCursor": null,
  "previousCursor": null,
  "hasNextPage": false
}
```

### Jobs

Search job listings posted on X.

#### Search jobs

`GET https://api.twittrapi.com/jobs/search/{searchTerm}`

Job listings posted on X, by keyword.

Reference page: https://www.twittrapi.com/docs/reference/jobs-search-searchterm

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `searchTerm` | path | string | yes |  | Keyword, URL-encoded. |
| `count` | query | integer | no | 20 | Number of jobs to ask X for. Sent to X as is; X treats it as a hint and decides how many it returns (follower and following pages often hold 50 to 70 users for count=20). A value that is not a number falls back to 20. |
| `cursor` | query | string | no |  | Bottom cursor from the previous page. Leave it out for the first page. See Pagination. |
| `jobLocationId` | query | string (comma-separated) | no |  | Location IDs from /jobs/find-location/{term}. |
| `jobLocationType` | query | string (comma-separated) | no |  | Location types, passed to X unchanged. |
| `seniorityLevel` | query | string (comma-separated) | no |  | Seniority levels, passed to X unchanged. |
| `employmentType` | query | string (comma-separated) | no |  | Employment types, passed to X unchanged. |
| `companyName` | query | string | no |  | Only jobs from this company. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Notes

- X's job search sometimes fails on X's side (DependencyError); the API retries with other accounts and returns an error if every attempt fails. Retrying a little later usually works.

##### Response

Raw JSON of X's JobSearchQueryScreenJobsQuery. Jobs are in data.job_search.items_results; the next-page cursor is data.job_search.slice_info.next_cursor.

With `format=simple`: Collection of Job objects.

##### Errors

| Status | When |
| --- | --- |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/jobs/search/golang' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "job_search": {
      "items_results": [
        {
          "id": "QXBpSm9iUmVzdWx0czoxOTMzNjIzMTE4NjY2NTcxNzc2",
          "rest_id": "1933623118666571776",
          "result": {
            "__isJobResult": "ApiJob",
            "__typename": "ApiJob",
            "company_profile_results": {
              "id": "QXBpQ29tcGFueVByb2ZpbGVSZXN1bHRzOjE3NTAzNDAyNDIxMDY4ODQyMDE=",
              "rest_id": "1750340242106884201",
              "result": {
                "__typename": "ApiCompanyProfile",
                "core": {
                  "name": "Example User 1"
                },
                "id": "QXBpQ29tcGFueVByb2ZpbGU6MTc1MDM0MDI0MjEwNjg4NDIwMQ==",
                "logo": {
                  "normal_url": "https://pbs.twimg.com/misc_img/1832099493933662208/AsbLJnCG?format=jpg&name=orig&cat=41"
                },
                "rest_id": "1750340242106884201"
              }
            },
            "core": {
              "location": "South San Francisco, California, United States",
              "redirect_url": "https://boards.greenhouse.io/neuralink/jobs/6607398003?gh_jid=6607398003",
              "title": "Electrical Engineer Intern, Robotics and Surgery Engineering"
            },
            "id": "QXBpSm9iOjE5MzM2MjMxMTg2NjY1NzE3NzY=",
            "user_results": {
              "id": "VXNlclJlc3VsdHM6MTAwMDAwMDAwMDAwMDAwMDAwMQ==",
              "result": {
                "__typename": "User",
                "id": "VXNlcjoxMDAwMDAwMDAwMDAwMDAwMDAx",
                "legacy": {
                  "name": "Example User 1",
                  "profile_image_url_https": "https://pbs.twimg.com/profile_images/1000000000000000000/example_normal.jpg",
                  "screen_name": "example_user_1",
                  "verified": false,
                  "verified_type": "Business"
                },
                "profile_image_shape": "Square",
                "rest_id": "1000000000000000001"
              }
            }
          }
        },
        "… 1 more"
      ],
      "slice_info": {
        "next_cursor": "WzMuNDcwMjg4OCwiMCIsIjE4MTMwMzEwNDExNTE3Mzc4NTgiXQ=="
      }
    }
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1933623118666571776",
      "title": "Electrical Engineer Intern, Robotics and Surgery Engineering",
      "location": "South San Francisco, California, United States",
      "url": "https://boards.greenhouse.io/neuralink/jobs/6607398003?gh_jid=6607398003",
      "description": null,
      "company": {
        "id": "1750340242106884201",
        "name": "Example User 1",
        "logoUrl": "https://pbs.twimg.com/misc_img/1832099493933662208/AsbLJnCG?format=jpg&name=orig&cat=41"
      },
      "postedBy": {
        "id": "1000000000000000001",
        "username": "example_user_1"
      }
    },
    "… 1 more"
  ],
  "nextCursor": "WzMuNDcwMjg4OCwiMCIsIjE4MTMwMzEwNDExNTE3Mzc4NTgiXQ==",
  "previousCursor": null,
  "hasNextPage": true
}
```

#### Job details

`GET https://api.twittrapi.com/jobs/{jobId}`

One job listing by ID.

Reference page: https://www.twittrapi.com/docs/reference/jobs-jobid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `jobId` | path | string | yes |  | Numeric job ID. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's JobScreenQuery.

With `format=simple`: {data: Job}.

##### Errors

| Status | When |
| --- | --- |
| 422 | jobId is not numeric. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/jobs/1234567890' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

#### Find job location

`GET https://api.twittrapi.com/jobs/find-location/{term}`

Location IDs for the jobLocationId filter of job search. Returns up to 10 matches.

Reference page: https://www.twittrapi.com/docs/reference/jobs-find-location-term

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `term` | path | string | yes |  | Place name, URL-encoded. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's LocationSelectorQuery.

With `format=simple`: Collection of {id, name} location suggestions (no cursor).

##### Errors

| Status | When |
| --- | --- |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/jobs/find-location/Berlin' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
{
  "data": {
    "location_type_ahead": [
      {
        "display_name": "Land Berlin, DE",
        "location_id": "1720462559101268315"
      },
      "… 2 more"
    ]
  }
}
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "id": "1720462559101268315",
      "name": "Land Berlin, DE"
    },
    "… 2 more"
  ],
  "nextCursor": null,
  "previousCursor": null,
  "hasNextPage": false
}
```

### Trends

Trending topics by location (WOEID).

#### Trends by location

`GET https://api.twittrapi.com/trends/{locationId}`

Current trending topics for a place, by WOEID.

Reference page: https://www.twittrapi.com/docs/reference/trends-locationid

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `locationId` | path | string | yes |  | Yahoo WOEID, digits only (1 to 12). 1 is worldwide, 23424977 the United States. /misc/locations lists them all. |

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's v1.1 trends/place endpoint: an array with one object holding trends (name, url, query, tweet_volume), as_of and locations.

With `format=simple`: Collection of Trend objects {name, query, url, tweetVolume} (no cursor). query is URL-decoded.

##### Errors

| Status | When |
| --- | --- |
| 400 | locationId is missing or not a numeric WOEID. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/trends/1' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

#### Trend locations

`GET https://api.twittrapi.com/misc/locations`

All places X has trends for, with their WOEIDs.

Reference page: https://www.twittrapi.com/docs/reference/misc-locations

##### Parameters

None.

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's v1.1 trends/available endpoint: an array of places with name, woeid, country and placeType.

With `format=simple`: Collection of TrendLocation objects {woeid, name, country, countryCode, placeType, parentId} (no cursor).

##### Errors

| Status | When |
| --- | --- |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/misc/locations' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

### Misc

Hashflags (hashtags with a custom emoji).

#### Hashflags

`GET https://api.twittrapi.com/misc/emojis`

X's hashflags: hashtags that show a custom emoji, with image URLs and active dates.

Reference page: https://www.twittrapi.com/docs/reference/misc-emojis

##### Parameters

None.

Every read endpoint also accepts `format=simple` (or the header `X-Response-Format: simple`) for compact, flat JSON with a fixed schema. Same price.

##### Response

Raw JSON of X's v1.1 hashflags.json.

With `format=simple`: Unchanged inside {data: ...}: X's hashflag list is already small, so the simple format only adds the envelope.

##### Errors

| Status | When |
| --- | --- |
| 400 | Any upstream failure ("Error making emoji request."). Refunded. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl 'https://api.twittrapi.com/misc/emojis' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

##### Example response

Shortened: arrays keep their first items and long strings are cut.

Raw (default):

```json
[
  {
    "hashtag": "13周年大カーン謝祭",
    "starting_timestamp_ms": 1783857600000,
    "ending_timestamp_ms": 1791817140000,
    "asset_url": "https://abs.twimg.com/hashflags/BF-16276_Hashmoji_monst_mmix_13rdAnniv/BF-16276_Hashmoji_monst_mmix_13rdAnniv.png",
    "is_hashfetti_enabled": false
  },
  "… 7 more"
]
```

Simple (`format=simple`):

```json
{
  "data": [
    {
      "hashtag": "13周年大カーン謝祭",
      "starting_timestamp_ms": 1783857600000,
      "ending_timestamp_ms": 1791817140000,
      "asset_url": "https://abs.twimg.com/hashflags/BF-16276_Hashmoji_monst_mmix_13rdAnniv/BF-16276_Hashmoji_monst_mmix_13rdAnniv.png",
      "is_hashfetti_enabled": false
    },
    "… 7 more"
  ]
}
```

---

## Account actions

POST endpoints that act on the caller's own X account. They need the account's session cookie and the caller's own proxy (http://user:pass@host:port) in the JSON body. Source: https://www.twittrapi.com/docs/account-actions

### Post a tweet

`POST https://api.twittrapi.com/client/create`

Post a tweet, quote tweet, long tweet, community tweet or scheduled tweet from your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-create

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `text` | body | string | yes |  | Tweet text. Over 280 characters it is sent as a long post (note tweet). |
| `media_ids` | body | string[] | no |  | Media IDs from /client/media or /client/media/video. |
| `possibly_sensitive` | body | boolean | no | false | Mark the media as sensitive. |
| `conversation_control_mode` | body | string | no |  | Who can reply: only_follows, only_verified or only_mentioned. Not used for community tweets. |
| `quote_url` | body | string | no |  | URL of a tweet to quote. |
| `community_id` | body | string | no |  | Post into this community. |
| `is_note_tweet` | body | boolean | no | false | Force a long post even for short text. |
| `schedule_time` | body | integer | no |  | Unix time in seconds. When set, the tweet is scheduled instead of posted; only text and media are used. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: Tweet}: the tweet you just posted. With schedule_time: {data: {success: true, scheduledTweetId}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/create' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","text":"Hello from the API"}'
```

### Reply to a tweet

`POST https://api.twittrapi.com/client/reply`

Reply to a tweet from your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-reply

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `id` | body | string | yes |  | ID of the tweet to reply to. |
| `text` | body | string | yes |  | Reply text. |
| `media_ids` | body | string[] | no |  | Media IDs from /client/media. |
| `attachment_url` | body | string | no |  | URL of a tweet to quote in the reply. |
| `possibly_sensitive` | body | boolean | no | false | Mark the media as sensitive. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: Tweet}: the reply you just posted. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/reply' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","id":"20","text":"Nice"}'
```

### Like a tweet

`POST https://api.twittrapi.com/client/like`

Like a tweet from your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-like

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to like. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/like' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"20"}'
```

### Retweet

`POST https://api.twittrapi.com/client/retweet`

Retweet a tweet from your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-retweet

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to retweet. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true, retweetId}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/retweet' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"20"}'
```

### Delete a tweet

`POST https://api.twittrapi.com/client/deleteTweet`

Delete one of your own tweets.

Reference page: https://www.twittrapi.com/docs/account-actions/client-deletetweet

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to delete. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/deleteTweet' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"1790000000000000000"}'
```

### Follow a user

`POST https://api.twittrapi.com/client/follow`

Follow an account from your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-follow

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `user_id` | body | string | no |  | Numeric ID of the account to follow. Either user_id or username is required. |
| `username` | body | string | no |  | Handle of the account to follow. Used instead of user_id when set. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: User}: the account you followed, the same User object as the read endpoints. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| 400 | Neither user_id nor username is set, or the user could not be found. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/follow' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","username":"XDevelopers"}'
```

### Bookmark a tweet

`POST https://api.twittrapi.com/client/bookmark`

Add a tweet to your bookmarks.

Reference page: https://www.twittrapi.com/docs/account-actions/client-bookmark

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to bookmark. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/bookmark' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"20"}'
```

### Remove a bookmark

`POST https://api.twittrapi.com/client/unbookmark`

Remove a tweet from your bookmarks.

Reference page: https://www.twittrapi.com/docs/account-actions/client-unbookmark

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to remove from your bookmarks. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/unbookmark' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"20"}'
```

### List bookmarks

`POST https://api.twittrapi.com/client/bookmarks`

Tweets in your bookmarks.

Reference page: https://www.twittrapi.com/docs/account-actions/client-bookmarks

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `count` | body | integer | no | 20 | Tweets per page. |
| `cursor` | body | string | no |  | Bottom cursor from the previous page. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/bookmarks' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","count":20}'
```

### Home timeline

`POST https://api.twittrapi.com/client/home-timeline`

The home timeline of your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-home-timeline

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `count` | body | integer | no | 20 | Tweets to ask X for. |
| `include_promoted_content` | body | boolean | no | true | Include promoted tweets. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: Collection of Tweet objects: {data: [Tweet], nextCursor, previousCursor, hasNextPage}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/home-timeline' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","count":20}'
```

### List scheduled tweets

`POST https://api.twittrapi.com/client/schedule-overview`

Tweets scheduled on your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-schedule-overview

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `ascending` | body | boolean | no | true | Oldest first when true. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: Collection of scheduled tweets {id, text, executeAt, state, mediaIds}, without cursors (hasNextPage false). executeAt is RFC 3339. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/schedule-overview' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port"}'
```

### Delete a scheduled tweet

`POST https://api.twittrapi.com/client/schedule-delete`

Cancel a scheduled tweet.

Reference page: https://www.twittrapi.com/docs/account-actions/client-schedule-delete

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `scheduled_tweet_id` | body | string | yes |  | ID from /client/schedule-overview. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/schedule-delete' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","scheduled_tweet_id":"1790000000000000000"}'
```

### Upload an image

`POST https://api.twittrapi.com/client/media`

Upload an image and get a media ID for /client/create or /client/reply.

Reference page: https://www.twittrapi.com/docs/account-actions/client-media

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `media` | body | string (base64) | yes |  | The image file, base64-encoded. JPEG, PNG or WebP up to 5 MB, GIF up to 15 MB; at most 8192 x 8192 pixels. |
| `media_type` | body | string | no |  | MIME type, e.g. image/jpeg or image/png. Optional: the API detects the type from the file's bytes and uses the detected type if this is missing or wrong. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Notes

- The response field you need is media_id_string inside response.
- Limits (X): JPEG, PNG, WebP up to 5 MB; GIF up to 15 MB; at most 8192 x 8192 pixels. BMP, TIFF and other formats are refused. For videos use /client/media/video.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {mediaId, mediaKey, expiresAfterSecs}}. Pass mediaId in media_ids of /client/create or /client/reply. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| 400 | The file breaks X's limits (not JPEG, PNG, GIF or WebP; over 5 MB, or 15 MB for a GIF; over 8192 x 8192 pixels; or a damaged image header). Checked before anything is sent to X, refunded. The error names the limit and the file's value, e.g. "Banner must be between 200x100 and 8192x8192 pixels (got 120x60)" or "Profile photo must be at most 2 MB (got 3.4 MB)". |
| 422 | media is not valid base64. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/media' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","media":"<base64>","media_type":"image/jpeg"}'
```

### Upload a video

`POST https://api.twittrapi.com/client/media/video`

Upload a video in 5 MB chunks, wait for X to process it, and get a media ID.

Reference page: https://www.twittrapi.com/docs/account-actions/client-media-video

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `media` | body | string (base64) | yes |  | The video file, base64-encoded. MP4 or MOV, up to 512 MB (X's limit; the gateway's 100 MB body limit applies first). |
| `media_type` | body | string | no |  | MIME type, e.g. video/mp4 or video/quicktime. Optional: the API detects the type from the file's bytes and uses the detected type if this is missing or wrong. |
| `video_duration_ms` | body | integer | yes |  | Length of the video in milliseconds. Must be under 4 hours; X allows 140 seconds without Premium and refuses longer videos itself. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Notes

- Request bodies are limited to 100 MB at the gateway (413 above that). With base64 overhead that is a video of about 75 MB.
- Limits (X): MP4 or MOV, up to 512 MB; up to 140 seconds without Premium, under 4 hours with Premium.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {mediaId, mediaKey, expiresAfterSecs}}. Pass mediaId in media_ids of /client/create or /client/reply. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| 400 | The file breaks X's limits (not MP4 or MOV, over 512 MB, or video_duration_ms not positive or 4 hours or more). Checked before anything is sent to X, refunded. The error names the limit and the file's value, e.g. "Banner must be between 200x100 and 8192x8192 pixels (got 120x60)" or "Profile photo must be at most 2 MB (got 3.4 MB)". |
| 422 | media is not valid base64, or X reports that processing failed. |
| 408 | X did not finish processing in time. Refunded. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/media/video' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","media":"<base64>","media_type":"video/mp4","video_duration_ms":12000}'
```

### Update your profile

`POST https://api.twittrapi.com/client/profile`

Change the name, bio, location, website or birth date of your account. Fields you leave out stay as they are.

Reference page: https://www.twittrapi.com/docs/account-actions/client-profile

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

Runs the same request as the Edit profile dialog on x.com. Send at least one of the profile fields; an empty string clears description, location or url.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `name` | body | string | no |  | Display name, up to 50 characters. Cannot be empty. |
| `description` | body | string | no |  | Bio, up to 160 characters. |
| `location` | body | string | no |  | Location, up to 30 characters. |
| `url` | body | string | no |  | Website, up to 100 characters. |
| `birthdate` | body | object \| null | no |  | {day, month, year, visibility?, year_visibility?}. visibility and year_visibility are public, followers, following, mutualfollow or self; left out, X keeps the current setting. The date must be real, not in the future and at most 120 years back. null removes the birth date. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Notes

- On a verified (Premium) account, x.com warns that changing the name can remove the checkmark until X has reviewed the change.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON. On success response is your updated user object (X's v1.1 user: name, description, location, url, profile_image_url_https, ...).

With `format=simple`: {data: User}: your profile after the change, the same User object as the read endpoints. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, or the cookie has no ct0. No profile field is set, a field is too long or not a string, name is empty, or birthdate is not a valid date or visibility. Also: the account is suspended, or X rejected the session. |
| 407 | The proxy is unreachable or timed out. |
| X's status | X refused the change: answered with X's status and {"error": "X refused the profile update: <X's reason>"}; when the image upload itself fails: "X refused the image upload: <reason>". |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/profile' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","description":"Building things with the API","location":"Berlin"}'
```

### Change your profile picture

`POST https://api.twittrapi.com/client/profile/image`

Upload an image and set it as the profile picture of your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-profile-image

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

The image is uploaded like /client/media and then set as your avatar in one call. x.com crops the picture to a square in the browser before uploading; do the same if you want a specific part shown.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `media` | body | string (base64) | yes |  | The image file, base64-encoded. JPEG, PNG or GIF, up to 2 MB, at most 8192 x 8192 pixels (400 x 400 recommended). |
| `media_type` | body | string | no |  | MIME type, e.g. image/jpeg or image/png. Optional: the API detects the type from the file's bytes and uses the detected type if this is missing or wrong. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Notes

- Limits (X): JPEG, PNG or GIF, up to 2 MB, at most 8192 x 8192 pixels. X does not animate GIFs in profile pictures.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON. On success response is your updated user object; the new picture is in profile_image_url_https.

With `format=simple`: {data: User}: your profile with the new avatarUrl, the same User object as the read endpoints. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 400 | The file breaks X's limits (not JPEG, PNG or GIF; over 2 MB; over 8192 x 8192 pixels; or a damaged image header). Checked before anything is sent to X, refunded. The error names the limit and the file's value, e.g. "Banner must be between 200x100 and 8192x8192 pixels (got 120x60)" or "Profile photo must be at most 2 MB (got 3.4 MB)". |
| 422 | cookie or proxy is missing, or the cookie has no ct0. media is missing, not valid base64, or empty. Also: the account is suspended, or X rejected the session. |
| 407 | The proxy is unreachable or timed out. |
| X's status | X refused the change: answered with X's status and {"error": "X refused the profile update: <X's reason>"}; when the image upload itself fails: "X refused the image upload: <reason>". |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/profile/image' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","media":"<base64>","media_type":"image/jpeg"}'
```

### Change your header image

`POST https://api.twittrapi.com/client/profile/banner`

Upload an image and set it as the header (banner) of your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-profile-banner

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

The image is uploaded like /client/media and then set as your banner in one call. X shows headers at 3:1 (1500 x 500 recommended); x.com crops in the browser before uploading, so crop the image yourself first.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `media` | body | string (base64) | yes |  | The image file, base64-encoded. JPEG, PNG or GIF, up to 5 MB, between 200 x 100 and 8192 x 8192 pixels (1500 x 500 recommended). |
| `media_type` | body | string | no |  | MIME type, e.g. image/jpeg or image/png. Optional: the API detects the type from the file's bytes and uses the detected type if this is missing or wrong. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Notes

- Limits (X): JPEG, PNG or GIF, up to 5 MB, between 200 x 100 and 8192 x 8192 pixels. X does not animate GIFs in headers.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON. On success response is your updated user object; the new header is in profile_banner_url.

With `format=simple`: {data: User}: your profile with the new bannerUrl, the same User object as the read endpoints. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 400 | The file breaks X's limits (not JPEG, PNG or GIF; over 5 MB; smaller than 200 x 100 or larger than 8192 x 8192 pixels; or a damaged image header). Checked before anything is sent to X, refunded. The error names the limit and the file's value, e.g. "Banner must be between 200x100 and 8192x8192 pixels (got 120x60)" or "Profile photo must be at most 2 MB (got 3.4 MB)". |
| 422 | cookie or proxy is missing, or the cookie has no ct0. media is missing, not valid base64, or empty. Also: the account is suspended, or X rejected the session. |
| 407 | The proxy is unreachable or timed out. |
| X's status | X refused the change: answered with X's status and {"error": "X refused the profile update: <X's reason>"}; when the image upload itself fails: "X refused the image upload: <reason>". |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/profile/banner' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","media":"<base64>","media_type":"image/jpeg"}'
```

### Report a tweet

`POST https://api.twittrapi.com/client/report`

Report a tweet and its author to X.

Reference page: https://www.twittrapi.com/docs/account-actions/client-report

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `tweet_id` | body | string | yes |  | ID of the tweet to report. |
| `user_id` | body | string | yes |  | Numeric ID of the tweet's author. |
| `reason` | body | string | yes |  | One of SpamSimpleOption, HateOrAbuseSimpleOption, ChildSafetySimpleOption, ViolentSpeechSimpleOption, ViolentMediaSimpleOption, IRBSimpleOption, ImpersonationSimpleOption, AdultContentSimpleOption, PrivateContentSimpleOption, SuicideSelfHarmSimpleOption, TerrorismSimpleOption, CivicIntegritySimpleOption. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/report' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","tweet_id":"1790000000000000000","user_id":"12","reason":"SpamSimpleOption"}'
```

### Create a community

`POST https://api.twittrapi.com/client/communities/create`

Create an X Community owned by your account.

Reference page: https://www.twittrapi.com/docs/account-actions/client-communities-create

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `name` | body | string | yes |  | Community name. |
| `description` | body | string | no |  | Community description. |
| `joinPolicy` | body | string | no | Open | Passed to X as is. |
| `invitesPolicy` | body | string | no | MemberInvitesAllowed | Passed to X as is. |
| `question` | body | string | no |  | Question for people who ask to join. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: Community}: the community you created. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie, proxy or name is missing. |
| 500 | The session could not be prepared (for example the cookie has no ct0, or the proxy is unreachable). Refunded. |
| X's status | Any status X answers with: passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/communities/create' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","name":"API testers"}'
```

### Delete a community

`POST https://api.twittrapi.com/client/communities/remove`

Delete a community your account owns.

Reference page: https://www.twittrapi.com/docs/account-actions/client-communities-remove

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `community_id` | body | string | yes |  | Community ID. |
| `name` | body | string | yes |  | Community name. Sent to X together with the ID. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

Wrapper built by the API: {"status_code": <X status>, "response": "<X response body as a string>"}. The HTTP status is X's status when X answered 4xx or 5xx, otherwise 200. Parse response a second time to get X's JSON.

With `format=simple`: {data: {success: true}}. When X reports an error inside the wrapper (for example a tweet that is already liked), the usual wrapper is returned unchanged.

#### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | A required field is missing (including proxy), an ID is not numeric, or the cookie has no ct0. Also: the account is suspended, X rejected the session, or the text was already posted. |
| 407 | The proxy is unreachable or timed out. |
| X's status | Any status X answers with (for example 401, 403, 404): passed through with X's body. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/communities/remove' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port","community_id":"1500000000000000000","name":"API testers"}'
```

### Account analytics overview

`POST https://api.twittrapi.com/premium/analyticsOverview`

The account analytics overview of your X account (needs X Premium).

Reference page: https://www.twittrapi.com/docs/account-actions/premium-analyticsoverview

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

#### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token and ct0; guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

#### Response

X's AccountOverviewQuery response, passed through with X's status code and content type. Not wrapped in {status_code, response}.

#### Errors

| Status | When |
| --- | --- |
| 400 | cookie is missing, the body is not valid JSON, the cookie has no ct0, or the proxy URL is invalid. |
| 422 | proxy is missing. |
| 502 | The request to X failed (for example through the proxy). Refunded. |
| X's status | Any status X answers with: passed through. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

#### Example request

```bash
curl -X POST 'https://api.twittrapi.com/premium/analyticsOverview' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...","proxy":"http://user:pass@host:port"}'
```

### Direct messages

Read and send direct messages of your own X account, with the same cookie and proxy as the other account actions. Direct messages on X are end-to-end encrypted (X Chat).

To read or send messages, the API also needs your X Chat passcode. It unlocks your account's chat keys; messages are then decrypted and encrypted on our server while your request runs. The API does not store messages or the passcode. Listing conversations, reading a conversation's settings and your DM settings work without a passcode.

Each call is one billed call like the other account actions. The answers are JSON built by the API (not the {status_code, response} wrapper); format=simple returns the same data in the simple format.

#### List conversations

`POST https://api.twittrapi.com/client/dm/inbox`

Your DM inbox: conversations with participants and settings, and with the passcode their latest messages.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-inbox

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

Without a passcode you get the conversations, their participants and settings, but no message text. With the passcode each conversation also carries its latest messages, decrypted.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `passcode` | body | string | no |  | Your X Chat passcode, the one you set up for encrypted messages on X. Without it the conversations are listed without messages. Send it as a string (a number works too, but loses leading zeros), up to 64 characters. It unlocks your chat keys for this request and is not saved. The unlocked keys stay in the server's memory for up to 30 minutes, so follow-up calls need no new unlock. |
| `count` | body | integer | no | 20 | Conversations to return, 1 to 50. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Notes

- 501 and the passcode errors only occur when a passcode is sent.

##### Response

{conversations: [...], message_requests_count, decrypted}. decrypted is true when a passcode was sent. Each conversation: {conversation_id, type (direct or group), name (groups), muted, disappearing_messages {enabled, ttl_seconds}, screen_capture_blocking, participants [{id, screen_name, name, avatar, is_blue_verified}], detail (X's conversation_detail)}, messages (with passcode: the latest ones, newest first), undecrypted (events that could not be read: {id, sender_id, error}), decrypt_error (when the conversation could not be decrypted) and events (other decrypted events such as read receipts and key changes). Each message: id (sequence ID, also the cursor unit), message_id, conversation_id, sender_id, created_at and expires_at (Unix milliseconds; expires_at only in chats with disappearing messages), type (text, reaction, reaction_removed, edit, deleted, ...), text, verified (the sender's signature was checked) and content (the decrypted message as X's chat library returns it: reactions, replies, attachments). Expired disappearing messages are left out.

With `format=simple`: Collection of conversations without cursors: {data: [id, type (direct or group), name, muted, disappearingMessages {enabled, ttlSeconds}, screenCaptureBlocking, participants [{id, username, name, avatarUrl, verified}], lastMessage, messages]}. Message objects {id, senderId, createdAt, expiresAt, type, text, emoji, targetId, replyTo {id, senderId, text}, attachments [{kind, type, filename, filesizeBytes, durationMs, width, height, url, mediaHashKey}], verified}; times are RFC 3339, unknown values null. Without a passcode lastMessage is null and messages is [].

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. count is not a number from 1 to 50. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 422 | Wrong passcode: "Wrong passcode (N attempts left before X deletes the chat keys)". X deletes the chat keys after too many wrong passcodes, so the API never sends the same wrong passcode to X twice (for 24 hours it answers 422 at once), and once only 3 attempts are left it does not try at all: "Only a few passcode attempts are left for this account; unlock Chat in the X app with the correct passcode first". |
| 422 | The account has no chat passcode yet: open Chat on x.com once and set one up. |
| 501 | Encrypted messages are not available on this server (the passcode cannot be used). Refunded. |
| 502 | The chat keys could not be unlocked ("Could not unlock the chat keys, try again") or the messages could not be decrypted. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/inbox' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","passcode":"1234","count":20}'
```

#### Read a conversation

`POST https://api.twittrapi.com/client/dm/conversation`

Messages of one conversation, decrypted, newest first, with a cursor for older messages.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-conversation

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `passcode` | body | string | yes |  | Your X Chat passcode, the one you set up for encrypted messages on X. Send it as a string (a number works too, but loses leading zeros), up to 64 characters. It unlocks your chat keys for this request and is not saved. The unlocked keys stay in the server's memory for up to 30 minutes, so follow-up calls need no new unlock. |
| `conversation_id` | body | string | no |  | ID of the conversation, as returned by /client/dm/inbox. A one-to-one chat has the ID <smaller user ID>:<larger user ID>. Letters, digits, colon, hyphen and underscore, up to 128 characters. Either conversation_id or user_id is required. |
| `user_id` | body | string | no |  | Numeric ID of the other person (up to 20 digits) for a one-to-one chat. Used when conversation_id is not set. |
| `cursor` | body | string | no |  | cursor from the previous page (digits only, up to 20) to get older messages. Leave it out for the newest page. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

Paginated: pass the bottom cursor of one page as `cursor` to get the next. Each page is one billed call.

##### Response

{conversation_id, messages, has_more, cursor, message_request_state, undecrypted, events}. messages are newest first; cursor is set when has_more is true. undecrypted lists events the API could not read ({id, sender_id, error}; for example data x.com keeps in your own conversation), events the other decrypted events (read receipts, settings and key changes). Each message: id (sequence ID, also the cursor unit), message_id, conversation_id, sender_id, created_at and expires_at (Unix milliseconds; expires_at only in chats with disappearing messages), type (text, reaction, reaction_removed, edit, deleted, ...), text, verified (the sender's signature was checked) and content (the decrypted message as X's chat library returns it: reactions, replies, attachments). Expired disappearing messages are left out.

With `format=simple`: Collection of Message objects {id, senderId, createdAt, expiresAt, type, text, emoji, targetId, replyTo {id, senderId, text}, attachments [{kind, type, filename, filesizeBytes, durationMs, width, height, url, mediaHashKey}], verified}; times are RFC 3339, unknown values null. nextCursor is the cursor for older messages, null on the last page.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. The passcode is missing, neither conversation_id nor user_id is set, user_id is not numeric, conversation_id or cursor has the wrong format. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 422 | Wrong passcode: "Wrong passcode (N attempts left before X deletes the chat keys)". X deletes the chat keys after too many wrong passcodes, so the API never sends the same wrong passcode to X twice (for 24 hours it answers 422 at once), and once only 3 attempts are left it does not try at all: "Only a few passcode attempts are left for this account; unlock Chat in the X app with the correct passcode first". |
| 422 | The account has no chat passcode yet: open Chat on x.com once and set one up. |
| 501 | Encrypted messages are not available on this server (the passcode cannot be used). Refunded. |
| 502 | The chat keys could not be unlocked ("Could not unlock the chat keys, try again") or the messages could not be decrypted. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/conversation' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","passcode":"1234","user_id":"12"}'
```

#### Conversation details

`POST https://api.twittrapi.com/client/dm/conversation/info`

Participants and settings of one conversation: disappearing messages, screen capture blocking, muted. No passcode needed.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-conversation-info

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `conversation_id` | body | string | no |  | ID of the conversation, as returned by /client/dm/inbox. A one-to-one chat has the ID <smaller user ID>:<larger user ID>. Letters, digits, colon, hyphen and underscore, up to 128 characters. Either conversation_id or user_id is required. |
| `user_id` | body | string | no |  | Numeric ID of the other person (up to 20 digits) for a one-to-one chat. Used when conversation_id is not set. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Response

{conversation_id, type (direct or group), name (groups), muted, disappearing_messages {enabled, ttl_seconds}, screen_capture_blocking, participants [{id, screen_name, name, avatar, is_blue_verified}], detail (X's conversation_detail)}. ttl_seconds is set when disappearing messages are on.

With `format=simple`: {data: {id, type (direct or group), name, muted, disappearingMessages {enabled, ttlSeconds}, screenCaptureBlocking, participants [{id, username, name, avatarUrl, verified}]}}. ttlSeconds is null when disappearing messages are off.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. Neither conversation_id nor user_id is set, user_id is not numeric, or conversation_id has the wrong format. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 404 | Conversation not found. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/conversation/info' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","user_id":"12"}'
```

#### Disappearing messages

`POST https://api.twittrapi.com/client/dm/conversation/disappearing`

Turn disappearing messages of a conversation on, change the time, or turn them off.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-conversation-disappearing

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

New messages in the conversation are deleted ttl_seconds after they were sent. Like x.com, the change is signed with your chat key, so the passcode is required. The API reads the setting back and answers with the conversation as it is afterwards; when the conversation already has the requested setting, nothing is changed.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `passcode` | body | string | yes |  | Your X Chat passcode, the one you set up for encrypted messages on X. Send it as a string (a number works too, but loses leading zeros), up to 64 characters. It unlocks your chat keys for this request and is not saved. The unlocked keys stay in the server's memory for up to 30 minutes, so follow-up calls need no new unlock. |
| `conversation_id` | body | string | no |  | ID of the conversation, as returned by /client/dm/inbox. A one-to-one chat has the ID <smaller user ID>:<larger user ID>. Letters, digits, colon, hyphen and underscore, up to 128 characters. Either conversation_id or user_id is required. |
| `user_id` | body | string | no |  | Numeric ID of the other person (up to 20 digits) for a one-to-one chat. Used when conversation_id is not set. |
| `ttl_seconds` | body | integer | yes |  | 0 turns disappearing messages off; otherwise 60 (1 minute) to 31536000 (365 days). |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Response

The conversation after the change, like /client/dm/conversation/info: {conversation_id, type (direct or group), name (groups), muted, disappearing_messages {enabled, ttl_seconds}, screen_capture_blocking, participants [{id, screen_name, name, avatar, is_blue_verified}], detail (X's conversation_detail)}.

With `format=simple`: {data: {id, type (direct or group), name, muted, disappearingMessages {enabled, ttlSeconds}, screenCaptureBlocking, participants [{id, username, name, avatarUrl, verified}]}}.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. The passcode or ttl_seconds is missing, ttl_seconds is not 0 or between 60 and 31536000, or the conversation is not set. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 404 | Conversation not found. |
| 422 | Wrong passcode: "Wrong passcode (N attempts left before X deletes the chat keys)". X deletes the chat keys after too many wrong passcodes, so the API never sends the same wrong passcode to X twice (for 24 hours it answers 422 at once), and once only 3 attempts are left it does not try at all: "Only a few passcode attempts are left for this account; unlock Chat in the X app with the correct passcode first". |
| 422 | The account has no chat passcode yet: open Chat on x.com once and set one up. |
| 501 | Encrypted messages are not available on this server (the passcode cannot be used). Refunded. |
| 502 | The chat keys could not be unlocked ("Could not unlock the chat keys, try again") or the messages could not be decrypted. Refunded. |
| 502 | X accepted the change but the setting did not change ("X accepted the change but did not apply it"), or the change could not be signed. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/conversation/disappearing' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","passcode":"1234","user_id":"12","ttl_seconds":86400}'
```

#### Send a message

`POST https://api.twittrapi.com/client/dm/send`

Send an encrypted text message to a conversation or to a user.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-send

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

The message is encrypted and signed with your chat key, like on x.com. The first message to a person you have not written to yet starts the encrypted conversation; that person needs X Chat (encrypted messages) on their account. Text only, no media.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `passcode` | body | string | yes |  | Your X Chat passcode, the one you set up for encrypted messages on X. Send it as a string (a number works too, but loses leading zeros), up to 64 characters. It unlocks your chat keys for this request and is not saved. The unlocked keys stay in the server's memory for up to 30 minutes, so follow-up calls need no new unlock. |
| `conversation_id` | body | string | no |  | ID of the conversation, as returned by /client/dm/inbox. A one-to-one chat has the ID <smaller user ID>:<larger user ID>. Letters, digits, colon, hyphen and underscore, up to 128 characters. Either conversation_id or user_id is required. |
| `user_id` | body | string | no |  | Numeric ID of the other person (up to 20 digits) for a one-to-one chat. Used when conversation_id is not set. |
| `text` | body | string | yes |  | Message text, up to 10,000 characters. Must not be empty or only spaces. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Response

{conversation_id, id (sequence ID), message_id, created_at (Unix milliseconds), text}.

With `format=simple`: {data: {id, messageId, conversationId, createdAt, text}}. createdAt is RFC 3339.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. The passcode or text is missing, text is longer than 10,000 characters, neither conversation_id nor user_id is set, or one of them has the wrong format. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 422 | The recipient cannot receive encrypted messages (no X Chat key), or the conversation has no key you can use (for example a group you cannot write to). |
| 422 | Wrong passcode: "Wrong passcode (N attempts left before X deletes the chat keys)". X deletes the chat keys after too many wrong passcodes, so the API never sends the same wrong passcode to X twice (for 24 hours it answers 422 at once), and once only 3 attempts are left it does not try at all: "Only a few passcode attempts are left for this account; unlock Chat in the X app with the correct passcode first". |
| 422 | The account has no chat passcode yet: open Chat on x.com once and set one up. |
| 501 | Encrypted messages are not available on this server (the passcode cannot be used). Refunded. |
| 502 | The chat keys could not be unlocked ("Could not unlock the chat keys, try again") or the messages could not be decrypted. Refunded. |
| 502 | The message or a new conversation key could not be encrypted. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/send' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","passcode":"1234","user_id":"12","text":"Hello"}'
```

#### DM settings

`POST https://api.twittrapi.com/client/dm/settings`

Who can message you, read receipts, the message quality filter and audio/video call settings. No passcode needed.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-settings

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Response

{allow_dms_from, dm_receipt_settings, dm_quality_filter, av_settings: {has_av_calls_enabled, av_call_permissions: {accept_calls_from_everyone, accept_calls_from_following, accept_calls_from_verified, accept_calls_from_addressbook}}}. Values as X sends them; dm_receipt_settings and dm_quality_filter are left out when X does not send them.

With `format=simple`: {data: {allowDmsFrom, readReceipts, qualityFilter, calls: {enabled, fromEveryone, fromFollowing, fromVerified, fromAddressBook}}}.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters.  Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 502 | X returned no DM settings. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/settings' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port"}'
```

#### Change DM settings

`POST https://api.twittrapi.com/client/dm/settings/update`

Change who can message you, read receipts and call settings. Settings you leave out stay as they are.

Reference page: https://www.twittrapi.com/docs/account-actions/client-dm-settings-update

> Account actions run with the session cookie of your own X account and go out through your own proxy. Use only accounts you control and follow X's rules; automated posting, following or liking can get the account limited or suspended. Treat the cookie like a password. Trial keys cannot call these endpoints.

The API reads your current settings, applies the fields you send and saves the full set, like x.com does. Send at least one setting. No passcode needed.

##### Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `cookie` | body | string | yes |  | Cookie header of your own logged-in X session. Must contain auth_token, ct0 and twid (your account ID, set by x.com at login); guest_id is used when present. |
| `proxy` | body | string | yes |  | Your proxy, format http://user:pass@host:port. The requests to X go out through it. Used only for this request. |
| `allow_dms_from` | body | string | no |  | Who can send you messages, X's value such as all or verified (/client/dm/settings shows your current one). Lower-case letters and underscore, up to 64 characters; X decides which values it accepts. |
| `dm_receipt_settings` | body | string | no |  | Read receipts, X's value (same format as allow_dms_from). |
| `always_allow_dms_from_subscribers` | body | boolean | no |  | Always accept messages from your subscribers. |
| `av_calls_enabled` | body | boolean | no |  | Allow audio and video calls. |
| `accept_calls_from_everyone` | body | boolean | no |  | Accept calls from everyone. |
| `accept_calls_from_following` | body | boolean | no |  | Accept calls from people you follow. |
| `accept_calls_from_verified` | body | boolean | no |  | Accept calls from verified users. |
| `accept_calls_from_addressbook` | body | boolean | no |  | Accept calls from your contacts. |

Body parameters are sent as a JSON object with `Content-Type: application/json`.

##### Response

Your DM settings after the change, like /client/dm/settings.

With `format=simple`: {data: {allowDmsFrom, readReceipts, qualityFilter, calls: {enabled, fromEveryone, fromFollowing, fromVerified, fromAddressBook}}}.

##### Errors

| Status | When |
| --- | --- |
| 400 | The body is not valid JSON. |
| 422 | cookie or proxy is missing, the cookie has no twid or ct0, or the passcode is neither a string nor a whole number or longer than 64 characters. No setting is sent, a string setting is empty, too long or has other characters than lower-case letters and underscore, or a boolean setting is not true or false. Also: the account is suspended, or X rejected the session ("Twitter temporarily rejected this session; refresh the login and try again"). |
| 407 | The proxy is unreachable or timed out. |
| 502 | X returned no DM settings. Refunded. |
| 429 | X's rate limit for this account: "X rate limit reached for this account, try again later". |
| X's status | X refused the request: answered with X's status and {"error": "X refused the request: <X's reason>"}. When X answers 200 with an error, the status is 422. |

Shared errors (401 missing key, 402 no balance, 429 rate limit, 5xx) are listed on https://www.twittrapi.com/docs/helpful-infos/error-handling. Calls that fail with 400, 408, 409 or a 5xx error are refunded.

##### Example request

```bash
curl -X POST 'https://api.twittrapi.com/client/dm/settings/update' \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{"cookie":"auth_token=...; ct0=...; twid=u%3D...","proxy":"http://user:pass@host:port","allow_dms_from":"verified","av_calls_enabled":false}'
```
