Bluesky Scraper — search posts, profiles, followers as JSON
Pricing
from $2.00 / 1,000 item results
Bluesky Scraper — search posts, profiles, followers as JSON
Search Bluesky posts by keyword or fetch profiles, followers, follows and author feeds via the open AT Protocol API. Engagement counts, embeds, language and direct links in clean JSON — no login, no API key, no proxies. Built for social analytics, research, brand monitoring and AI agents.
Pricing
from $2.00 / 1,000 item results
Rating
0.0
(0)
Developer
Daniel Posztos
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Share
SEO title: Bluesky Scraper — search posts, profiles, followers as JSON SEO description: Search Bluesky posts by keyword or fetch profiles, followers, follows and author feeds via the open AT Protocol API. Engagement counts, embeds, language and direct links in clean JSON — no login, no API key, no proxies. Built for social analytics, research, brand monitoring and AI agents.
One actor, five data types, zero credentials. Bluesky runs on the open AT Protocol: its AppView API is public and documented, so this actor talks to the same endpoints the official app uses — no scraping fragility, no anti-bot arms race, no proxy costs.
What it does
Pick a type and get clean, deduplicated JSON items:
type | What you get | Needs |
|---|---|---|
posts (default) | Keyword search across all Bluesky posts, newest first | search_query |
profiles | Full profile per account: bio, follower/post counts | handles |
followers | Who follows the given account(s) | handles |
follows | Who the given account(s) follow | handles |
author_feed | Recent posts/reposts from the given account(s)' own timelines | handles |
Input
| Field | Type | Default | Description |
|---|---|---|---|
type | string | "posts" | One of posts, profiles, followers, follows, author_feed. |
search_query | string | — | Keyword(s) for post search. Required for (and only used by) type: "posts". |
handles | array of strings | [] | Bluesky handles ("bsky.app") and/or DIDs ("did:plc:..."), mixable. Required for every type except posts. |
max_items | integer (1–5000) | 100 | Total result cap. For followers/follows/author_feed with multiple handles the budget is split evenly across handles. |
date_from | string | — | ISO date ("2026-06-01") or datetime ("2026-06-01T00:00:00Z"). Only used by type: "posts": restricts results to posts on/after this moment. |
Type-specific required fields are validated up front with an error message that says exactly what to add (e.g. type: "posts" without search_query → "Post search needs a keyword: set e.g. 'search_query': "bitcoin"").
Worked example — input
{"type": "posts","search_query": "bitcoin","max_items": 25}
Worked example — output (1 of 25 items, real data)
{"post_uri": "at://did:plc:22mtqdiuelequw4xh4hirr4w/app.bsky.feed.post/3mpwduk2qgk2u","cid": "bafyreidyobz5lit5f4k46yeyil3i26b3jvkdv35xuhhmztxuufdjwaqry4","author": {"handle": "bufbvr.bsky.social","did": "did:plc:22mtqdiuelequw4xh4hirr4w","display_name": "Buffcoat and Beaver"},"text": "Just walked into the luncheonery store... Oh, macro-flavored Bitcoin.","created_at": "2026-07-05T19:49:34.521858+00:00","likes": 0,"reposts": 0,"replies": 0,"quotes": 0,"embeds": [],"lang": "en","url": "https://bsky.app/profile/bufbvr.bsky.social/post/3mpwduk2qgk2u","scraped_at": "2026-07-05T19:51:39.872045+00:00"}
A profile lookup ({"type": "profiles", "handles": ["bsky.app"]}) returns:
{"did": "did:plc:z72i7hdynmk6r22z27h6tvur","handle": "bsky.app","display_name": "Bluesky","description": "official Bluesky account (check username👆)...","followers_count": 33981092,"follows_count": 10,"posts_count": 800,"created_at": "2023-04-12T04:53:57.057Z","url": "https://bsky.app/profile/bsky.app","scraped_at": "2026-07-05T19:45:01.163306+00:00"}
Output schema
type: "posts" and type: "author_feed" — post item
| Field | Type | Notes |
|---|---|---|
post_uri | string | AT Protocol URI (at://did/app.bsky.feed.post/rkey) — the post's stable ID |
cid | string | Content hash of the post record |
author.handle | string | Author's handle (may read handle.invalid while an account's domain verification is broken) |
author.did | string | Author's stable decentralized ID |
author.display_name | string or null | |
text | string | Post text |
created_at | string (ISO 8601) | Author-declared creation time |
likes / reposts / replies / quotes | integer | Engagement counts at scrape time |
embeds | array | Simplified: {type, url, alt} with type one of image, link, quote, video, other. alt is image alt-text / link title. |
lang | string or null | Post's self-declared primary language ("en", …) |
url | string | https://bsky.app/profile/{handle}/post/{rkey} — opens in a browser |
is_repost | boolean | author_feed only: the requested account reposted someone else's post (whose author is then the original author) |
feed_of | string | author_feed only: the requested handle/DID this item came from |
scraped_at | string (ISO 8601) | When this actor fetched the data |
type: "profiles" — profile item
| Field | Type | Notes |
|---|---|---|
did | string | Stable decentralized ID |
handle | string | e.g. "bsky.app" |
display_name | string or null | |
description | string or null | Profile bio |
followers_count / follows_count / posts_count | integer | |
created_at | string (ISO 8601) | Account creation time |
url | string | https://bsky.app/profile/{handle} |
scraped_at | string (ISO 8601) |
type: "followers" and type: "follows" — connection item
| Field | Type | Notes |
|---|---|---|
subject_handle | string | The requested account this connection belongs to |
subject_did | string | The requested account's DID |
did, handle, display_name, description, created_at, url | The connected profile (the follower, or the followed account) — same meaning as the profile item | |
followers_count / follows_count / posts_count | null | Always null here: Bluesky's follower/follow list API returns compact profiles without counts. Run a second type: "profiles" pass on the handles you care about to get counts. |
scraped_at | string (ISO 8601) |
Pricing (pay-per-event)
| Event | Price | When it's charged |
|---|---|---|
item-result | $0.002 | Once per item returned, regardless of type |
Plus Apify's own apify-actor-start synthetic event (first 5 seconds of compute free, platform-managed).
Examples: 1000 posts = 1000 × $0.002 = $2.00. 25-post smoke run = $0.05. 10 profile lookups = $0.02. 5000 followers = $10.00.
If a run's cost would exceed the Max total charge USD you set for it, the actor stops producing further results at exactly that point — it never crashes and never produces unbilled/"free" results past the limit. Delivered count always equals charged count.
Error messages
type: "posts"withoutsearch_query: "Post search needs a keyword: set e.g. 'search_query': "bitcoin"..." — add the field (the message also tells you which type to use if you meant account lookups).profiles/followers/follows/author_feedwithouthandles: "This type needs at least one Bluesky handle or DID to look up..."- Unknown
type: the message lists all five valid values and what each does. max_itemsout of range / wrong types / baddate_from: the message states the expected format and a corrected example.- A handle that doesn't exist: that handle is skipped with a log warning naming it (other handles in the list still return data); if every input handle fails, the run ends with the explicit 0-results warning below.
- 0 results with otherwise valid input: not an error — the run succeeds, but the log carries an explicit
WARNING: 0 results produced despite valid input (...)line and the run's status message says so. Never a silent "nothing happened."
Known limitations (documented, not hidden)
- Public data only. The actor reads Bluesky's open, unauthenticated AppView. No private accounts, no logged-in-only data, no DMs — by design.
- Post search depth: Bluesky's API rejects unauthenticated cursor pagination on post search, so this actor paginates search results by time-windowing (
untilkeyset on the post timestamp) instead. Results stay newest-first and are deduplicated; pathological corner cases (hundreds of posts sharing one millisecond) could theoretically end pagination early. Followers/follows/author feeds use normal cursor pagination (not affected). - Rate limits: the AppView rate-limits per IP; bursts get HTTP 429, which the actor retries with exponential backoff automatically. Very large runs simply take a bit longer.
- Follower/follow counts are
nullin list items (see the connection-item table) — that's what the upstream list API provides. author_feedincludes reposts (markedis_repost: true); pinned-post duplication is avoided by URI-level dedup.
Data sources (public, no API key, no scraping)
Bluesky AppView (AT Protocol XRPC over HTTPS): app.bsky.feed.searchPosts, app.bsky.actor.getProfile, app.bsky.feed.getAuthorFeed, app.bsky.graph.getFollowers, app.bsky.graph.getFollows — the same documented, unauthenticated endpoints the official Bluesky app calls.