Sandbox
@pc-style/x-md

X to Markdown and JSON for agents

x-md turns public X URLs, profiles, searches, and timelines into compact Markdown or structured JSON. It serves the same data through normal web routes, an OpenAPI surface, and an MCP server, so agents can read X content in the format they handle best.

141 stars7 forksTypeScriptUpdated 6d ago
Who it's for

Builders who want their agent to browse public X content, search it, or import post histories.

What it delivers

You can give an agent a public X URL or query and get structured, readable content back instead of raw page scraping.

What it does

Post conversion

Converts a public status URL into compact Markdown, expanded Markdown, Obsidian, or JSON, with thread context, replies, media URLs, and quoted posts when available.

Profile and search browsing

Returns profile data, followers, following, and search results in Markdown or JSON, with pagination and filters like feed, since, until, and with_replies.

Bulk post import

Fetches an account's post history in bulk with options like since, until, max_posts, format, concurrency, and refresh.

MCP and OpenAPI surfaces

Exposes the same reads through `/mcp` and `/openapi.json` so agents can call it as tools or through a documented HTTP API.

Agent skill

Provides a `browse-x` skill installable with `bunx skills add pc-style/x-md -g -y --skill browse-x`.

How to get it

  1. 1Replace x.com with x.pcstyle.dev on a public status URL
    https://x.com/handle/status/1234567890
    https://x.pcstyle.dev/handle/status/1234567890
  2. 2Run
    curl -sS -H 'Accept: text/markdown' \
      'https://x.pcstyle.dev/handle/status/1234567890'
    
    curl -sS -G 'https://x.pcstyle.dev/api/v1/posts' \
      --data-urlencode 'url=https://x.com/handle/status/1234567890'
  3. 3The default limit is 20 and the maximum is 100; every page is cut exactly at limit…
    curl -sS 'https://x.pcstyle.dev/elonmusk'
    curl -sS 'https://x.pcstyle.dev/search?q=typescript&feed=latest&limit=20'
    curl -sS 'https://x.pcstyle.dev/elonmusk/followers?full=true'
    curl -sS -H 'Accept: application/json' \
      'https://x.pcstyle.dev/elonmusk/following?limit=20'
  4. 4Run
    curl -sS 'https://x.pcstyle.dev/api/v1/profiles/elonmusk'
    
    curl -sS -G 'https://x.pcstyle.dev/api/v1/search' \
      --data-urlencode 'q=typescript' \
      --data-urlencode 'feed=top' \
      --data-urlencode 'format=json'
  5. 5GET /{handle}/posts returns an account's history in bulk — thousands of posts, replies…
    curl -sS 'https://x.pcstyle.dev/paulg/posts?since=2025-09-01&max_posts=2000'
    curl -sN 'https://x.pcstyle.dev/paulg/posts?since=2026-06-01&format=ndjson'   # stream
    curl -sS 'https://x.pcstyle.dev/paulg/posts?index=true'                        # what is archived already
  6. 6Install the hosted skill, browse-x, with the skills CLI
    bunx skills add pc-style/x-md -g -y --skill browse-x

README

x.md

Turn public X posts, conversations, profiles, search results, and social graphs into compact Markdown for agents. The hosted API is available at x.pcstyle.dev; no X API key is required for the default provider path.

[!IMPORTANT] Status: beta. Routes and output fields can change as upstream X providers change. The compatibility target for self-hosting is Bun 1.4 (see .bun-version) and the locked dependencies in this repository.

Not affiliated with X Corp. Public lists are not available.

What it returns

  • Compact, agent-friendly Markdown by default; add ?full=true for dates, metrics, and richer profile details.
  • A source URL for every post and reply, including quoted posts.
  • Direct video URLs, thumbnails, duration/dimensions/bitrate when supplied, and all available video variants.
  • Conversation context by default: parents, the author's thread, and top replies. Use context and replies to narrow it.
  • Structured JSON with the rendered Markdown and raw post/profile data via ?format=json or Accept: application/json.
  • Profiles with profile data and up to 20 latest original posts by default.
  • Search, followers, and following, with cursor or bounded page pagination.

Use the hosted API

Replace x.com with x.pcstyle.dev on a public status URL:

https://x.com/handle/status/1234567890
https://x.pcstyle.dev/handle/status/1234567890
curl -sS -H 'Accept: text/markdown' \
  'https://x.pcstyle.dev/handle/status/1234567890'

curl -sS -G 'https://x.pcstyle.dev/api/v1/posts' \
  --data-urlencode 'url=https://x.com/handle/status/1234567890'

Browsers that request HTML get a readable page containing the Markdown. Agents can explicitly request text/markdown. Discord, Telegram, Slack, and other preview bots receive Open Graph embed HTML for the same status URL, including multiple images on Discord, video streams where supported, video thumbnails on Slack, quote/poll text, and an oEmbed engagement line.

For agents

Everything here is public, needs no key, and is meant to be read by a program:

SurfaceWhereWhat it is
Markdown negotiationany read route, or /, /docs, /aboutAccept: text/markdown returns Markdown; docs pages also answer to a .md suffix or ?mode=agent. Responses carry Vary: Accept and a Link: …; rel="alternate" to the Markdown twin
/llms.txtx.pcstyle.dev/llms.txtWhat x.md is for, when not to use it, and every route, in one text file
/openapi.jsonx.pcstyle.dev/openapi.jsonOpenAPI 3.1: parameters, response schemas, error bodies, quotas, lifecycle
/apix.pcstyle.dev/apiA small JSON index for an agent that has only the domain
/mcpx.pcstyle.dev/mcpMCP server over Streamable HTTP, exposing the same reads as tools
Agent skillbunx skills add pc-style/x-md -g -y --skill browse-xThe browse-x skill for coding agents
/.well-known/ard.jsonx.pcstyle.dev/.well-known/ard.jsonAgentic Resource Discovery catalog of every machine surface above

Errors are RFC 9457 problem documents (application/problem+json) with a stable machine code, a human detail, and a resolution hint — see the error catalogue. Rate limits are advertised on every response with the IETF RateLimit-Policy and RateLimit structured fields plus the RateLimit-Limit/-Remaining/-Reset compatibility triple, and Retry-After on a 429.

Versioning

/api/v1/* is the stable machine surface: /api/v1/posts, /api/v1/profiles/{handle} (/followers, /following), /api/v1/search, /api/v1/oembed. Additive changes ship inside v1; breaking ones ship as a new path prefix, and v1 keeps working for at least 12 months after a successor appears. The permalink routes (/{handle}, /{handle}/status/{id}, /search, /oembed) are the unversioned product surface and are not deprecated.

GET /api/convert and GET /api/browse are aliases scheduled for deprecation on 2026-09-15, with a 2027-09-15 sunset. They still work unchanged and already announce the schedule on every response:

Deprecation: @1789430400
Sunset: Wed, 15 Sep 2027 00:00:00 GMT
Link: <https://x.pcstyle.dev/api/v1/posts>; rel="successor-version"

Full policy: x.pcstyle.dev/docs/versioning.

Post conversion

Both GET /:handle/status/:id and GET /api/v1/posts?url=… support:

ParameterDefaultSupported values
formatmarkdownmarkdown, obsidian, json
fullfalsetrue, 1, or yes enables expanded Markdown; Obsidian is always expanded
threadfulloff, full, conversation, or a limit from 2 to 100
contextfullfull includes parents, author thread, and selected replies; thread excludes unrelated replies
repliestoptop, recent, off
userinfooffoff, author, all
nocachefalsetrue, 1, or yes bypasses the application cache

thread=off returns only the requested post. The default conversation result is ordered and labels posts as parent, post, thread, or reply when that data is available. Provider fallbacks can return less context, article content, or quote data.

# Expanded conversation without replies
curl -sS 'https://x.pcstyle.dev/handle/status/1234567890?full=true&replies=off'

# Author thread only, capped at 20 posts
curl -sS 'https://x.pcstyle.dev/handle/status/1234567890?context=thread&thread=20'

# Structured output
curl -sS -H 'Accept: application/json' \
  'https://x.pcstyle.dev/handle/status/1234567890'
curl -sS 'https://x.pcstyle.dev/handle/status/1234567890?format=json'

JSON conversion responses contain url, markdown, raw posts, compact, warnings, postCount, source, cache, and format. Media in both Markdown and posts includes direct video data when the upstream provider exposes it; availability and lifetime of X CDN URLs are controlled by X.

Preview bots hitting GET /:handle/status/:id receive embed HTML instead of Markdown. GET /oembed is the Discord oEmbed document advertised from that HTML. Explicit ?format= or Accept: application/json / text/markdown still wins over user-agent detection.

Browse profiles and X

Browse routes return compact Markdown by default and structured data with ?format=json or Accept: application/json.

RouteBehavior
GET /:handleProfile data and latest original posts (replies and reposts filtered out)
GET /search?q=…Search posts or users; feed=latest, top, photos, videos, or users (media aliases photos) (invalid values fall back to latest)
GET /:handle/followersFollowers
GET /:handle/followingAccounts followed

The default limit is 20 and the maximum is 100; every page is cut exactly at limit (search feeds served by own accounts answer 20). Pass the opaque cursor returned as nextCursor — there is no ceiling on a cursor chain — or use page=1 through page=10; values above 10 are clamped. Profile reads return original posts unless with_replies=true / with_reposts=true is set, and until=<date> jumps straight to a date. Search takes since and until.

curl -sS 'https://x.pcstyle.dev/elonmusk'
curl -sS 'https://x.pcstyle.dev/search?q=typescript&feed=latest&limit=20'
curl -sS 'https://x.pcstyle.dev/elonmusk/followers?full=true'
curl -sS -H 'Accept: application/json' \
  'https://x.pcstyle.dev/elonmusk/following?limit=20'

Versioned browse routes

curl -sS 'https://x.pcstyle.dev/api/v1/profiles/elonmusk'

curl -sS -G 'https://x.pcstyle.dev/api/v1/search' \
  --data-urlencode 'q=typescript' \
  --data-urlencode 'feed=top' \
  --data-urlencode 'format=json'

The older GET /api/browse?resource=profile|search|followers|following alias takes the same options and still works, but it is deprecated and sunsets on 2027-09-15.

Search feeds are case-insensitive. users returns account profiles in users; other feeds return posts. Latest and Top try FxTwitter first, then a custom-built live search provider, and can fall back to web-indexed snippets. Photos, Videos, and Users use the live provider directly.

Search limits

  • Live search allows 5 uncached requests per minute per IP. Cache hits are free.
  • Requests served by the live provider have an additional allowance of 10 per IP per 15-minute window, drawn from a shared public pool. All feeds share it, and each page of a page walk counts as one request.
  • A rejected request returns 429 with Retry-After in seconds until the window resets. An upstream outage returns 503 with Retry-After: 30.

Every response advertises what is left, so a client can pace itself instead of discovering the limit by hitting it:

RateLimit-Policy: "api-ip";q=600;w=60, "search-ip";q=5;w=60, "account-ip";q=10;w=900
RateLimit: "api-ip";r=599;t=60, "search-ip";r=5;t=60, "account-ip";r=10;t=900
RateLimit-Limit: 5
RateLimit-Remaining: 5
RateLimit-Reset: 60

q is the quota, w the window in seconds, r what is left, t the seconds until that window resets. The unprefixed RateLimit-Limit/-Remaining/-Reset triple describes the tightest policy. Ignore these values on a response with a positive Age: a CDN hit replays the state of whoever filled the cache.

Counters are per instance unless a shared KV store is configured.

Browse JSON includes the resource-specific profile, posts, or users, plus page, limit, optional nextCursor, rendered markdown, and cache status. The verified upstream profile API does not expose pinned-post markers, and public X lists are explicitly unsupported.

Import a post history

GET /{handle}/posts returns an account's history in bulk — thousands of posts, replies included, raw JSON, in seconds:

curl -sS 'https://x.pcstyle.dev/paulg/posts?since=2025-09-01&max_posts=2000'
curl -sN 'https://x.pcstyle.dev/paulg/posts?since=2026-06-01&format=ndjson'   # stream
curl -sS 'https://x.pcstyle.dev/paulg/posts?index=true'                        # what is archived already

X hands out a timeline one cursor at a time; x.md mints cursors for arbitrary instants and walks many chains at once, then stores what it collected so the next import only fetches the gap. Measured: 1379 posts in 7.8 s at concurrency=32 against the public upstream, 8× a sequential walk at equal completeness (bench/RESULTS-scale.md). Parameters: since, until, max_posts (≤5000), with_replies, with_reposts, only_replies, concurrency (≤32 per upstream), format=json|ndjson, refresh, index. Full guide: Import a post history.

Agent skill

Install the hosted skill, browse-x, with the skills CLI:

bunx skills add pc-style/x-md -g -y --skill browse-x

The skill uses https://x.pcstyle.dev; it does not require a local checkout or local API keys. Its helper is a TypeScript CLI (bun skills/browse-x/scripts/browse-x.ts …) that needs Bun, and exits with code 3 on a rate limit after printing Retry-After, so agents know exactly how long to wait.

The skills CLI command follows the repository's current default branch. For a reviewable, immutable copy, check out the newest tag on the releases page and copy skills/browse-x from that checkout.

Caching and reliability

FxTwitter is the primary data provider and X's syndication endpoint is the fallback. Self-hosted deployments may additionally configure Context.dev and Firecrawl. X-Source reports fxtwitter, syndication, contextdev, or firecrawl; X-Cache reports cache status. Browse endpoints use FxTwitter directly.

Successful responses are cached for about one hour by default (CACHE_TTL_SECONDS=3600) and send cache headers unless bypassed. nocache=true bypasses the application cache, but it cannot bypass upstream caches. Public X data can be missing, delayed, rate-limited, deleted, protected, or shaped differently by upstream providers, so context, counts, media variants, and pagination cursors are best effort. The API does not authenticate to private accounts and does not provide public lists.

Trust and privacy boundaries

  • The hosted service receives the public X URL, handle, or search query you request and sends it to FxTwitter or X's public syndication service. Successful results are cached for about one hour and can be served to other callers requesting the same public resource.
  • Optional Context.dev and Firecrawl fallbacks are disabled unless a self-hosted operator configures their API keys. When enabled, the public X URL is sent to that provider.
  • The hosted browse-x skill sends its arguments to x.pcstyle.dev. Do not put secrets or private-account information in URLs or search terms.
  • Optional, disabled-by-default PostHog dataset capture archives allowlisted structured public results, not just metrics. See the archive/privacy policy for actor pseudonyms, opt-out controls, retention duties, and delivery limits.
  • Media links point to upstream X/FxTwitter CDNs. Fetching those links is outside x.md's cache and privacy boundary.

x.md is read-only and does not accept X credentials, post content, or account mutations. It is the canonical implementation, has no successor, and is not affiliated with X Corp.

Self-host

git clone --depth 1 https://github.com/pc-style/x-md.git
cd x-md
bun install --frozen-lockfile
cp .env.local.example .env.local
bun run dev

That clones main, which is the moving target. For a reviewable source snapshot, clone with --branch and the newest tag from the releases page — the hosted API runs the newest tag — and keep the lockfile. No container image or deployment artifact is published; you deploy the source.

Optional environment variables:

VariableDescription
CONTEXT_DEV_API_KEYContext.dev converter fallback
X_SEARCH_SESSIONS_JSONConfiguration for the live search provider; the expected shape is defined in lib/xsearch.ts. Locally, the gitignored accounts.local.json is read instead. Without it, Photos, Videos, and Users return 503
KV_REST_API_URL / KV_REST_API_TOKENOptional Upstash/Vercel KV REST endpoint (or UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN) for shared rate-limit counters, the per-account post archive, and durable app state. Falls back to per-instance memory
FXTWITTER_BASE_URLOptional upstream override: one FxEmbed base URL or a comma-separated pool. Requests rotate across the pool and a throttled base sits out its Retry-After. Default https://api.fxtwitter.com
FIRECRAWL_API_KEYFirecrawl converter fallback and degraded /search fallback (web-indexed x.com snippets, X-Source: firecrawl, X-Search-Degraded: true) when live X search is down
CACHE_TTL_SECONDSCache TTL; default 3600
CACHE_DISABLEDSet to 1 to disable caching
CACHE_PERSISTSet to 0 for memory-only caching

Deploy with Vercel after bun run build; vercel.json configures dist, the API handlers, and all public route rewrites.

Project layout

api/convert.ts     Post conversion handler
api/browse.ts      Profile, search, followers, and following handler
api/oembed.ts      Discord oEmbed JSON for chat previews
lib/               Providers, rendering, pagination, cache, and embeds
src/               Vite landing page and rendered documentation

License

MIT

Documentation

Docs are MDX pages in docs/, built with Blume and mounted at /docs. Run bun run docs:dev for the documentation server. bun run build builds docs first, then the landing page into the same dist directory. Configure navigation and site metadata in blume.config.ts; theme.css maps the shared src/tokens.css palette into Blume.

Contributing

See CONTRIBUTING.md for setup, checks, and PR expectations, and SECURITY.md for private vulnerability reports.

Files in the repo

Repository payload40 top-level entries
  • .agents
  • .claude
  • .github
  • api
  • bench
  • docs
  • docs-assets
  • internal
  • lib
  • patches
  • public
  • scripts
  • skills
  • src
  • .bun-version
  • .env.local.example
  • .gitignore
  • about.html
  • admin.html
  • AGENTS.md
  • blume.config.ts
  • bun.lock
  • CHANGELOG.md
  • CLAUDE.md
  • contact.html
  • CONTRIBUTING.md
  • DECISIONS.md
  • index.html
  • LICENSE
  • middleware.ts
  • package.json
  • privacy.html
  • README.md
  • SECURITY.md
  • skills-lock.json
  • terms.html
  • theme.css
  • tsconfig.json
  • vercel.json
  • vite.config.ts

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More connectors

Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface

86k

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

43k

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

14k
okf-memory/
okf-agent-memory

Git-native persistent memory for AI coding agents. Implements Google OKF v0.2 with sub-300µs in-memory BM25 search, embedded MCP server, and progressive disclosure. Slashes token bloat by 80% with zero external databases or dependencies. Built in pure Go.

547
tirth8205/
code-review-graph

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.

31k
2akouwu/
reverify

Stop your AI from making things up — it proposes, deterministic tools decide, every claim checked against ground truth with evidence. Grounded facts and context survive resets. Reverse engineering is the proving ground. MCP server + CLI.

1.1k