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

RoutePurposeQuery parameters
GET /api/convert?url=<x-status-url> (or handle= + id=)Convert a status/threadsee Post conversion parameters
GET /:handle/status/:idSame, via URL rewritesame as /api/convert
GET /api/browse?resource=profile|search|followers|following&…Browse endpointsee Browse parameters
GET /search?q=…Search postsq (required), feed, cursor, page, limit, full, format, nocache
GET /:handleProfile + latest original postscursor, page, limit, full, format, nocache
GET /:handle/followersFollower userscursor, page, limit, full, format, nocache
GET /:handle/followingFollowing userscursor, page, limit, full, format, nocache
GET /oembed?url=…oEmbed JSONurl; optional text, author, status, provider overrides
GET /og.png1200×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:

ToolRequired inputOptional input
browse_xresourcehandle, q, feed, cursor, page, limit, full, format, nocache
convert_statusurl, or handle + idformat, thread, context, replies, userinfo, full, nocache
search_postsqfeed, cursor, page, limit, full, format, nocache
get_profilehandlecursor, page, limit, full, format, nocache
list_followershandlecursor, page, limit, full, format, nocache
list_followinghandlecursor, page, limit, full, format, nocache
get_oembedurl, 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:

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 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:

ParameterDefaultSupported values
qSearch query; required on /search and resource=search. Supports X operators like from:, since:
feedlatestlatest, top, media — search only
cursorOpaque continuation token from Continue → / nextCursor
page1110; walks pages when no cursor is given
limit20150 results per response
fullfalsetrue, 1, or yes adds dates/metrics to posts and follower counts/bios to users
formatmarkdownmarkdown, json
nocachefalsetrue, 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.