Markdown-first API for the AT Protocol ecosystem, built for LLM agents and tools that consume plain text.
TypeScript 100%
JavaScript <1%
<1%

README.md

atproto.md #

A read-only, markdown-first API for the AT Protocol ecosystem, built for LLM agents and tools that consume plain text.

Accepts at:// URIs directly in the URL path and returns structured markdown — fetched from the user's actual PDS, not a Bluesky-specific AppView. Works with any collection on any PDS. Also goes network-wide: discover every repo using a given lexicon, and explore the backlinks pointing at any record, identity, or URL.


URL structure #

URLs accept at:// URIs directly:

GET /at://did:plc:eob75vcjtmbaef2tn4evc4sl
GET /at://did:plc:eob75vcjtmbaef2tn4evc4sl/app.bsky.feed.post
GET /at://did:plc:eob75vcjtmbaef2tn4evc4sl/app.bsky.feed.post/{rkey}
GET /at://alice.bsky.social/com.whtwnd.blog.entry

Handles and DIDs are both accepted as the authority segment.


Endpoints #

GET / #

API reference in markdown.

GET /llms.txt #

Structured API summary following the llms.txt convention for LLM agent discovery.

GET /mcp (POST) #

Model Context Protocol server endpoint. Install in Claude Code:

claude mcp add --transport http atproto-md https://atproto.md/mcp

Exposes ten tools: resolve_identity, get_repo, list_records, get_record, get_lexicon, discover_repos_by_collection, get_backlinks, plc_audit, plc_data, plc_last.

GET /skill.md #

Full agent skill sheet with usage triggers, examples, and endpoint reference. Save it as a Claude Code slash command (invoke with /atproto):

curl -s https://atproto.md/skill.md > ~/.claude/commands/atproto.md

GET /resolve/{handle-or-did} #

Resolves the full identity chain for an actor: handle → DID → DID document → PDS endpoint. Useful for debugging identity issues or understanding where a user's data lives.

GET /plc/audit/{handle-or-did} #

Chronological history of a did:plc identity from plc.directory, each operation diffed against the previous one — PDS migrations (from/to host and date), handle changes, and signing/rotation key rotations. Useful for dating a migration or verifying provenance. did:web identities have no PLC log.

GET /plc/data/{handle-or-did} #

Current canonical PLC state — active PDS, all handles, atproto signing key, and rotation keys in priority order. Unlike /resolve (the DID document), this exposes the rotation keys that actually control the identity.

GET /plc/last/{handle-or-did} #

The most recent PLC operation and the state it established.

GET /at://{actor} #

Lists all collections present in the actor's repo.

GET /at://{actor}/{collection} #

Lists records in any collection. No prior knowledge of the lexicon required — unknown collection types are rendered as generic key-value markdown.

Param Default Max Notes
limit 25 100 Records per page
cursor — — Pagination cursor from previous response
reverse false — Set to true for oldest-first ordering

GET /at://{actor}/{collection}/{rkey} #

Fetches a single record by its rkey.

GET /lexicon/{nsid} #

Resolves a Lexicon schema by its NSID using AT Protocol DNS-based lexicon resolution: the _lexicon.{authority} TXT record points at a DID, whose repo holds the schema at com.atproto.lexicon.schema/{nsid}. Returns the schema's definitions and full JSON — e.g. /lexicon/app.bsky.feed.post.

GET /discover/{collection} #

Discovers every repo on the network with records in a collection — find all users of a lexicon, e.g. /discover/site.standard.document. Network-wide, via the relay's com.atproto.sync.listReposByCollection. Each result links straight into that repo's records for the collection.

Param Default Max Notes
limit 100 2000 Repos per page
cursor — — Pagination cursor from previous response

GET /backlinks/{at-uri-or-did-or-url} #

Finds records across the network that link to a target — likes, reposts, replies, follows, quotes, or any custom lexicon. Without source, returns a summary table of every link source with record and distinct-DID counts. Indexed by Constellation (microcosm.blue).

Param Default Max Notes
source — — A {collection:path} selector (e.g. app.bsky.feed.like:subject.uri) to list linking records
limit 50 100 Linking records per page
cursor — — Pagination cursor from previous response

GET /stats #

Anonymous usage dashboard — request counts by route and MCP tool, MCP sessions, most-queried collections (with which still need a formatter), status codes, upstream failures, and aggregate timing. HTML for browsers, markdown for agents. Only markdown/API responses are counted — never IPs, handles, DIDs, or record keys.


How it works #

Every request follows this resolution chain:

handle → DID → DID document → #atproto_pds service endpoint → com.atproto.repo.*

Data is fetched directly from the user's PDS via com.atproto.repo.listRecords and com.atproto.repo.getRecord. This means it works for self-hosters, third-party PDS providers, and any actor on the network — not just bsky.social users.

Supports did:plc (resolved via plc.directory) and did:web.

Network-wide routes #

/discover and /backlinks answer questions a single PDS can't, so they reach past the resolution chain to network-wide indexes:

  • /discover/{collection} queries a public relay's com.atproto.sync.listReposByCollection to enumerate every DID with records in a collection. Results link back into per-PDS /at:// views.
  • /backlinks/{target} queries Constellation (microcosm.blue), a firehose-wide backlink index, for the records that link to a given at-uri, DID, or URL. Both the summary (links/all) and the per-source record list (blue.microcosm.links.getBacklinks) are exposed.
  • /lexicon/{nsid} does a DNS-over-HTTPS TXT lookup on _lexicon.{authority} to find the lexicon's publishing DID, then fetches the schema from that repo. No central registry — resolution is fully decentralized via DNS.

No authentication is used for either — both indexes serve public data.


Record formatting #

Known collection types get structured rendering. Everything else falls back to a generic key-value markdown representation, so no collection is unreadable.

Collection Rendering
app.bsky.feed.post Text, embeds, reply context
app.bsky.actor.profile Display name, bio
app.bsky.graph.follow / block / list / listitem Subjects, timestamps
app.bsky.feed.like / repost / generator Subjects, timestamps
app.bsky.labeler.service Label policies
site.standard.publication Name, URL, description, icon, labels, locale
site.standard.document Title, dates, tags, labels, cover image, contributors, content
pub.leaflet.publication / document Name, URL, content from pages
app.offprint.publication / document.article References to standard records
blog.pckt.publication Reference to standard record
link.woosh.linkPage Description, labeled link sections
blue.linkat.entry Title, URL, description
events.smokesignal.calendar.event Name, dates, location
Any other collection Generic key-value markdown

Adding a new formatter #

  1. Create src/formatters/{nsid.namespace}.ts
  2. Import register from ./registry and call it with your collection NSID(s)
  3. Add one import line to src/formatters/index.ts
  4. Add a matching test in test/formatters/{nsid.namespace}.spec.ts

See any existing formatter file for the pattern — e.g. src/formatters/blue.linkat.ts.


Deployment #

Built as a Cloudflare Worker.

npm install
npm run dev      # local dev via wrangler
npm run test     # run tests
npm run deploy   # deploy to Cloudflare

Notes #

  • Public data only. No authentication is supported or planned. Private records on a PDS will not be accessible.
  • Rate limits follow the upstream PDS. If you expect significant traffic, add Cloudflare KV-backed rate limiting per IP.
  • Caching is set to Cache-Control: public, max-age=60. Adjust per endpoint if needed.
  • Usage stats are aggregated anonymously in a Durable Object (the STATS binding, SQLite-backed) and surfaced at /stats. To wipe them, set a STATS_RESET_TOKEN secret (wrangler secret put STATS_RESET_TOKEN) and POST /stats/reset with an Authorization: Bearer <token> header. Without the secret the reset endpoint is disabled (404).

  • pds.ls — AT Protocol repo explorer (same URL convention)
  • bsky.md — Bluesky-specific markdown API
  • plc.directory — DID PLC method registry