X-Lookup
Read-only, no-auth browser for public X/Twitter content, purpose-built for AI agents. Statuses and threads, profiles, search, followers/following
Documentation
x-lookup
Read-only, no-auth browser for public X/Twitter content, purpose-built for AI agents. Statuses and threads, profiles, search, followers/following — served as compact Markdown by default, structured JSON on request, Open Graph HTML for chat-app preview bots, plus an oEmbed endpoint.
Hosted at https://x-lookup.mynameistito.com as a single Cloudflare Worker. No database, no login, no API keys — the only upstreams are the free FxTwitter API and Twitter's syndication endpoint.
Not affiliated with X Corp.
Quick start
Replace x.com with x-lookup.mynameistito.com on any public status URL:
https://x.com/handle/status/1234567890
https://x-lookup.mynameistito.com/handle/status/1234567890
curl -sS -H "Accept: text/markdown" "https://x-lookup.mynameistito.com/handle/status/1234567890"
curl -sS -G "https://x-lookup.mynameistito.com/api/convert" --data-urlencode "url=https://x.com/handle/status/1234567890"
Browsers that request HTML get a readable page containing the Markdown. Discord, Telegram, Slack, and other preview bots receive Open Graph embed HTML.
Routes
| Route | Purpose | Query parameters |
|---|---|---|
GET /api/convert?url=<x-status-url> (or handle= + id=) | Convert a status/thread | see Post conversion parameters |
GET /:handle/status/:id | Same, via URL rewrite | same as /api/convert |
GET /api/browse?resource=profile|search|followers|following&… | Browse endpoint | see Browse parameters |
GET /search?q=… | Search posts | q (required), feed, cursor, page, limit, full, format, nocache |
GET /:handle | Profile + latest original posts | cursor, page, limit, full, format, nocache |
GET /:handle/followers | Follower users | cursor, page, limit, full, format, nocache |
GET /:handle/following | Following users | cursor, page, limit, full, format, nocache |
GET /oembed?url=… | oEmbed JSON | url; optional text, author, status, provider overrides |
GET /og.png | 1200×630 Open Graph / Twitter share image | — |
GET / (also /docs) | Full usage documentation (Markdown) | — |
All API responses send CORS *, support OPTIONS (204) and HEAD; other methods get 405. Errors are always { "error": string, "code": string } with a truthful status: 400 bad input, 404 genuinely missing content, 502 upstream refusal or failure.
MCP server
The same public, read-only capabilities are available through the stateless MCP endpoint:
https://x-lookup.mynameistito.com/mcp
Example remote MCP configuration:
{
"mcpServers": {
"x-lookup": {
"url": "https://x-lookup.mynameistito.com/mcp"
}
}
}
The server exposes these tools:
| Tool | Required input | Optional input |
|---|---|---|
browse_x | resource | handle, q, feed, cursor, page, limit, full, format, nocache |
convert_status | url, or handle + id | format, thread, context, replies, userinfo, full, nocache |
search_posts | q | feed, cursor, page, limit, full, format, nocache |
get_profile | handle | cursor, page, limit, full, format, nocache |
list_followers | handle | cursor, page, limit, full, format, nocache |
list_following | handle | cursor, page, limit, full, format, nocache |
get_oembed | — | url, text, author, status, provider |
get_health | — | — |
Browse tools return structured JSON with an included Markdown rendering. Conversion returns structured JSON with the rendered Markdown, posts, warnings, provider, and cache status. The MCP endpoint is stateless; it does not require an MCP session ID or authentication.
Post conversion parameters
Both GET /:handle/status/:id and GET /api/convert?url=… support:
| Parameter | Default | Supported values |
|---|---|---|
format | markdown | markdown, obsidian, json |
full | false | true, 1, or yes enables expanded Markdown; Obsidian is always expanded |
thread | full | off, full, conversation, or a limit from 2 to 100 |
context | full | full includes parents, author thread, and selected replies; thread excludes unrelated replies |
replies | top | top, recent, off |
userinfo | off | off, author, all |
nocache | false | true, 1, or yes bypasses the cache |
Content negotiation: format=json or Accept: application/json → JSON; preview-bot User-Agents with no explicit format → OG HTML; Accept: text/html → HTML page; otherwise Markdown.
Browse parameters
/api/browse, /search, /:handle, and follower lists accept:
| Parameter | Default | Supported values |
|---|---|---|
q | — | Search query; required on /search and resource=search. Supports X operators like from:, since: |
feed | latest | latest, top, media — search only |
cursor | — | Opaque continuation token from Continue → / nextCursor |
page | 1 | 1–10; walks pages when no cursor is given |
limit | 20 | 1–50 results per response |
full | false | true, 1, or yes adds dates/metrics to posts and follower counts/bios to users |
format | markdown | markdown, json |
nocache | false | true, 1, or yes bypasses the cache |
Prefer the opaque cursor from the Markdown Continue → link or JSON nextCursor over page walking.
Meaningful response headers
X-Source (fetch provider), X-Cache (HIT|MISS|BYPASS), X-Browse-Resource, X-Result-Count, X-Converter, X-Post-Count, X-Warnings, X-Embed.
Search availability note
FxTwitter refuses some datacenter egress IPs. When that happens, search returns 502 with code search_unavailable — never a fake "post not found". Status lookups fall back from FxTwitter to Twitter's syndication endpoint.
Development
The repository uses Bun 1.4.0. On Windows, install Bun from the official PowerShell instructions, then run:
bun install --frozen-lockfile
bun run dev # local workerd, isolated dev_<user> stage
bun run test # vitest
bun run typecheck # tsc --noEmit
bun run plan # preview the production diff
bun run deploy # deploy the prod stage (attaches x-lookup.mynameistito.com)
bun run destroy # tear down the prod stage (interactive confirm)
Alchemy uses the default profile unless ALCHEMY_PROFILE or an explicit --profile argument selects another profile. To use a named local profile for the package scripts in PowerShell, set it for the current shell before running a command:
$env:ALCHEMY_PROFILE = "your-profile"
bun run plan
bun run dev
In Bash, use export ALCHEMY_PROFILE=your-profile. Profiles are stored locally in ~/.alchemy/profiles.json; configure one with bunx alchemy login --profile your-profile. GitHub Actions do not use local profiles: deploy jobs authenticate with the CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID repository secrets. Infrastructure lives entirely in alchemy.run.ts and src/worker.ts — there is no wrangler.jsonc. In prod, the Worker keeps its physical name x-lookup and custom domain x-lookup.mynameistito.com; every other stage (local dev, PR previews) derives an isolated identity. The only var is CACHE_TTL_SECONDS (default 3600). There are no secrets. Caching is two-tier: in-isolate memory L1 plus Cloudflare Cache API L2.
CI runs credential-free lint/typecheck/tests plus a stateless stack validation (.github/workflows/ci.yml). .github/workflows/deploy.yml calls the pinned alchemy-deploy reusable workflow, which deploys only after CI succeeds for the exact commit: prod for main and an isolated pr-<number> preview for same-repository pull requests. Preview stacks are torn down when the PR closes, and fork pull requests never receive Cloudflare credentials or preview deployments. Preview resolution runs trusted default-branch workflow code and waits for the exact PR-head CI result before credentials are exposed.
The shared workflow owns preview and production deployment reporting, URL resolution, and preview cleanup. Cleanup uses explicitly mapped repository-scoped Cloudflare secrets, checks out only the trusted default branch, destroys pr-<number> first, and removes GitHub deployment records only after successful teardown; failed cleanup retains those records and the diagnostic run. The workflow_run deployment checks out the exact CI-passed SHA.
The bundled agent skill in skills/x-lookup/ wraps this API for CLI use; override its target with X_API_BASE when testing another deployment.