Bluesky API

Search Bluesky posts and read whole threads with one API call

The Bluesky API returns public social data from the AT Protocol network as clean JSON.

no credit card1,000 free credits · instant API key · pay by card or crypto
Missing a Bluesky endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
B
/bluesky/v1

23 active endpoints. Every call is 1 credit.

  • POST/bluesky/v1/profile
  • POST/bluesky/v1/author_feed
  • POST/bluesky/v1/search_actors
  • POST/bluesky/v1/thread
  • POST/bluesky/v1/post_detail
  • POST/bluesky/v1/likes
  • POST/bluesky/v1/reposts
  • +16 more

What Bluesky endpoints does ReefAPI ship?

23 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

23 endpoints

profile

1 cr

actor profile.

required
optional
actor, actors

author_feed

1 cr

an actor's posts (full record+embed+counts), cursor-paginated.

required
actor
optional
limit, filter, cursor

search_actors

1 cr

search users by name/handle/bio (cursor).

required
q
optional
limit, cursor, typeahead

thread

1 cr

a post + its reply tree (recursive) + parent.

required
uri
optional
depth, parent_height

post_detail

1 cr

hydrate one or more posts by at:// URI (<=25).

required
uris
optional

likes

1 cr

actors who liked a post (cursor).

required
uri
optional
cid, limit, cursor

reposts

1 cr

actors who reposted a post (cursor).

required
uri
optional
cid, limit, cursor

followers

1 cr

actors following the subject (cursor).

required
actor
optional
limit, cursor

follows

1 cr

actors the subject follows (cursor).

required
actor
optional
limit, cursor

quotes

1 cr

Accounts/posts that QUOTE a given post (quote-posts), cursor-paginated.

required
uri
optional
cid, limit, cursor

suggested_follows

1 cr

Accounts suggested from a given account.

required
actor
optional
limit

lists

1 cr

The curated/moderation lists an account CREATED (user-curated lists + mute/block mod-lists),…

required
actor
optional
limit, cursor

list

1 cr

A single list's metadata plus its MEMBERS (the accounts on the list), members cursor-paginated.

required
list
optional
limit, cursor

list_feed

1 cr

The feed of posts authored by the members of a list (a list used as a custom timeline), curso…

required
list
optional
limit, cursor

actor_feeds

1 cr

The custom feed generators an account CREATED (its published feeds), cursor-paginated.

required
actor
optional
limit, cursor

feed_generator

1 cr

One custom feed generator's metadata plus its live online/valid status (is the feed service up).

required
feed
optional

popular_feeds

1 cr

Discover popular custom feed generators.

required
optional
q, limit, cursor

get_feed

1 cr

The posts served BY a custom feed generator (e.g.

required
feed
optional
limit, cursor

starter_packs

1 cr

The starter packs an account CREATED (shareable onboarding bundles of accounts + feeds), curs…

required
actor
optional
limit, cursor

starter_pack

1 cr

One starter pack's full detail.

required
starter_pack
optional

search_posts

1 cr

Search Bluesky posts by keyword or hashtag.

required
q
optional
sort, limit, cursor, since, until, author, mentions, lang, domain, url, tag

get_actor_likes

1 cr

posts an actor liked (authed.

required
optional
actor, limit, cursor

timeline

1 cr

the authenticated account's home timeline (authed).

required
optional
limit, cursor, algorithm

Every parameter, every allowed value →

Bluesky API

3 of 23 endpoints, ready to run

View docs ↗

Keyword and hashtag search across the network, filterable by author, language, linked domain and date, sorted by relevance or newest first.

1 credit1 required · 5 optional
POST/bluesky/v1/search_posts
ok3049 ms · 50 records · sample
{
  "ok": true,
  "meta": {
    "api": "bluesky",
    "endpoint": "search_posts",
    "mode": "live",
    "latency_ms": 3048.7,
    "record_count": 50,
    "cache_hit": false,
    "completeness_pct": 100
  },
  "data": {
    "query": "web scraping",
    "sort": "latest",
    "posts": [
      {
        "uri": "at://did:plc:tcgrhuwvyatn27qujdyczp3l/app.bsky.feed.post/3mu5p3yh7ol2p",
        "cid": "bafyreihlnd5worayp3rgz6mrca2xgl5r2cuqyerdlccurjbeb6o4bulati",
        "rkey": "3mu5p3yh7ol2p",
        "text": "Building an OSINT scraper for untrusted PDFs, the author asks if hardened Docker suffices or VM isolation is needed, citing CVE-2025-64512 (pickle RCE in pdfplumber's dependency) and kernel side-channel fears. Plans a 4-8 week burn-in with AIDE, auditd, and osquery.",
        "created_at": "2026-08-28T15:43:19.600682+00:00",
        "indexed_at": "2026-08-28T15:43:20.186Z",
        "langs": [
          "en"
        ],
        "like_count": 0,
        "repost_count": 0,
        "reply_count": 0,
        "quote_count": 0,
        "bookmark_count": 0,
        "labels": [],
        "embed": {
          "type": "external",
          "external": {
            "uri": "https://reddit.com/r/cybersecurity/comments/1vzs7nj/",
            "title": "Architecture Advice: Air-gapped VMs vs. hardened Docker containers for an automated PDF scraping pipeline (Deep Web / unstructured sources)",
            "description": "Hey everyone,\nI'm currently building an automated OSINT pipeline and I'm facing an architectural security dilemma.\nThe Project: My crawler (written in Python) is designed to search old, unstructure...",
            "thumb": null
          }
        },
        "tags": [],
        "mentions": [],
        "links": [],
        "is_reply": false,
        "reply_parent": null,
        "reply_root": null,
        "url": "https://bsky.app/profile/cybersecurity.page/post/3mu5p3yh7ol2p"
      },
      {
        "uri": "at://did:plc:m2yuqynl2cttvi4k5453yegh/app.bsky.feed.post/3mu5kjjpvwhl2",
        "cid": "bafyreicar23ijzshrarwy4tdabzeeytfyjj43unz6daaaarybp7sd3lody",
        "rkey": "3mu5kjjpvwhl2",
        "text": "🐧 **SeleniumBase – all-in-one Python framework**\n\nSeleniumBase is an all-in-one Python framework for browser automation, end-to-end web testing, crawling and scraping. The post SeleniumBase – all-in-one Python framework appeared first on LinuxLinks.\n\n📰 Source: LinuxLinks\n🔗 Link […]",
        "created_at": "2026-08-28T14:18:04.000Z",
        "indexed_at": "2026-08-28T14:18:13.864Z",
        "langs": [
          "en"
        ],
        "like_count": 1,
        "repost_count": 0,
        "reply_count": 0,
        "quote_count": 0,
        "bookmark_count": 0,
        "labels": [],
        "embed": {
          "type": "external",
          "external": {
            "uri": "https://igeek.gamer-geek-news.com/@feed/statuses/01M14BR8HBFHNG8T15VGMG0X1S",
            "title": "Original post on igeek.gamer-geek-news.com",
            "description": "",
            "thumb": null
          }
        },
        "tags": [
          "linux",
          "opensource",
          "python"
        ],
        "mentions": [],
        "links": [],
        "is_reply": false,
        "reply_parent": null,
        "reply_root": null,
        "url": "https://bsky.app/profile/feed.igeek.gamer-geek-news.com.ap.brid.gy/post/3mu5kjjpvwhl2"
      },
      {
        "uri": "at://did:plc:4jzmmjsrhntvjz3llgmn5fon/app.bsky.feed.post/3mu4terr6lk2g",
        "cid": "bafyreiad3iuvptnmblv72fhj6pl62cmrtfo2bcsmllv25rzfjyoyi3xewm",
        "rkey": "3mu4terr6lk2g",
        "text": "Lightpanda's agent now searches the web with no API key. The \nKeenable public endpoint is the new default.\n\nIt used to fall back to scraping a DuckDuckGo SERP, which their robots.txt disallows.\n\nIn the clip, every search credential is unset. Two tool calls, 19 seconds.\n\nlightpanda.io/docs/usage/a...",
        "created_at": "2026-08-28T07:23:50.206Z",
        "indexed_at": "2026-08-28T07:23:50.969Z",
        "langs": [
          "en"
        ],
        "like_count": 0,
        "repost_count": 0,
        "reply_count": 0,
        "quote_count": 0,
        "bookmark_count": 0,
        "labels": [],
        "embed": {
          "type": "video",
          "video": {
            "playlist": "https://video.bsky.app/watch/did%3Aplc%3A4jzmmjsrhntvjz3llgmn5fon/bafkreighpeccier3hd6pthsozxaw6n7to2u3n7wrcilb3qg6w64v3bimay/playlist.m3u8",
            "thumbnail": "https://video.bsky.app/watch/did%3Aplc%3A4jzmmjsrhntvjz3llgmn5fon/bafkreighpeccier3hd6pthsozxaw6n7to2u3n7wrcilb3qg6w64v3bimay/thumbnail.jpg",
            "alt": null,
            "aspect_ratio": {
              "height": 1080,
              "width": 1080
            },
            "cid": "bafkreighpeccier3hd6pthsozxaw6n7to2u3n7wrcilb3qg6w64v3bimay"
          }
        },
        "tags": [],
        "mentions": [],
        "links": [
          "https://lightpanda.io/docs/usage/agent#web-search"
        ],
        "is_reply": false,
        "reply_parent": null,
        "reply_root": null,
        "url": "https://bsky.app/profile/lightpanda.bsky.social/post/3mu4terr6lk2g"
      }
    ],
    "count": 50,
    "hits_total": 8725,
    "cursor": "eyJzIjpbMTc4NzQyNjU5NzAwMCwiZGlkOnBsYzp3bDR5cXJjYnRhYXlranBzbGlzc2E0aW4iLCIzbXRveXJyMjV0cTJtIl19"
  }
}
Real response, fetched from the live endpoint with the parameters on the left — trimmed to the first few rows, with seller names left out. Press Try it for the untrimmed response.

How the Bluesky API works

Bluesky is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 184 engines.

02
Call
POST /bluesky/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 credit per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

Find the conversation, then read all of it

Search hands you post URIs; the thread action turns any one of them into the entire discussion. Two calls, and the second one does not page.

01search_posts
POST/bluesky/v1/search_posts
{"q": "web scraping", "sort": "latest", "limit": 50}

Fifty posts in 1.6 seconds, with hits_total 8,723 in the response so you know how much more there is, and a cursor to walk it.

02thread
POST/bluesky/v1/thread
{"uri": "<the uri from any row>", "depth": 10}

740 records for one post in 1.8 seconds, with reply_total 739 confirming nothing was cut. No cursor, no second call.

A keyword and everything said under every post that matched it, without an app password of yours and without writing a line of AT Protocol.

request
curl -X POST https://api.reefapi.com/bluesky/v1/search_posts \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"q":"bluesky","sort":"top","limit":25}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "bluesky",
    "endpoint": "search_posts",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

AT Protocol identifiers: DID, handle, at:// URI, rkey and CID

Bluesky has two names for an account and a URI for everything else, and the difference matters because one of them is permanent and the other is not. Every value below was returned by a live read of the bsky.app account and its pinned post on 2026-08-27.

IdentifierMeasured shapeWhat it is for
DIDdid:plc:z72i7hdynmk6r22z27h6tvurThe account's permanent id. Store this, not the handle. The actor parameter accepts it, a handle, or a bsky.app/profile/... URL, and all three returned the identical profile
handlebsky.appA domain name the owner controls. It can be changed or reassigned, so it is a display value, not a key
post uriat://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2lThe primary key for thread, likes, reposts, quotes and post_detail
rkey3l6oveex3ii2lThe last segment of the URI, and also the last segment of the bsky.app share URL, so you can rebuild the URI from a link plus the author DID
cidbafyreicnt42y6vo6pfpvyro234ac4o6ijug6adwwrh7awflgrqlt4zibxqA content hash of that exact version of the post. Optional on likes, reposts and quotes, where it skips a resolve step
cursor2026-08-21T18:45:43.613ZPagination on author_feed comes back as an ISO timestamp, not an opaque token. Pass it straight back as cursor
unknown handleNOT_FOUND, retryable falseA handle that does not resolve is a hard error with detail.context app.bsky.actor.getProfile, not an empty result

profile has two modes. One actor returns the profile object at the top level; up to 25 actors returns data.profiles[] with a count and meta.batch true. Note also that a post's author block is thinner than the profile action's output: description and indexed_at come back null inside a post even for accounts that have both.

What Bluesky serves, how far it pages, and who has to authenticate (not you)

Measured on 2026-08-28 against one keyword, one large account and one 739-reply thread. One line goes against us.

You do not send Bluesky credentials, not even for search

Post search is the one endpoint the network itself puts behind an authenticated session, and the reference note beside it mentions an app password. You do not supply one: measured with nothing but a ReefAPI key, a fifty-row latest-first search answered in 1.6 seconds and reported hits_total 8,723. No account of yours is involved anywhere on this page, so there is no account of yours to rate-limit, and a failed call is not charged.

Search is filterable in a way most social search is not

One query can be constrained by date range, author, mentioned account, language, linked domain, exact URL and hashtag, and sorted by relevance or newest. A filtered hundred-row run — one date onward, English only, latest first — answered in 3.2 seconds and returned a cursor.

Every row is a whole record, and every row is dated twice

created_at and indexed_at both come back as full ISO-8601 timestamps, so you can tell when a post was written and when the network saw it. Alongside them: like, repost, reply, quote and bookmark counts, declared languages, tags, mentions, extracted links, the embed, and whether the post is a reply and to what.

A feed pages 200 at a time, ten months in one call

Asking an account's feed for 200 returned 200 rows with stop_reason limit_reached and a cursor stamped 2025-10-06 — ten months of that account's output in a single request. The filter picks whether replies, media posts and author threads come with it.

A whole thread arrives in one call and says so

740 records for one post in 1.8 seconds, with reply_total 739 in the response as its own audit. Depth runs to a hundred levels below the post and parent_height to a hundred above it, and there is no cursor to walk.

🔴 A follower list is a sample, not a roster

Against us. Asking for 2,000 followers of an account with 34.7 million of them returned 483 rows, in 42 seconds, with stop_reason complete and a cursor still attached. That is what the network's public view serves for that account rather than a truncation on our side — but it is nowhere near a roster, and a product that assumes otherwise will quietly under-count. Likes, reposts and quotes on a single post behaved normally: 196 quote-posts came back for one post in 2.9 seconds.

Custom feeds are first-class, not an afterthought

The popular feed generators come back fifty at a time with a cursor, a feed generator reports whether its service is currently up, and reading the posts a feed serves returned 100 rows in 2.5 seconds. Lists, list timelines and starter packs are separate actions on the same pattern.

Profiles batch twenty-five at a time

One call resolved five accounts in 1.2 seconds and returned four — the fifth handle does not exist on the network, and the response simply omitted it rather than padding the array. Handles, DIDs and profile URLs are all accepted as the input.

What comes back, and what does not

Public records from the network: posts, profiles, the follow graph as the public view serves it, custom feeds, lists and starter packs. Not direct messages, not moderation queues, not anything an account has not published.

What people build with Bluesky

The jobs this data is most often used for.

23

endpoints

1

credit per call

01

Social analytics call profile and author_feed to measure a handle's reach.

02

Brand monitoring uses search_actors and thread to track mentions.

03

Research uses likes and reposts to gauge engagement.

What Bluesky data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 184 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/bluesky/v1/search_posts \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"q":"bluesky","sort":"top","limit":25}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/bluesky/v1/search_posts",
    headers={"x-api-key": REEF_KEY},
    json={
  "q": "bluesky",
  "sort": "top",
  "limit": 25
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up Bluesky.

Get a free key →
Should I key my records on the Bluesky handle or the DID?

The DID. A handle is a domain the account owner controls and can change; the did:plc identifier never moves. On 2026-08-27, actor bsky.app and actor did:plc:z72i7hdynmk6r22z27h6tvur returned the same profile field for field, so you lose nothing by resolving to the DID once and storing that.

Why does author_feed return posts written by other accounts?

Because reposts are part of an author's feed. A live author_feed read for bsky.app returned three posts, one of which carried an author.handle and author.did belonging to a different account entirely. The filter parameter controls replies and media, not reposts, so if you only want the account's own writing, compare each post's author.did to the subject's DID and drop the ones that differ.

Which engagement counts come back on a post?

Five, all as integers on every post object: like_count, repost_count, reply_count, quote_count and bookmark_count. One measured post carried 5,144 likes, 1,224 reposts, 235 replies, 519 quotes and 288 bookmarks. bookmark_count is worth knowing about because the Bluesky web interface does not display it anywhere.

Why does thread return far fewer replies than reply_count claims?

reply_count is the post's lifetime counter and the tree is what the network will serve right now. A live thread call on the bsky.app pinned post returned reply_total 159 while the root post's own reply_count read 8,569. Deleted accounts, blocked authors and replies deeper than your depth setting all drop out of the tree but stay in the counter. Raise depth, which accepts 0 to 100, to recover the deeper levels; nothing will recover the deleted ones.

What does the verified field mean, and why is it sometimes null?

It is tri-state. jay.bsky.team returned verified true, the official bsky.app account returned false, and authors embedded inside a quoted post returned null. Null means the check was not evaluated in that context rather than that the account failed it, so treat only an explicit true as verification and never read null as false.

What is the associated block on a profile?

A count of the things that account publishes. bsky.app returned associated with feedgens 7, lists 17, starter_packs 14 and is_labeler false. Use it as a cheap check before spending a call: if feedgens is 0 there is no point calling actor_feeds, and is_labeler tells you whether the account issues moderation labels.

Can I look up several accounts in one call?

Yes. Pass actors with up to 25 handles or DIDs instead of a single actor, and the response shape changes to data.profiles[] plus a count, with meta.batch true. A measured two-actor batch returned both full profiles including followers_count, associated and pinned_post. pinned_post is an at:// URI when one is set and null when it is not.

What comes back for a handle that does not exist?

A clean failure, not an empty object. profile for a made-up handle returned ok false with error.code NOT_FOUND, retryable false, and detail.context app.bsky.actor.getProfile. Because retryable is false, do not put that call in a retry loop; the handle is simply not on the network.

What is the Bluesky API?

Bluesky API is a ReefAPI endpoint group for profiles, posts, followers and the social graph. It returns live JSON through POST requests under /bluesky/v1.

Is the Bluesky API free to try?

Yes. ReefAPI starts with 1,000 free credits, no card required. Bluesky calls use the same shared credit balance as every other ReefAPI engine.

Do I need a Bluesky login or account?

No login to Bluesky is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

How fresh is the Bluesky data?

The page example is captured from a live profile call, and production requests fetch live data through ReefAPI rather than a static sample.

How many credits does the Bluesky API use?

Bluesky actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.

Can I call Bluesky from an AI assistant or MCP client?

Yes. Connect ReefAPI once through MCP and your assistant can call bluesky actions with the same key, credit pool and JSON envelope used by normal REST requests.

10 Social Media APIs on the same key

One key, one credit pool, one response envelope. If you are pulling Bluesky, you are one call away from the rest of the category — no second contract, no second integration.

Already paying for something else?Bluesky vs Apify

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 183 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-28.