Bluesky Scraper — Posts, Profiles & Search
Pricing
from $0.70 / 1,000 results
Bluesky Scraper — Posts, Profiles & Search
Returns url, text, author.handle, createdAt, likeCount, repostCount, replyCount and hashtags for every post. Reads Bluesky over the AT Protocol — no login needed for public data. Posts, profiles, threads and full-text search, with deduplication across relays. Empty runs cost nothing.
Pricing
from $0.70 / 1,000 results
Rating
0.0
(0)
Developer
Radosław Szal
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Bluesky Scraper — Posts, Profiles, Hashtags & Search (AT Protocol)
🔗 Part of the Apify actors collection — actors that chain: scrape → clean → use.
Scrape Bluesky without a login: posts, profiles, threads, hashtags, follower graphs and full-text search, exported as clean JSON, CSV or Excel. Built directly on the public AT Protocol — the decentralized protocol Bluesky itself runs on — so there is no browser, no proxy and no fragile HTML parsing to break when the site changes.
Built for social listening, brand monitoring and research. $0.001 per record — $1.00 per 1 000, and an empty run costs nothing.
What you get back, and how fast
Every record is flat and typed — engagement counts, hashtags, mentions, links and media pulled
up to top-level fields, with a real clickable https://bsky.app/... URL. Post records carry
uri, url, cid, text, createdAt, indexedAt, langs, author{did,handle,displayName,avatar}, likeCount, repostCount, replyCount, quoteCount, hashtags, mentions, links, media[{url,alt,type}], quotedPost, isReply, isRepost, labelsdid, handle, url, displayName, description, avatar, banner, followersCount, followsCount, postsCount, createdAt, labelsA typical run finishes in about 6 seconds (25 records, measured), against Apify's 5-minute limit for automated checks. There is no browser and no proxy in the path — this reads the AT Protocol directly — which is why it is both fast and cheap to run.
Full samples are in Output below.
What can this Bluesky scraper do?
Seven jobs in one Actor. Pick a mode:
| Mode | What it does | Login? |
|---|---|---|
author_posts | Every post from one or more accounts, with reply and media filters | No |
search_posts | Full-text keyword search across Bluesky — date, language, author and hashtag filters | App password |
profiles | Full profile details for given accounts | No |
search_profiles | Find accounts by keyword | No |
post_thread | The complete reply tree under a post | No |
followers | An account's followers | No |
follows | Who an account follows | No |
Only keyword post search needs an app password — Bluesky does not serve that one endpoint anonymously. Everything else runs on the public AppView out of the box.
Why use this one rather than another Bluesky scraper?
- Image alt-text.
media[].altcarries the author's own description of each image. Most scrapers drop it; it is the single most useful field for AI, search and accessibility work. - The quoted post, in full. When a post quotes another, you get the quoted text, author and URL — not just an opaque reference.
handleResolved. You can tell a verified handle from an unresolved one (handle.invalid), and you get a working profile URL either way.- A flat, stable schema — engagement counts, hashtags, mentions, links and media pulled up to
top-level fields, and a real clickable
https://bsky.app/...URL on every post. Not a raw API dump. - Incremental monitoring built in. Set
onlyNewand a scheduled run returns only what it has not seen before — so a daily brand-alert costs you the new posts, not the same ones again. - It fails loudly. A run that fetches data and delivers nothing does not report success. See What happens when something fails?
How do I use it?
Get an account's latest posts (no login):
{ "mode": "author_posts", "handles": ["nytimes.com", "bsky.app"], "maxItems": 100 }
Search posts by keyword (needs an app password):
{"mode": "search_posts","searchQuery": "electric vehicles","lang": "en","since": "2026-07-01","sort": "latest","maxItems": 500,"blueskyIdentifier": "you.bsky.social","blueskyAppPassword": "<your-app-password>"}
Monitor mentions of your brand — schedule it hourly and pay only for new posts:
{"mode": "search_posts","searchQuery": "\"YourBrand\"","onlyNew": true,"stateLabel": "yourbrand-mentions","blueskyIdentifier": "you.bsky.social","blueskyAppPassword": "<your-app-password>"}
Get the full thread under a post (no login):
{ "mode": "post_thread", "postUrls": ["https://bsky.app/profile/bsky.app/post/3l..."], "threadDepth": 6 }
How do I scrape a Bluesky hashtag?
Two ways, depending on what you want:
{ "mode": "search_posts", "hashtags": ["climate"], "since": "2026-07-01","blueskyIdentifier": "you.bsky.social", "blueskyAppPassword": "<your-app-password>" }
That searches all of Bluesky for the tag. If instead you want a specific account's tagged posts,
use author_posts and filter on the hashtags field in the output — every post record carries
its hashtags as a top-level array.
How do I narrow what I get back — and pay less?
Billing is per delivered record, so every filter below is also a discount.
| Input | What it does |
|---|---|
maxItems | Hard stop on records delivered. The cheapest way to try the Actor. |
authorFeedFilter | Which slice of an author's feed: posts_and_author_threads keeps their posts and own threads, posts_no_replies drops replies to other people, posts_with_media keeps only posts with images or video. |
since / until | ISO date bounds. Use both to pull a fixed window — one calendar month — rather than "everything newer than X". |
fromAuthor | In search_posts, restricts the search to one author. "What did this account say about Y" in a single run instead of two. |
lang | Restricts search to one language. |
onlyNew + stateLabel | Returns only records not seen in previous runs under the same key. Turns any scrape into a monitor. |
How do I get a Bluesky app password?
- Open Bluesky → Settings → Privacy and Security → App Passwords.
- Add App Password, name it (e.g. "Apify"), copy the value (format
xxxx-xxxx-xxxx-xxxx). - Paste your handle into Bluesky handle and the value into Bluesky app password.
An app password is revocable and scoped — it is not your main password. It is sent only to
Bluesky's own createSession endpoint to sign in, and is never written to the dataset or the logs.
⬇️ Input reference
Every field the Actor accepts. The prefilled example works as-is — you only need mode and one target.
| Field | Type | Default | What it does |
|---|---|---|---|
mode | string | "author_posts" | Pick the capability. Everything except 'search_posts' works with no login. |
handles | array | — | e.g. nytimes.com, bsky.app, or did:plc:... . Used by author_posts, profiles, followers, follows. |
searchQuery | string | — | Keyword(s) for 'search_posts' or 'search_profiles'. Supports Bluesky search syntax. Example: climate policy — or with Bluesky syntax: "climate policy" -greenwashing. |
postUrls | array | — | bsky.app post URLs (or at:// URIs) for 'post_thread'. Example: https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l (at:// URIs work too). |
maxItems | integer | 50 | Hard cap on delivered records. You are billed per delivered record ($0.001), so this is also your cost ceiling: the default of 50 caps a run at $0.05. Raise it when you know how much you… |
authorFeedFilter | string | "posts_and_author_threads" | For 'author_posts': which posts to include. |
sort | string | "latest" | Sort order for search results: 'latest' (newest first) or 'top' (most relevant). |
since | string | — | Only posts on/after this time, e.g. 2026-07-01 or 2026-07-01T00:00:00Z. |
until | string | — | Only posts before this date (YYYY-MM-DD). Search modes only. |
lang | string | — | Two-letter code, e.g. en, pl. |
fromAuthor | string | — | Restrict search to one account's posts. Handle with or without the @, e.g. bsky.app or @bsky.app. |
mentions | string | — | Posts mentioning this account. Handle with or without the @, e.g. bsky.app. |
hashtags | array | — | Posts carrying these tags, without the # sign — e.g. climate, opensource. |
threadDepth | integer | 6 | For 'post_thread': how many reply levels to walk. |
onlyNew | boolean | false | Skip posts already delivered in previous runs of this saved task — turns any scrape into a change monitor / alert feed. |
stateLabel | string | — | Optional label that groups the 'Only new' memory. Leave blank to derive it from the query or accounts. Example: daily-climate-watch. Two schedules sharing a label share one memory. This is… |
blueskyIdentifier | string | — | Only needed for 'search_posts'. Your Bluesky handle, e.g. you.bsky.social. |
blueskyAppPassword | string | — | Create at Bluesky → Settings → App Passwords. NOT your main password. Only used to sign in for post search; never stored. |
⬆️ Output — what you get back
📝 Posts (author_posts, search_posts, post_thread)
{"uri": "at://did:plc:.../app.bsky.feed.post/3l...","url": "https://bsky.app/profile/handle/post/3l...","cid": "bafy...","text": "the post text","createdAt": "2026-07-20T10:00:00.000Z","indexedAt": "2026-07-20T10:00:01.000Z","langs": ["en"],"author": {"did": "did:plc:...", "handle": "handle", "handleResolved": true,"displayName": "Name", "avatar": "https://..."},"likeCount": 6512, "repostCount": 539, "replyCount": 88, "quoteCount": 12, "bookmarkCount": 4,"hashtags": ["climate"], "mentions": ["did:plc:..."], "links": ["https://..."],"embedType": "images","media": [{ "url": "https://...", "alt": "a solar farm at sunset", "type": "image" }],"quotedPost": {"uri": "at://...", "url": "https://bsky.app/profile/.../post/...","author": "quoted.handle", "text": "the quoted post"},"external": null,"isReply": false, "isRepost": false, "replyParent": null, "replyRoot": null,"labels": []}
👤 Profiles (profiles, search_profiles, followers, follows)
{"did": "did:plc:...", "handle": "handle", "handleResolved": true,"url": "https://bsky.app/profile/handle","displayName": "Name", "description": "bio text","avatar": "https://...", "banner": "https://...","followersCount": 12043, "followsCount": 311, "postsCount": 2894,"createdAt": "2023-05-01T09:12:00.000Z", "indexedAt": "2026-07-20T10:00:01.000Z","labels": []}
Download from the run's Dataset tab as JSON, CSV or Excel, or pull it through the Apify API.
What happens when something fails?
Failures are never written into your dataset as records, and you are never charged for one.
| Situation | What the Actor does |
|---|---|
| Bad or missing input for the chosen mode | Fails immediately with a message naming the problem. |
| App password rejected | Fails with Authentication failed, and still bills only what was already delivered — a mid-run auth failure never re-delivers or double-charges next run. |
| One target account errors, others work | Continues, delivers what worked, and counts the failure. |
| Data fetched but zero records delivered | The run fails with the reason — every record dropped in normalisation, every target errored, or pagination hit a safety backstop. A broken run never reports success. |
onlyNew finds nothing new | Succeeds with zero records. That is a legitimate quiet poll, not a failure, and it costs nothing. |
| Pagination stops at a safety backstop | Reported as truncated — a partial result is never passed off as a complete one. |
| Billing calls keep failing | Delivery stops rather than giving data away unpaid, and the run says how many records it delivered first. |
The two rows that matter most are the fourth and the fifth. Distinguishing "nothing broke, there is simply nothing new" from "everything broke and you got nothing" is the difference between a monitor you can trust on a schedule and one you have to check by hand.
How much does it cost?
- $0.001 per record delivered (
result-item), flat — $1.00 per 1 000. - An empty run costs nothing. No record written, no charge.
- Nothing is charged for compute time, pagination, retries or bandwidth. A slow run costs the same as a fast one.
- To try it cheaply, set
maxItemsto 10. That is one cent.
What can I do with the data?
- Social listening and brand monitoring on the fastest-growing X alternative — schedule
search_postswithonlyNewand get only what is new since the last check. - Research datasets for AI and academia: public data, open protocol, alt-text included.
- Topic and hashtag tracking as Bluesky adoption grows.
- Community and lead research — find accounts around a topic, then profile them.
- Clean it first: pipe the dataset straight into Dataset Deduplicator & Cleaner to merge duplicates across runs before analysis.
FAQ
Can I use it with the Apify API?
Yes. POST /v2/acts/eszetael_lab~bluesky-scraper/runs with your input as the JSON body, then read
the run's dataset. Standard API, CLI and client libraries all work.
Can I use it through an MCP server?
Yes. It is exposed through Apify's Actors MCP server like any public Actor, so an AI agent can call it as a tool. It is also enabled for agentic payments — an agent can run and pay for it without a human in the loop.
Can I schedule it to run automatically?
Yes — use Apify Schedules, and set onlyNew: true with a stateLabel. Each run then returns only
posts it has not seen before, so a daily monitor costs you the new posts rather than the whole feed
again.
Do I need a proxy?
No. The AT Protocol AppView serves public data directly. There is no anti-bot layer to defeat, which is also why this Actor is cheap to run and does not break when a page layout changes.
Does it scrape private posts or DMs?
No. It reads only public data through the official public AppView. No private posts, no logged-in-only content, no fake accounts.
Is it legal, and what about GDPR?
Reading public posts from an open protocol is broadly lawful, but profile and follower data is personal data under GDPR. You need your own lawful basis for processing it, and you are responsible for how you store and use what you export. This Actor gives you the data; it does not give you the basis.
Your feedback
Found a bug, or a case where a mode returns something odd? Open an issue on the Actor's Issues tab. Real failure reports are worth more to us than feature requests.
Ready-made setups
Each of these is this Actor with the input already filled in — open it, press Run, then change the target to yours. No configuration to read first.
- Get Bluesky Posts From a Date Onwards
- Export Bluesky Posts With Images or Video
- Get Bluesky Profile Details for a List of Handles
- Export Bluesky Account Posts With Likes and Reposts
- Export the Followers of a Bluesky Account
- See Who a Bluesky Account Follows
- Export a Bluesky Reply Thread in Full
- Find Bluesky Accounts by Keyword
- Check a Bluesky Account for New Posts Only
Related actors
Three tools built to chain into each other — scrape, then clean, then use.
- Dataset Deduplicator & Cleaner — pass this Actor's dataset ID straight in to merge duplicates across runs before analysis. Six times cheaper than a scraper, because it processes data you already paid to collect.
- Python Web Scraper — when your target is an ordinary website rather than Bluesky, write your own Python page function and get exactly the fields you want, with no template to fight.
All three are on pay-per-result pricing, and an empty run costs nothing in every one of them.
Built on the AT Protocol (app.bsky.*). Keywords: bluesky scraper, bluesky api, at protocol
scraper, scrape bluesky posts, bluesky profile scraper, bluesky hashtag, bluesky search, social
listening, decentralized social, social media scraper, bluesky data export.
If it worked for you
Ratings feed the Apify quality score, which decides whether anyone finds this Actor at all — and it is the one part we cannot build ourselves. If it did the job, a rating takes a few seconds.
If it did not, the issue tab is more useful than a low rating with no detail: tell us the input that failed and what you expected, and it becomes a test case in the next release.