Issue Nº 22 — Sep 6, 2026

Bluesky's public API fails four different ways. None of them is a 404

Issue twenty-two covers Bluesky, the AT Protocol social network's public, keyless read API. This session queried it live and deliberately tried to break it. An unimplemented method returns a real 501. A well-formed but unregistered handle returns 400. A request missing a required parameter returns a different 400. And a request for a post that does not exist returns a 400 whose own error field is named NotFound. Four requests, four failure shapes, zero 404s.

What it covers

Bluesky runs on the AT Protocol. Posts, follows, and identity live on portable Personal Data Servers, not one company's database. Anyone can read public profiles and feeds from the public AppView at public.api.bsky.app with no key and no sign-up. Posting, following, or reading a signed-in user's own data needs an authenticated session against a Personal Data Server, through an app password or OAuth, not one global API key.

GET /xrpc/app.bsky.actor.getProfile?actor=bsky.app, queried live this session, returned 200 with the account's DID, handle, display name, and profile fields, plus an access-control-allow-origin: * header. GET /xrpc/app.bsky.feed.getAuthorFeed?actor=bsky.app&limit=1 also returned 200, with one real post record.

The catalogue's stored rateLimit field documents 3,000 requests per 5 minutes per IP. Neither live response above carried any x-ratelimit-* header. The only caching signal present was a BunnyCDN edge layer's own cache-control and cdn-cache headers. The limit may still apply. Nothing in the response lets a caller see it coming.

Four ways to fail, none of them a 404

An unknown XRPC method, queried live as GET /xrpc/app.bsky.actor.getBogusMethod, returned a real HTTP 501 with the body {"error":"MethodNotImplemented","message":"Method Not Implemented"}.

A handle that reads as valid but is not registered, passed to getProfile, returned HTTP 400 with {"error":"InvalidRequest","message":"Profile not found"}. The status code says the request was malformed. The message says the opposite: the request was fine, and the account just is not there.

Dropping the required actor parameter from that same call returned a different HTTP 400: {"error":"InvalidRequest","message":"Invalid app.bsky.actor.getProfile params: Missing required key \"actor\""}. This one really is malformed. It gets the same status code as the account-not-found case above, with no way to tell the two apart from the status line alone.

Asking getPostThread for a post URI that does not exist returned HTTP 400 again, but with a third error key: {"error":"NotFound","message":"Post not found: at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/doesnotexist123"}. The API's own JSON names this error NotFound. The transport status sitting under it is 400, not 404.

A client that only checks the status code cannot tell these four cases apart. Three of them share the same 400. A client that reads the error key can, but has to get past the fact that an error called NotFound never actually returns a 404.

The spec and the SDKs hold up

The published OpenAPI document at endpoints.bsky.app/openapi.bluesky-app.json lists 152 endpoint paths, including this catalogue's three hero endpoints for the entry: getProfile, getAuthorFeed, and getPostThread, the last of which returns real thread data for a real post URI and the 400 above for a fake one.

Bluesky PBC links two official SDKs from its own docs, and both resolved live this session: a TypeScript client at bluesky-social/atproto's packages/api, and a Go client at bluesky-social/indigo. The status page at status.bsky.app and the docs at docs.bsky.app and endpoints.bsky.app all resolved live too.

Bluesky, by the numbers

Rendered live from the atlas entry
AuthenticationNone required
HTTPSSupported
CORSEnabled
PricingFree
FormatsJSON
Rate limit

3,000 requests per 5 minutes per IP overall; content-creation additionally capped at 5,000 points/hour and 35,000 points/day per account

Key endpoints
  • GET/xrpc/app.bsky.actor.getProfile
  • GET/xrpc/app.bsky.feed.getAuthorFeed
  • GET/xrpc/app.bsky.feed.getPostThread
at-protocoldecentralizedsocialfeedxrpc

Sources

Facts checked Sep 2026