Social media · Live on Apify

Bluesky Scraper: Posts, Profiles, Feeds and Threads

Get public Bluesky posts, profiles, threads and feeds as clean rows. You need no account and no app password.

About $0.90 for 1,000 posts$0.0009 per post + $0.001 per run. Apify bills you, not us.Free to try: the Apify free plan gives you $5 of usage every month, no credit card needed.

Run it on Apify Opens apify.com, the website where our tools run. Sign up free with Google or email, no card needed.

Apify links on this page are affiliate links. We may earn a commission. You pay the same price. Affiliate disclosure

What you get, for example

recordTypepost
modesearchPosts
uriat://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/app.bsky.feed.post/3mexample2abc

How to use it: 3 steps

  1. Make a free Apify accountApify is the cloud platform where our tools run. The free plan gives you $5 of usage every month, no credit card needed. You pay Apify, not us.
  2. Paste your input and press StartNo code needed: you fill in a form (the input). It already has a working example. For example: apify in Search queries (mode: Search posts). Each start is one run.
  3. Download the resultsWhen the run ends, the results are in a table (the dataset). Open Output, press Export and pick Excel or CSV. JSON and the API are there too.

This tool is an Apify Actor: a small program that runs on Apify. You can also run it on a schedule, for example every Monday. New to it? Read how it works, step by step and the 8 words you need first.

What it does

Scrape public Bluesky posts, profiles, custom feeds, lists and reply threads with no login, and get clean JSON rows with text, engagement counts, links, mentions, hashtags, image alt text, quoted posts and author details.

Good for

  • Track what people post about a brand, product, hashtag or domain on Bluesky.
  • Collect the posts and profiles of a list of accounts for research or lead lists.
  • Pull a full reply thread for sentiment or discussion analysis.
  • Feed clean, typed Bluesky rows into an AI agent, a spreadsheet or a database.

Not a good fit

  • No private data: no DMs, no muted or blocked lists, nothing that needs login.
  • No followers or following lists in this version.
  • Search returns at most 100 posts per query, because Bluesky blocks further search pages without login. The Actor does not try to get around this.
  • Counts (likes, reposts, followers) are a snapshot at fetch time.

Who uses it

Social listening teams

Track what people post about a brand or hashtag.

Researchers

Collect a full reply thread for discussion analysis.

Lead generation teams

Get profiles with follower counts for a list of accounts.

Price

Pay per event: you pay for each result the tool saves, plus a small start fee for each run. The price includes Apify platform costs. Apify bills you, not us. Prices below are for the Apify free plan.

What you pay forPriceWhen
Start fee$0.001Charged once per run. This tool uses 1 GB of memory or less by default; more memory costs more.
Post$0.0009 (from $0.00055 on paid Apify plans)One Bluesky post row saved to the Dataset (search, account posts, thread or feed).
Profile$0.0027 (from $0.0016 on paid Apify plans)One Bluesky profile row saved to the Dataset.
Empty result$0.0005One input value that returned no data: a search or account with no matching posts, or a profile, post, feed or list that does not exist or cannot be read.

Examples

  • One post: about $0.0019.
  • 1,000 posts: about $0.90.
  • Every day 100 posts, for 30 days: about $2.73 a month.

Free to try: the Apify free plan gives you $5 of usage every month, no credit card needed. That is enough for about 5,500 posts.

Prices checked on 24 October 2026. See the current price on Apify.

Questions

Why do I get only 100 posts per search?

Bluesky shows more search pages only to logged-in users. The tool respects that limit.

Can I get a follower list?

Not in this version. You get the follower count in profile rows.

Are likes and reposts up to date?

They are a snapshot from the time of the run. Run again to refresh them.

Do I pay for posts that do not exist?

An input with no data is one small empty-result event. Error rows and duplicates are free.

Do I need a Bluesky account or app password?

No. The Actor reads only public data from the public Bluesky API and never asks for login.

Are the counts live?

They are a snapshot at fetchedAt. Run again to refresh them.

Is this data complete?

It is what the Bluesky API returns at fetch time. Deleted posts, blocked content and posts hidden by moderation are not included, and counts can be null when Bluesky does not report them.

Can I scrape followers or DMs?

Not in this version. DMs are private and will never be supported.

Is scraping Bluesky allowed?

The Actor uses only public endpoints at a modest request rate and does not bypass any limit. You are responsible for how you store and reuse the content, including personal data and authors' rights.

Details

Step by step on Apify
  1. Open the Input tab.
  2. Keep What to scrape on Search posts and add a word to Search queries, e.g. apify.
  3. Keep Max posts per query at the prefilled 30 (the runtime default is 100).
  4. Click Start.
  5. Open the Posts view in the Output tab and check text, author.handle and likeCount.
Use cases
TaskInputYou getKey fieldsNext step
Brand or keyword listeningmode: searchPosts, queriesposttext, createdAt, likeCount, author.handle, linksSchedule the Actor, export to Sheets or a database
Account researchmode: authorFeed, handles, sinceposttext, isRepost, replyCount, externalLinkCompare accounts, build datasets
Influencer or lead listmode: profiles, handlesprofiledisplayName, description, followersCount, postsCountCRM import
Discussion analysismode: thread, postUrlspostthreadRole, threadDepth, text, replyToSentiment or topic analysis
Curated topic monitoringmode: feed, feedUrlsposttext, hashtags, images[].altAI summarisation
Tips for better results
  • Split a big topic into narrow queries; search gives 100 posts per query.
  • Use date filters to get posts from one week only.
  • Turn off reposts if you want original posts only.
Limits and good to know
  • Post row / profile row: one per data row written
  • Empty result: one per input value that produced no data: a search query with no matches, an account with no matching posts, a profile, post or feed that does not exist, or a value that cannot be read (for example a typo in a link). If every value is unreadable, the run ends as NO_RESULTS with a message saying so, and each value is one empty result. It covers the requests made for that value
  • Actor start: charged by Apify once per run (per GB of memory; the default 256 MB counts as one)
  • Free: error rows (Bluesky failed), skipped inputs, duplicate posts and duplicate input values (the same account, post or feed written another way, e.g. @Name.bsky.social and its profile link)
  • Search: up to 100 posts per query (Bluesky's limit without login). Split the topic into narrower queries or date ranges to see more
  • Account posts: up to 10,000 feed items scanned per account. A scan also stops early when fewer than 1 in 100 items match the filters after the first 1,000 items, so a sparse filter does not read the whole history
  • Feeds: the same sparse stop applies
  • Speed at 256 MB: one account's 1,030 posts in about 5 s; 10,000 account posts took about 2–3 minutes in tests. Large jobs need a longer run timeout than the default 300 s; an input cut by the timeout is marked partial with its row count

For developers

Sample result

One result from a real test run, in JSON. Real values change from run to run. In Excel or CSV each field is a column.

one result · JSON
{
  "recordType": "post",
  "status": "complete",
  "mode": "searchPosts",
  "input": "opendata",
  "uri": "at://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/app.bsky.feed.post/3mexample2abc",
  "url": "https://bsky.app/profile/author.example/post/3mexample2abc",
  "author": {
    "did": "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa",
    "handle": "author.example",
    "displayName": "Sample Author",
    "url": "https://bsky.app/profile/author.example"
  },
  "text": "Our open dataset is live! Thanks @team.example #opendata https://data.example/downloads",
  "createdAt": "2026-10-12T09:15:00.000Z",
  "likeCount": 2431,
  "repostCount": 318,
  "replyCount": 154,
  "isReply": false,
  "isRepost": false,
  "embedType": "images",
  "images": [
    { "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/bafkexample@jpeg", "alt": "Chart of monthly downloads" }
  ],
  "links": ["https://data.example/downloads"],
  "hashtags": ["opendata"],
  "mentions": [{ "did": "did:plc:bbbbbbbbbbbbbbbbbbbbbbbb", "handle": "team.example" }],
  "quotedPost": null,
  "threadRole": null,
  "fetchedAt": "2026-10-13T01:52:14.278Z",
  "source": "Bluesky AppView API (AT Protocol)"
}

Use it from code

Actor ID: cliqtomedia/bluesky-scraper. You need your Apify API token from Apify Console → Settings → API & Integrations.

Python · apify-client
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("cliqtomedia/bluesky-scraper").call(run_input={
    "mode": "searchPosts",
    "queries": [
        "apify"
    ]
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
cURL
curl -X POST "https://api.apify.com/v2/acts/cliqtomedia~bluesky-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "searchPosts", "queries": ["apify"]}'
LangChain · langchain-apify
# pip install langchain-apify
# set APIFY_TOKEN in your environment
from langchain_apify import ApifyActorsTool

tool = ApifyActorsTool("cliqtomedia/bluesky-scraper")
result = tool.invoke({"run_input": {"mode": "searchPosts", "queries": ["apify"]}})
print(result)

The cURL call waits for the results, up to 300 seconds. For longer runs, start a run (/runs) and get the results with a webhook or by checking the run. Apify API docs. LlamaIndex, MCP for AI agents and the JavaScript client: AI agents & MCP.

Data fields (40)
FieldTypeMeaning
recordTypestringRow type: post, profile or status. Error rows are free; a not-found or invalid value is charged as one empty result.
statusstringcomplete for data rows; not_found, invalid_input or error for status rows.
modestringMode that produced the row.
inputstringYour input value (query, handle, post URL or feed URL) that produced the row.
errorCodestringStatus rows: machine code such as NOT_FOUND, INVALID_INPUT, RATE_LIMITED, SOURCE_FORBIDDEN, SOURCE_UNAVAILABLE.
messagestringStatus rows: plain-English reason.
uristringPost AT URI, e.g. at://did:plc:…/app.bsky.feed.post/3l…
cidstringPost content ID (CID).
urlstringbsky.app link to the post or profile.
authorobjectPost author: did, handle, displayName, avatar, url.
textstringPost text.
createdAtstringWhen the post or account was created (ISO 8601, as stated by the author record).
indexedAtstringWhen Bluesky indexed the post or profile (ISO 8601).
langsarrayLanguage codes declared by the post, e.g. ["en"].
replyCountintegerReplies at fetch time.
repostCountintegerReposts at fetch time.
likeCountintegerLikes at fetch time.
quoteCountintegerQuote posts at fetch time.
bookmarkCountintegerBookmarks at fetch time, when Bluesky reports it.
isReplybooleantrue when the post is a reply.
replyToobjectParent post of a reply: uri and url.
rootPostobjectThread root of a reply: uri and url.
isRepostbooleantrue when the row comes from a repost in a feed (see repostedBy, repostedAt).
repostedByobjectWho reposted it into the feed: did and handle.
repostedAtstringWhen it was reposted.
isPinnedbooleantrue when the post is pinned on the author profile.
isQuotebooleantrue when the post quotes another post.
quotedPostobjectQuoted post: uri, url, authorHandle, text.
embedTypestringAttached media: images, video, external, record (quote), recordWithMedia or null.
imagesarrayImages: url, thumbnailUrl, alt text, width, height.
videoobjectVideo: playlistUrl, thumbnailUrl, alt text.
externalLinkobjectLink card: url, title, description.
linksarrayAll links in the post text and link card.
hashtagsarrayHashtags without #.
mentionsarrayMentioned accounts: did and handle.
labelsarrayModeration and self labels on the post or profile.
threadRolestringThread mode: parent, anchor or reply.
threadDepthintegerThread mode: 0 for the requested post, negative for parents, positive for replies.
didstringProfile DID (stable account ID).
handlestringProfile handle, e.g. bsky.app.
Input settings (13)

The main fields of the input form. With the API you send them as JSON.

SettingName in the APITypeExample value
What to scrape requiredmodestring"searchPosts"
Search queries (mode: Search posts)queriesarray["apify"]
Handles (modes: Posts from accounts, Profiles)handlesarray
Post links (mode: Post thread)postUrlsarray
Feed or list links (mode: Custom feed or list)feedUrlsarray
Max posts per query, account, thread or feedmaxItemsinteger30
From datesincestring
To dateuntilstring
Search ordersearchSortstring"latest"
Post language (search)languagestring
Include repliesincludeRepliesbooleantrue
Include reposts (account posts, feeds)includeRepostsbooleanfalse
Reply depth (thread)threadDepthinteger10

Use cases with this tool

Each guide shows all the steps and the other tools.

Start on Apify

Make a free Apify account: you get $5 of free usage every month. Missing a tool? Suggest it. We read every idea, but we do not promise to build it.

Apify links on this page are affiliate links. We may earn a commission. You pay the same price. Affiliate disclosure