TwitterViewer
Reference

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 key

Overview

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.

EndpointMethodWhat it does
/api/v1/convertPOST, GETA post, a thread or a profile as Markdown, Obsidian, text, JSON or PDF
/{handle}/status/{id}GETThe same as convert, using the post address with the domain swapped
/api/v1/searchGETSearch public posts (Pro and Business)
/api/v1/repliesGETThe replies under a post
/api/v1/trendsGETWhat is trending in a region
/api/v1/creditsGETYour credit balance, free of charge
/mcpPOSTRemote 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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Calls 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

POST · GET/api/v1/convert

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.

NameTypeDescription
urlstring, requiredA link to a post, thread or profile on x.com or twitter.com, or the same address on twitterviewer.app.
scopethread / post / conversationDefault 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.
formatjson / markdown / obsidian / text / pdfDefault json. obsidian adds YAML frontmatter. pdf returns a PDF file and is part of Pro and Business.
statsall / root / offDefault all: counts on every post. root: only the first post. off: no counts.
maxinteger 1–200Profile links only: how many recent posts to return. Default 20.
repliesbooleanProfile links only: include the account's replies. Default false.
repostsbooleanProfile links only: include reposts. Default false.
papera4 / letterPage 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 }
}
NameTypeDescription
kindstringpost, thread, conversation or profile.
titlestringThe first 60 characters of the first post, cut at a word boundary. For profiles, "Posts by Name (@handle)".
sourceUrlstringThe x.com address of the source.
authorobjectName, handle, bio, followers, verification and join date, as far as X shows them.
markdownstringThe rendered document in the requested format (markdown or obsidian).
textstringThe same content as plain text.
postsarrayThe posts as structured data: id, text, date, media, counts, quoted post.
repliesarrayReplies, only for scope=conversation.
postsReturnedintegerHow many posts this call returned. This is the credit cost.
wordCountintegerWords in the rendered document.
creditsobjectcharged 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.md

PDF

format=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

GET/{handle}/status/{id}

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"

Read replies

GET/api/v1/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.

NameTypeDescription
urlstring, requiredA link to an X post.
maxinteger 1–100How 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 }
}

Check your credits

GET/api/v1/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 402 when 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.

PlanCredits a monthRequests a minute, API keys, PDF
Free0 (+ 50 at sign-up)10 a minute · 1 key · no PDF
Plus3,00030 a minute · 2 keys · no PDF
Pro8,00060 a minute · 5 keys · PDF
Business25,000300 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 }
  }
}
CodeStatusWhen
INVALID_INPUT400A parameter is missing or has the wrong value, or the link is not an X post or profile.
UNAUTHORIZED401No API key, or the key is wrong or deleted. Nothing is fetched from X and nothing is charged.
LIMIT_REACHED402details.reason says why: INSUFFICIENT_CREDITS (balance too low; details.required and details.balance) or SERVER_PDF_REQUIRES_PRO (PDF output needs Pro or Business).
PROTECTED403The account is protected. Only public content is available.
X_ACCOUNT_REQUIRED403Search needs a Pro or Business plan with a connected X account. details.reason is upgrade or connect.
NOT_FOUND404The post or account does not exist or was deleted.
SUSPENDED410The account is suspended.
RATE_LIMITED429More than your plan allows per minute. Wait retryAfterSec seconds (also in the Retry-After header).
UPSTREAM502The X data source failed. Nothing is charged; retry in a few seconds.
SERVICE_UNAVAILABLE503A 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.

ToolArgumentsWhat it does
convert_x_urlurl, scope, format, stats, maxTurns a post, thread or profile link into Markdown, Obsidian, text or JSON.
search_x_postsquery, maxSearches public posts (Pro and Business).
get_x_repliesurl, maxReads the replies under a post.
get_x_trendsregionLists trends for a region.
get_creditsnoneShows the balance.

Client configuration

Claude Code

claude mcp add --transport http twitterviewer https://twitterviewer.app/mcp

Cursor

{
  "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
NameTypeDescription
<link> or @handlestringA post, thread or profile link, or an @handle for a profile's recent posts.
--scopethread / post / conversationSame as the API. Default thread.
--formatmd / obsidian / json / text / pdfOutput format. Default md. pdf needs -o and a Pro or Business plan.
--statsall / root / offEngagement numbers. Default all.
--maxinteger 1–200Profile only: how many posts to export. Default 20.
--replies, --repostsflagsProfile only: include replies or reposts.
--papera4 / letterPDF paper size. Default a4.
-o, --outputpathWrite 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.