Bluesky Scraper - Posts, Profiles, Followers, Threads & Search
Pricing
Pay per event
Bluesky Scraper - Posts, Profiles, Followers, Threads & Search
Scrape Bluesky without login: search posts, profiles, author feeds, followers/following, full threads & user search. Clean JSON/CSV rows with text, metrics, hashtags, links & media. Cheapest per-result pricing.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Creator Fusion
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Bluesky Scraper — Posts, Profiles, Followers, Threads & Search for AI Agents & Data Pipelines
The all-in-one Bluesky scraper for AI agents and data pipelines: search posts, fetch profiles, export author feeds, followers/following graphs, full conversation threads, and user search — and get back typed JSON rows validated against a published output schema (documented below). Built on the official public AT Protocol AppView API: no login, no cookies, no proxies, data is near-realtime, and every run's output is exportable as JSON, CSV, or Excel.
Why AI agents use this Bluesky scraper
- Deterministic typed output — every dataset row validates against the actor's dataset schema; field names and types never drift between runs or modes.
- Cost-predictable per-event pricing — a fixed price per result plus a start fee ($1 per 1,000 results, cheapest in the Apify Store).
maxItemshard-caps spend, so autonomous budgets are safe. - Zero auth, zero proxies — reads the official public AT Protocol AppView anonymously. No credentials to manage, no account risk, and no hidden proxy costs ever (many scrapers bill proxy traffic on top of results — this one cannot, by construction).
- Clear error semantics — invalid input fails fast with exit code 1 and a human-readable status message; nothing fails silently (details in Error semantics below).
- Rate-limit handling built in — 429/5xx responses retry automatically with exponential backoff; WAF 403s fall back to an alternate official host.
Modes
| Mode | What it does | Row shape | Needs |
|---|---|---|---|
search-posts (default) | Full-text post search with sort, since, until, author, mentions, lang filters | post | queries |
profiles | Full profile details (bio, banner, follower/post counts) | profile | targets |
author-feed | All posts by given accounts, filterable (no replies / with media / …) | post | targets |
followers | Who follows the given accounts | profile | targets |
following | Who the given accounts follow | profile | targets |
thread | Full conversation thread for given post URLs, flattened to rows | post | postUrls |
search-users | Search user accounts by keyword | profile | queries |
Input schema
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
mode | string (enum) | yes | search-posts | One of search-posts, profiles, author-feed, followers, following, thread, search-users. |
queries | array of strings | for search-posts, search-users | — | Keywords or phrases to search for. Supports Bluesky search syntax ("exact phrase", from:handle). |
targets | array of strings | for profiles, author-feed, followers, following | — | Accounts — handles (bsky.app or @bsky.app), DIDs (did:plc:...), or profile URLs (https://bsky.app/profile/bsky.app). |
postUrls | array of strings | for thread | — | Post URLs (https://bsky.app/profile/<handle>/post/<id>) or AT-URIs (at://<did>/app.bsky.feed.post/<id>); returns root + all replies up to 10 levels deep. |
maxItems | integer | no | 100 | Maximum result rows in total across all queries/targets. Controls cost: you are charged per result. |
sort | string (enum) | no | latest | search-posts only. latest (newest first) or top (most relevant). |
since | string | no | — | search-posts only. Only posts on/after this date, ISO format (2024-11-01 or 2024-11-01T00:00:00Z). |
until | string | no | — | search-posts only. Only posts before this date, ISO format. |
author | string | no | — | search-posts only. Only posts by this account (handle, DID, or profile URL). |
mentions | string | no | — | search-posts only. Only posts mentioning this account (handle, DID, or profile URL). |
lang | string | no | — | search-posts only. 2-letter language code (en, ja, pt). |
postFilter | string (enum) | no | posts_no_replies | author-feed only. posts_no_replies, posts_with_replies, posts_with_media, or posts_and_author_threads. |
Output schema
Rows come in two shapes — post rows (search-posts, author-feed, thread) and profile rows (profiles, followers, following, search-users) — plus a few mode-specific fields. Fields absent from a row's shape are simply omitted.
| Field | Type | Nullable | Modes | Description |
|---|---|---|---|---|
query | string | no | search-posts, search-users | The search query that produced this row. |
target | string | no | author-feed, followers, following | Normalized target account (handle or DID) this row was scraped for. |
threadOf | string | no | thread | The input post URL whose thread this row belongs to. |
parentUri | string | yes | thread | AT-URI of the parent post; null for the thread root when it has no parent. |
depth | integer | no | thread | 0 = the requested post, 1 = direct reply, etc. |
isRepost | boolean | no | author-feed | Feed entry is a repost by the target account. |
isPin | boolean | no | author-feed | Feed entry is the account's pinned post. |
uri | string | no | post rows | Canonical AT-URI (at://did/app.bsky.feed.post/rkey). |
cid | string | no | post rows | Content identifier of the post record. |
url | string | no | all | bsky.app post URL (post rows) or profile URL (profile rows). |
author | object | no | post rows | { handle, displayName, did, avatar } — each string or null. |
text | string | no | post rows | Full post text (empty string for text-less posts). |
createdAt | string | yes | all | ISO 8601 creation time of the post (post rows) or account (profile rows). |
likeCount | integer | no | post rows | Like count (0 when absent). |
repostCount | integer | no | post rows | Repost count. |
replyCount | integer | no | post rows | Reply count. |
quoteCount | integer | no | post rows | Quote-post count. |
langs | array of strings | no | post rows | Language codes declared on the post record. |
hashtags | array of strings | no | post rows | Hashtags parsed from rich-text facets (no # prefix). |
links | array of strings | no | post rows | External link URLs parsed from rich-text facets. |
embedType | string | yes | post rows | AT Protocol embed view type (e.g. app.bsky.embed.images#view); null when no embed. |
imageUrls | array of strings | no | post rows | Full-size image URLs from image embeds (empty when none). |
videoPlaylistUrl | string | yes | post rows | HLS playlist URL for video embeds; null otherwise. |
externalUrl | string | yes | post rows | URL of an external-link card embed; only present when the post has one. |
did | string | no | profile rows | Decentralized identifier of the account. |
handle | string | no | profile rows | Bluesky handle. |
displayName | string | yes | profile rows | Display name. |
description | string | yes | profile rows | Profile bio. |
avatar | string | yes | profile rows | Avatar image URL. |
banner | string | yes | profile rows | Banner image URL (full profile views; null in list views). |
followersCount | integer | yes | profile rows | Follower count (null in list views that omit counts). |
followsCount | integer | yes | profile rows | Following count (null in list views that omit counts). |
postsCount | integer | yes | profile rows | Post count (null in list views that omit counts). |
indexedAt | string | yes | profile rows | When the AppView last indexed the account. |
labels | array of strings | no | profile rows | Moderation label values (e.g. !no-unauthenticated). |
Example item — search-posts
{"query": "apify","uri": "at://did:plc:3qx3wgjvwbhbr2uvbv5qnot4/app.bsky.feed.post/3mt2dck2a3u2p","cid": "bafyreif5cxxqatw3om7sfto6xhtoovzfq6ewpw6dpcruhavjwv3cvsdgcq","url": "https://bsky.app/profile/pingoru.bsky.social/post/3mt2dck2a3u2p","author": {"handle": "pingoru.bsky.social","displayName": "Pingoru.io","did": "did:plc:3qx3wgjvwbhbr2uvbv5qnot4","avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:.../bafkrei..."},"text": "Update: Apify marked this resolved at 14:05 UTC.","createdAt": "2026-08-14T14:05:43Z","likeCount": 12,"repostCount": 3,"replyCount": 1,"quoteCount": 0,"langs": ["en"],"hashtags": ["apify"],"links": ["https://status.apify.com"],"embedType": "app.bsky.embed.images#view","imageUrls": ["https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:.../bafkrei..."],"videoPlaylistUrl": null}
Example item — profiles
{"did": "did:plc:z72i7hdynmk6r22z27h6tvur","handle": "bsky.app","url": "https://bsky.app/profile/bsky.app","displayName": "Bluesky","description": "The official Bluesky account.","avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:.../bafkrei...","banner": "https://cdn.bsky.app/img/banner/plain/did:plc:.../bafkrei...","followersCount": 3452871,"followsCount": 8,"postsCount": 312,"createdAt": "2023-04-12T04:53:57Z","indexedAt": "2026-08-10T02:11:08Z","labels": []}
Example item — author-feed
Same shape as search-posts but with target, isRepost, isPin instead of query:
{"target": "bsky.app","isRepost": false,"isPin": true,"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l","cid": "bafyreihx...","url": "https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l","author": { "handle": "bsky.app", "displayName": "Bluesky", "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "avatar": "https://cdn.bsky.app/img/avatar/plain/..." },"text": "Welcome to Bluesky!","createdAt": "2024-10-24T17:02:23Z","likeCount": 88231, "repostCount": 12045, "replyCount": 4102, "quoteCount": 903,"langs": ["en"], "hashtags": [], "links": [],"embedType": null, "imageUrls": [], "videoPlaylistUrl": null}
Example item — followers / following
Profile shape plus target:
{"target": "bsky.app","did": "did:plc:44ybard66vv44zksje25o7dz","handle": "jay.bsky.team","url": "https://bsky.app/profile/jay.bsky.team","displayName": "Jay","description": "CEO of Bluesky.","avatar": "https://cdn.bsky.app/img/avatar/plain/...","banner": null,"followersCount": null,"followsCount": null,"postsCount": null,"createdAt": "2023-04-12T04:53:57Z","indexedAt": "2026-08-01T00:00:00Z","labels": []}
(List views omit counts — expect null for followersCount/followsCount/postsCount/banner here; run profiles mode on the handles if you need counts.)
Example item — thread
Post shape plus threadOf, parentUri, depth:
{"threadOf": "https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l","parentUri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l","depth": 1,"uri": "at://did:plc:abc.../app.bsky.feed.post/3l6ovxyz...","cid": "bafyreig...","url": "https://bsky.app/profile/someone.bsky.social/post/3l6ovxyz...","author": { "handle": "someone.bsky.social", "displayName": "Someone", "did": "did:plc:abc...", "avatar": null },"text": "Congrats on the launch!","createdAt": "2024-10-24T17:10:00Z","likeCount": 5, "repostCount": 0, "replyCount": 0, "quoteCount": 0,"langs": ["en"], "hashtags": [], "links": [],"embedType": null, "imageUrls": [], "videoPlaylistUrl": null}
Example item — search-users
Profile shape plus query:
{"query": "apify","did": "did:plc:xyz...","handle": "apify.com","url": "https://bsky.app/profile/apify.com","displayName": "Apify","description": "Full-stack web scraping platform.","avatar": "https://cdn.bsky.app/img/avatar/plain/...","banner": null,"followersCount": null,"followsCount": null,"postsCount": null,"createdAt": "2023-07-01T09:00:00Z","indexedAt": "2026-08-12T10:00:00Z","labels": []}
Error semantics
- Bad input fails fast. A mode missing its required field (
queries,targets, orpostUrls), or a malformed post URL inthreadmode, aborts the run immediately with exit code 1 and an explanatory status message (e.g.Mode "profiles" requires at least one entry in "targets"). No partial charge beyond the start event. - Invalid targets fail loudly. A nonexistent handle or invalid parameter returns a permanent 4xx from the API; the run fails with the API's error detail in the message. Rows scraped before the failure are already in the dataset — nothing is silently dropped.
- Transient errors are retried. 429 and 5xx responses get 3 attempts with exponential backoff (1s, 2s); 403 WAF blocks automatically fall back to the alternate official host. Only after all retries fail does the run fail.
- Zero results is a success. A query matching nothing completes normally with an empty dataset (exit 0) — treat "run succeeded, 0 items" as a valid empty answer, not an error to retry.
Use from AI agents (MCP)
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=apricot_blackberry/bluesky-all-in-one","headers": { "Authorization": "Bearer <YOUR_APIFY_TOKEN>" }}}}
Works in Claude, Cursor, ChatGPT deep research connectors, and any MCP client; the input schema above is the tool's parameter schema.
Use from code
curl (synchronous run, returns dataset items directly):
curl -X POST "https://api.apify.com/v2/acts/apricot_blackberry~bluesky-all-in-one/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"mode":"search-posts","queries":["apify"],"maxItems":100}'
JavaScript (apify-client):
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('apricot_blackberry/bluesky-all-in-one').call({mode: 'search-posts',queries: ['apify'],maxItems: 100,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Python (apify-client):
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("apricot_blackberry/bluesky-all-in-one").call(run_input={"mode": "search-posts","queries": ["apify"],"maxItems": 100,})items = client.dataset(run["defaultDatasetId"]).list_items().items
Use from automation platforms
- n8n / Make / Zapier — use the native Apify integration and pick this actor by name (
bluesky-all-in-one); the run's dataset feeds directly into the next step. - LangChain / LlamaIndex — call it through the Apify tool wrappers (
ApifyActorsTool/ApifyActorloader) with the input JSON shown above. - Webhooks — Apify webhooks can fire on run completion (e.g.
ACTOR.RUN.SUCCEEDED) to hand results to your own pipeline automatically, no polling required.
Pricing
Pay per event — you only pay for what you scrape:
| Event | Price | When |
|---|---|---|
| Actor start | per run | Once per run |
| Result | per row | Every dataset row, all modes |
That works out to $1 per 1,000 results — no subscription, no proxy fees, no platform-usage surprises, and maxItems caps your spend on every run.
FAQ
Do I need a Bluesky account or app password? No. The actor only reads public data through Bluesky's official public AppView API. Nothing to configure, no account at risk.
Can I export Bluesky followers to CSV?
Yes — run the followers mode with your target handle, then download the dataset as CSV from the run's Storage tab (or via the dataset API).
How fresh is the data?
Near-realtime. The AppView indexes the AT Protocol firehose continuously; a search-posts run with sort: latest returns posts from seconds ago.
Does it scrape private or deleted content? No. Only publicly visible content served by the official AppView. Blocked, deleted, or private data is not accessible — this keeps the actor compliant and stable.
What happens if I hit a rate limit? The actor retries automatically with exponential backoff. For very large jobs, split targets across multiple runs.
Can I search posts by a specific user within a date range?
Yes — use search-posts with author, since, and until set. Or use author-feed to get a user's complete timeline.
Changelog
- 1.1 — Published dataset output schema (typed, validated rows) + agent-first docs: MCP config, curl/JS/Python snippets, error semantics.
- 1.0 — Initial release: 7 modes (search-posts, profiles, author-feed, followers, following, thread, search-users), facet parsing (hashtags/links), media extraction, pay-per-result pricing.