API Docs
Authentication, endpoints, errors, rate limits, credits, MCP tools and the command-line tool. English only; code examples are the same in every language.
Create an API keyOverview
The TwitterViewer API reads public X (Twitter) content and returns it as clean Markdown, Obsidian notes, plain text, JSON or PDF. It is an independent service. It is not X's official API, it is not affiliated with X Corp., and you do not need an X developer account to use it. It cannot post, like, follow, send messages or read private accounts.
The base URL is https://twitterviewer.app. Endpoints live under /api/v1. Requests and responses are JSON in UTF-8 unless a section says otherwise. Every endpoint is billed in credits: 1 credit is 1 post returned.
| Endpoint | Method | What it does |
|---|---|---|
| /api/v1/convert | POST, GET | A post, a thread or a profile as Markdown, Obsidian, text, JSON or PDF |
| /{handle}/status/{id} | GET | The same as convert, using the post address with the domain swapped |
| /api/v1/search | GET | Search public posts (Pro and Business) |
| /api/v1/replies | GET | The replies under a post |
| /api/v1/trends | GET | What is trending in a region |
| /api/v1/credits | GET | Your credit balance, free of charge |
| /mcp | POST | Remote MCP server (Streamable HTTP) |
Authentication
Create a key on the API & credits page after signing in. The full key is shown once; only a hash is stored, so a lost key has to be replaced. Keys look like tv_live_ followed by 32 letters and digits. Send the key as a Bearer token:
Authorization: Bearer tv_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCalls are billed to the account that owns the key. Keep keys out of client-side code and public repositories; delete a key on the API page the moment it leaks. A request with no key or a wrong key gets 401 and costs nothing. Keys per account: Plus 2, Pro 5, Business 20, and 1 for a free account.
Convert a link
Turns an X link into a document. Send a post link to get the thread by the same author, a profile link to get recent posts. POST takes a JSON body; GET takes the same names as query parameters, which is handy with curl.
| Name | Type | Description |
|---|---|---|
url | string, required | A link to a post, thread or profile on x.com or twitter.com, or the same address on twitterviewer.app. |
scope | thread / post / conversation | Default thread: the author's connected posts. post: only that post. conversation: the thread plus the first page of replies. Profile links always return recent posts. |
format | json / markdown / obsidian / text / pdf | Default json. obsidian adds YAML frontmatter. pdf returns a PDF file and is part of Pro and Business. |
stats | all / root / off | Default all: counts on every post. root: only the first post. off: no counts. |
max | integer 1–200 | Profile links only: how many recent posts to return. Default 20. |
replies | boolean | Profile links only: include the account's replies. Default false. |
reposts | boolean | Profile links only: include reposts. Default false. |
paper | a4 / letter | Page size for format=pdf. Default a4. |
Request
curl -X POST https://twitterviewer.app/api/v1/convert \
-H "Authorization: Bearer $TWITTERVIEWER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://x.com/username/status/1234567890123456789", "scope": "thread", "format": "markdown", "stats": "root" }'Response
With format set to markdown, obsidian or text and the header Accept: text/markdown (or text/plain) the body is only the document, and the post count comes in the x-posts-returned header. Otherwise you get JSON:
{
"kind": "thread",
"title": "How we shipped the new release:",
"sourceUrl": "https://x.com/username/status/1234567890123456789",
"author": { "handle": "username", "name": "Example", "followers": 1204 },
"markdown": "# How we shipped the new release…",
"text": "How we shipped the new release…",
"posts": [ { "id": "1234567890123456789", "text": "…" } ],
"replies": [],
"postsReturned": 31,
"wordCount": 2214,
"credits": { "charged": 31, "remaining": 2969 }
}| Name | Type | Description |
|---|---|---|
kind | string | post, thread, conversation or profile. |
title | string | The first 60 characters of the first post, cut at a word boundary. For profiles, "Posts by Name (@handle)". |
sourceUrl | string | The x.com address of the source. |
author | object | Name, handle, bio, followers, verification and join date, as far as X shows them. |
markdown | string | The rendered document in the requested format (markdown or obsidian). |
text | string | The same content as plain text. |
posts | array | The posts as structured data: id, text, date, media, counts, quoted post. |
replies | array | Replies, only for scope=conversation. |
postsReturned | integer | How many posts this call returned. This is the credit cost. |
wordCount | integer | Words in the rendered document. |
credits | object | charged and remaining credits after this call. |
Plain body example, saved to a file:
curl "https://twitterviewer.app/api/v1/convert?url=https://x.com/username/status/1234567890123456789&format=obsidian" \
-H "Authorization: Bearer $TWITTERVIEWER_API_KEY" \
-H "Accept: text/markdown" -o thread.mdformat=pdf returns application/pdf with the posts in order, images and post links, on A4 or Letter. It needs Pro or Business; on other plans the call fails with 402 LIMIT_REACHED and SERVER_PDF_REQUIRES_PRO before anything is fetched. If rendering fails, nothing is charged.
Repeat calls
The same key asking for the same link with the same parameters again within 10 minutes is not charged a second time.
Swap the domain
Replace x.com in a post or profile address with twitterviewer.app. When the request carries a valid key and an Accept header that does not ask for HTML, the response is the Markdown body, the same as convert. A browser that asks for HTML still gets the normal viewer page. Without a key the call returns 401 with a note on how to get one, and nothing is fetched from X.
The query parameters scope, format, stats and max work as in convert. ?thread=off is the same as scope=post.
# x.com/username/status/1234567890123456789 becomes:
curl -H "Authorization: Bearer $TWITTERVIEWER_API_KEY" \
https://twitterviewer.app/username/status/1234567890123456789
# only the first post, as Obsidian Markdown
curl -H "Authorization: Bearer $TWITTERVIEWER_API_KEY" \
"https://twitterviewer.app/username/status/1234567890123456789?scope=post&format=obsidian"Search posts
Searches public posts with X search operators such as from:, min_faves:, since: and lang:. X only serves search to signed-in users, so this endpoint is part of Pro and Business and needs an X account connected on the X account page. Other plans get 403 X_ACCOUNT_REQUIRED. Billed 1 credit per post returned.
| Name | Type | Description |
|---|---|---|
q | string, required | The query, with X search operators. Up to 500 characters. |
max | integer 1–100 | How many posts to return. Default 20. |
curl "https://twitterviewer.app/api/v1/search?q=from%3Ausername%20min_faves%3A100&max=20" \
-H "Authorization: Bearer $TWITTERVIEWER_API_KEY"
{
"query": "from:username min_faves:100",
"posts": [ { "id": "…", "text": "…" } ],
"postsReturned": 20,
"credits": { "charged": 20, "remaining": 2949 }
}Read replies
Returns the public replies under a post. The first page needs no X account; further pages are included when your plan and connected X account allow them, otherwise you get the first page. Billed 1 credit per reply returned.
| Name | Type | Description |
|---|---|---|
url | string, required | A link to an X post. |
max | integer 1–100 | How many replies to return. Default 20. |
curl "https://twitterviewer.app/api/v1/replies?url=https://x.com/username/status/1234567890123456789&max=30" \
-H "Authorization: Bearer $TWITTERVIEWER_API_KEY"
{
"sourceUrl": "https://x.com/username/status/1234567890123456789",
"replies": [ { "id": "…", "text": "…" } ],
"postsReturned": 30,
"credits": { "charged": 30, "remaining": 2919 }
}Trends
Returns what is trending in a region, cached for about 10 minutes. Billed 1 credit per call, however many trends come back.
| Name | Type | Description |
|---|---|---|
region | string | One of worldwide, united-states (default), united-kingdom, japan, saudi-arabia, india, brazil or germany. |
curl "https://twitterviewer.app/api/v1/trends?region=united-states" \
-H "Authorization: Bearer $TWITTERVIEWER_API_KEY"
{
"region": "united-states",
"trends": [ { "name": "…", "postCount": "…" } ],
"credits": { "charged": 1, "remaining": 2918 }
}Check your credits
Returns your balance split by source. It costs nothing and still counts toward the rate limit.
curl https://twitterviewer.app/api/v1/credits -H "Authorization: Bearer $TWITTERVIEWER_API_KEY"
{
"total": 3050,
"plan": { "remaining": 3000, "granted": 3000, "expiresAt": "2026-11-22T00:00:00.000Z" },
"purchased": { "remaining": 50 }
}plan is the monthly pool of your subscription and expiresAt is when it resets. purchased covers credit packs and the 50 sign-up credits, which never expire.
How credits are counted
- 1 credit = 1 post returned. A quoted post counts as one more post. Author details are free.
- Replies, search results and conversation replies count the same way. Trends cost 1 credit per call.
- Failed calls are free: errors, empty results and PDF rendering failures are not charged.
- A call is refused with
402when the balance is 0. If the balance is lower than the result, you get as many posts as the balance covers. - Credits are spent from the pool that expires first: the monthly plan pool, then purchased credits.
- Every account gets 50 credits once, at sign-up. Plans add a monthly pool; credit packs (1,000 credits, 6,000 credits, 20,000 credits) are one-time and never expire.
- The same key asking for the same link with the same parameters within 10 minutes is not charged twice. On the website everyone gets 1 free conversion a day; the API always uses credits.
Rate limits and plans
Requests are limited per account per minute. Every response carries x-ratelimit-remaining; credit-billed responses also carry x-credits-charged and x-credits-remaining. Past the limit you get 429 RATE_LIMITED with retryAfterSec.
| Plan | Credits a month | Requests a minute, API keys, PDF |
|---|---|---|
| Free | 0 (+ 50 at sign-up) | 10 a minute · 1 key · no PDF |
| Plus | 3,000 | 30 a minute · 2 keys · no PDF |
| Pro | 8,000 | 60 a minute · 5 keys · PDF |
| Business | 25,000 | 300 a minute · 20 keys · PDF |
Prices and credit packs are on the pricing page. Without an account, the website allows 3 views a day; the API always needs a key.
Errors
Errors use one shape: { "error": { "code", "message", "retryAfterSec?", "details?" } }, with the HTTP status in the table below.
HTTP/1.1 402 Payment Required
{
"error": {
"code": "LIMIT_REACHED",
"message": "Not enough credits",
"details": { "reason": "INSUFFICIENT_CREDITS", "required": 31, "balance": 12 }
}
}| Code | Status | When |
|---|---|---|
INVALID_INPUT | 400 | A parameter is missing or has the wrong value, or the link is not an X post or profile. |
UNAUTHORIZED | 401 | No API key, or the key is wrong or deleted. Nothing is fetched from X and nothing is charged. |
LIMIT_REACHED | 402 | details.reason says why: INSUFFICIENT_CREDITS (balance too low; details.required and details.balance) or SERVER_PDF_REQUIRES_PRO (PDF output needs Pro or Business). |
PROTECTED | 403 | The account is protected. Only public content is available. |
X_ACCOUNT_REQUIRED | 403 | Search needs a Pro or Business plan with a connected X account. details.reason is upgrade or connect. |
NOT_FOUND | 404 | The post or account does not exist or was deleted. |
SUSPENDED | 410 | The account is suspended. |
RATE_LIMITED | 429 | More than your plan allows per minute. Wait retryAfterSec seconds (also in the Retry-After header). |
UPSTREAM | 502 | The X data source failed. Nothing is charged; retry in a few seconds. |
SERVICE_UNAVAILABLE | 503 | A part of the service, such as PDF rendering, is not available right now. Nothing is charged. |
MCP server
The remote MCP server at https://twitterviewer.app/mcp speaks Streamable HTTP. Sign in with your browser the first time a client connects, or send an API key as Authorization: Bearer. All tools are read-only and use the same credits as the REST API. The full guide is on the Twitter MCP Server page.
| Tool | Arguments | What it does |
|---|---|---|
convert_x_url | url, scope, format, stats, max | Turns a post, thread or profile link into Markdown, Obsidian, text or JSON. |
search_x_posts | query, max | Searches public posts (Pro and Business). |
get_x_replies | url, max | Reads the replies under a post. |
get_x_trends | region | Lists trends for a region. |
get_credits | none | Shows the balance. |
Client configuration
Claude Code
claude mcp add --transport http twitterviewer https://twitterviewer.app/mcpCursor
{
"mcpServers": {
"twitterviewer": { "url": "https://twitterviewer.app/mcp" }
}
}Codex
[mcp_servers.twitterviewer]
url = "https://twitterviewer.app/mcp"When credits run out, a tool answers with one line and a link to the pricing page instead of an error.
Command-line tool
The twitterviewer package on npm wraps the API. It reads the key from TWITTERVIEWER_API_KEY first, then from the file twitterviewer login writes.
npx twitterviewer https://x.com/username/status/1234567890123456789 --scope thread --format obsidian -o thread.md
npx twitterviewer login # opens your browser, approve the device code
npx twitterviewer credits # prints your balance
npx twitterviewer logout # removes the saved key| Name | Type | Description |
|---|---|---|
<link> or @handle | string | A post, thread or profile link, or an @handle for a profile's recent posts. |
--scope | thread / post / conversation | Same as the API. Default thread. |
--format | md / obsidian / json / text / pdf | Output format. Default md. pdf needs -o and a Pro or Business plan. |
--stats | all / root / off | Engagement numbers. Default all. |
--max | integer 1–200 | Profile only: how many posts to export. Default 20. |
--replies, --reposts | flags | Profile only: include replies or reposts. |
--paper | a4 / letter | PDF paper size. Default a4. |
-o, --output | path | Write to a file instead of the terminal. |
Exit codes: 0 ok, 1 error, 2 bad arguments, 3 sign-in or credits needed.
Agent Skill
An Agent Skill teaches an agent what to do when it meets an X link: swap the domain, call the CLI or use the MCP tools. It is published at https://twitterviewer.app/.well-known/agent-skills/twitterviewer/SKILL.md. Point your agent at that address or copy the file into its skills folder.
The site also publishes /llms.txt for agents, and every page has a Markdown version at the same address plus .md.