Social — entry 004 of 39
Bluesky
Bluesky is the decentralized social network built on the AT Protocol, where posts, follows, and identity live on portable Personal Data Servers instead of one company's database. Its public AppView at public.api.bsky.app exposes keyless, read-only XRPC methods such as app.bsky.actor.getProfile and app.bsky.feed.getAuthorFeed, so any client can pull public profiles and feeds without registering an app. Posting, following, or reading a signed-in user's private data instead requires an authenticated session against a Personal Data Server via an app password or OAuth, not a single global API key.
Bluesky's public AppView, public.api.bsky.app, exposes a *partial* keyless slice of its XRPC surface: profile, author-feed, thread, and follower lookups answer with no credentials at all, while others (timeline, search) require auth or are blocked outright. A live GET this run against `app.bsky.actor.getProfile?actor=bsky.app` returned genuine profile JSON for Bluesky's own account with no key of any kind.
GreatAPIs Score
Auth quickstart
- No API key, signup, or credit card required for the keyless subset -- a live GET this run confirmed `getProfile`, `getAuthorFeed`, `getPostThread`, `getFollowers`, and `resolveHandle` all return 200 with zero credentials. Posting, following, or reading a signed-in user's private data instead needs an authenticated session against a Personal Data Server (app password or OAuth), which this keyless quickstart does not cover.
Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.
Fetch a public profile
GEThttps://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=bsky.app
{"did":"did:plc:z72i7hdynmk6r22z27h6tvur","handle":"bsky.app","displayName":"Bluesky","createdAt":"2023-04-12T04:53:57.057Z","labels":[],"...":"followersCount, postsCount, avatar, banner, description, associated, pinnedPost omitted -- these change constantly"}`did` is the account's permanent, portable identifier -- `handle` (`bsky.app`) can be changed by the user later, but `did` cannot, so chain the `did` (not the handle) into `getAuthorFeed`/`getPostThread` calls where an app needs a stable reference.
Try it
Developer reference
https://public.api.bsky.app/xrpc- GET/app.bsky.actor.getProfile?actor={handle}
- GET/app.bsky.feed.getAuthorFeed?actor={handle}
- GET/app.bsky.feed.getPostThread?uri={at-uri}
Gotchas & limits
- `app.bsky.feed.searchPosts` -- deliberately left out of this API's `heroEndpoints` for exactly this reason -- returns **HTTP 403 with an HTML error page**, not XRPC JSON: a live GET this run against `?q=test` returned `content-type: text/html` and a body opening `<html><head><title>403 Forbidden</title>...` (a CDN edge block page). A client that assumes every XRPC failure is parseable JSON breaks here.
- `app.bsky.feed.getTimeline` requires an authenticated session and is not keyless -- a live GET this run returned `HTTP 401` with genuine XRPC JSON `{"error":"AuthMissing","message":"Authentication Required"}`. Contrast with `searchPosts`'s HTML 403 above: two different failure shapes for two different kinds of "you can't do this here".
- Errors are not RESTful: an unknown handle is **400**, not 404 -- a live GET this run against `getProfile?actor=` a nonexistent handle returned `HTTP 400` `{"error":"InvalidRequest","message":"Profile not found"}`. A missing required param is also 400 and names the key: a live GET with no `actor` at all returned `{"error":"InvalidRequest","message":"Invalid app.bsky.actor.getProfile params: Missing required key \"actor\""}`.
- `limit` over the max is a 400 that names the ceiling -- a live GET this run against `getAuthorFeed?actor=bsky.app&limit=101` returned `HTTP 400` `{"error":"InvalidRequest","message":"Invalid app.bsky.feed.getAuthorFeed params: integer too big (maximum 100, got 101)"}`.
- `getAuthorFeed` items are not all authored by the requested actor -- a live GET this run against `getAuthorFeed?actor=bsky.app` returned a first feed item whose `post.author.handle` was `paretooptimizer.bsky.social`, not `bsky.app`, carrying a sibling `reason.$type: "app.bsky.feed.defs#reasonRepost"`. Reading `feed[].post.author` as "the requested actor" is wrong whenever `reason` is present -- it means a repost.
- An unimplemented XRPC method returns **HTTP 501** `{"error":"MethodNotImplemented","message":"Method Not Implemented"}` -- a live GET this run against a made-up method name confirmed it, distinct from the 400s above (bad params) and the 403/401s above (blocked/unauthenticated).