How to use it: 3 steps
- 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.
- Paste your input and press StartNo code needed: you fill in a form (the input). It already has a working example. For example:
apifyin Search queries (mode: Search posts). Each start is one run. - 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 for | Price | When |
|---|---|---|
| Start fee | $0.001 | Charged 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.0005 | One 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
- Open the Input tab.
- Keep What to scrape on Search posts and add a word to Search queries, e.g.
apify. - Keep Max posts per query at the prefilled
30(the runtime default is100). - Click Start.
- Open the Posts view in the Output tab and check
text,author.handleandlikeCount.
Use cases
| Task | Input | You get | Key fields | Next step |
|---|---|---|---|---|
| Brand or keyword listening | mode: searchPosts, queries | post | text, createdAt, likeCount, author.handle, links | Schedule the Actor, export to Sheets or a database |
| Account research | mode: authorFeed, handles, since | post | text, isRepost, replyCount, externalLink | Compare accounts, build datasets |
| Influencer or lead list | mode: profiles, handles | profile | displayName, description, followersCount, postsCount | CRM import |
| Discussion analysis | mode: thread, postUrls | post | threadRole, threadDepth, text, replyTo | Sentiment or topic analysis |
| Curated topic monitoring | mode: feed, feedUrls | post | text, hashtags, images[].alt | AI 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_RESULTSwith 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:
errorrows (Bluesky failed), skipped inputs, duplicate posts and duplicate input values (the same account, post or feed written another way, e.g.@Name.bsky.socialand 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
partialwith 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.
{
"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.
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 -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"]}'# 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)
| Field | Type | Meaning |
|---|---|---|
recordType | string | Row type: post, profile or status. Error rows are free; a not-found or invalid value is charged as one empty result. |
status | string | complete for data rows; not_found, invalid_input or error for status rows. |
mode | string | Mode that produced the row. |
input | string | Your input value (query, handle, post URL or feed URL) that produced the row. |
errorCode | string | Status rows: machine code such as NOT_FOUND, INVALID_INPUT, RATE_LIMITED, SOURCE_FORBIDDEN, SOURCE_UNAVAILABLE. |
message | string | Status rows: plain-English reason. |
uri | string | Post AT URI, e.g. at://did:plc:…/app.bsky.feed.post/3l… |
cid | string | Post content ID (CID). |
url | string | bsky.app link to the post or profile. |
author | object | Post author: did, handle, displayName, avatar, url. |
text | string | Post text. |
createdAt | string | When the post or account was created (ISO 8601, as stated by the author record). |
indexedAt | string | When Bluesky indexed the post or profile (ISO 8601). |
langs | array | Language codes declared by the post, e.g. ["en"]. |
replyCount | integer | Replies at fetch time. |
repostCount | integer | Reposts at fetch time. |
likeCount | integer | Likes at fetch time. |
quoteCount | integer | Quote posts at fetch time. |
bookmarkCount | integer | Bookmarks at fetch time, when Bluesky reports it. |
isReply | boolean | true when the post is a reply. |
replyTo | object | Parent post of a reply: uri and url. |
rootPost | object | Thread root of a reply: uri and url. |
isRepost | boolean | true when the row comes from a repost in a feed (see repostedBy, repostedAt). |
repostedBy | object | Who reposted it into the feed: did and handle. |
repostedAt | string | When it was reposted. |
isPinned | boolean | true when the post is pinned on the author profile. |
isQuote | boolean | true when the post quotes another post. |
quotedPost | object | Quoted post: uri, url, authorHandle, text. |
embedType | string | Attached media: images, video, external, record (quote), recordWithMedia or null. |
images | array | Images: url, thumbnailUrl, alt text, width, height. |
video | object | Video: playlistUrl, thumbnailUrl, alt text. |
externalLink | object | Link card: url, title, description. |
links | array | All links in the post text and link card. |
hashtags | array | Hashtags without #. |
mentions | array | Mentioned accounts: did and handle. |
labels | array | Moderation and self labels on the post or profile. |
threadRole | string | Thread mode: parent, anchor or reply. |
threadDepth | integer | Thread mode: 0 for the requested post, negative for parents, positive for replies. |
did | string | Profile DID (stable account ID). |
handle | string | Profile handle, e.g. bsky.app. |
Input settings (13)
The main fields of the input form. With the API you send them as JSON.
| Setting | Name in the API | Type | Example value |
|---|---|---|---|
| What to scrape required | mode | string | "searchPosts" |
| Search queries (mode: Search posts) | queries | array | ["apify"] |
| Handles (modes: Posts from accounts, Profiles) | handles | array | |
| Post links (mode: Post thread) | postUrls | array | |
| Feed or list links (mode: Custom feed or list) | feedUrls | array | |
| Max posts per query, account, thread or feed | maxItems | integer | 30 |
| From date | since | string | |
| To date | until | string | |
| Search order | searchSort | string | "latest" |
| Post language (search) | language | string | |
| Include replies | includeReplies | boolean | true |
| Include reposts (account posts, feeds) | includeReposts | boolean | false |
| Reply depth (thread) | threadDepth | integer | 10 |
Use cases with this tool
Each guide shows all the steps and the other tools.