Bluesky Posts & Profiles Scraper (No Login) avatar

Bluesky Posts & Profiles Scraper (No Login)

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Bluesky Posts & Profiles Scraper (No Login)

Bluesky Posts & Profiles Scraper (No Login)

Scrape Bluesky posts by keyword or hashtag, user feeds, profiles, followers and following via Bluesky's official public API. No login, no proxies. Monitor mode returns only new posts.

Pricing

from $1.00 / 1,000 results

Rating

0.0

(0)

Developer

Agent Factory

Agent Factory

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

16 days ago

Last modified

Categories

Share

Bluesky Posts, Profiles & Followers: official API, no login, pay only for new posts

Collect public Bluesky data from Bluesky's own public API: posts matching keywords or #hashtags, any account's posts, profiles, and follower/following lists. No login, no cookies, no proxies, no browser.

Turn on monitor mode and schedule it. Each run returns only posts this monitor hasn't delivered before, so you never pay twice for the same post. It also tells you when you might have missed posts (Bluesky caps logged-out search at 100 posts per query), so you know when to schedule runs more often.

  • ✅ Pay only for new posts. Monitor mode remembers what it already delivered (per query/account, per filter set). Re-runs with nothing new cost only the $0.00005 start fee.
  • ✅ Honest about Bluesky's limits. Gap warnings, per-query hitsTotal, and a clear OUTPUT record of what was skipped and why. No silent truncation.
  • ✅ Official API only: public.api.bsky.app / api.bsky.app, the same AppView the Bluesky app uses. Nothing to break when the website changes.
  • ✅ Respects user privacy choices. Accounts that asked Bluesky not to show their content to logged-out viewers are never returned (and never charged).
  • ✅ One clean row per result. No summary rows, no empty rows, no error rows in your dataset. You pay only for real posts, profiles and accounts.

What can I use it for?

  • Brand and keyword monitoring: hourly runs on your brand, product or competitors, with only new mentions delivered to Slack, Sheets or a webhook.
  • Hashtag and event tracking: #hashtag with a date window.
  • Creator and competitor tracking: new posts from a list of accounts, with engagement counts.
  • Audience research and lead lists: profiles, followers and following, with bios and counts.
  • Research and AI pipelines: clean JSON with language, hashtags, mentions, links, media and labels.

Modes

modeInputOne result =
searchPostsqueries: keywords, phrases or a single #hashtag per lineone matching post
userPostsaccounts: handles, DIDs or profile URLsone post from that account (reposts optional)
profilesaccountsone profile
followersaccountsone follower of that account
followingaccountsone account it follows

Input

FieldTypeDescription
modestringsearchPosts (default), userPosts, profiles, followers, following
queriesarraysearchPosts: one search per line. #tag = hashtag search. Bluesky search syntax works (e.g. "exact phrase")
accountsarraybsky.app, @bsky.app, did:plc:…, https://bsky.app/profile/…; a bare jay means jay.bsky.social
onlyNewSinceLastRunbooleanMonitor mode (searchPosts, userPosts): emit only posts not delivered before. The first run is the baseline
stateKeystringMonitor name. One per schedule/watchlist (default default)
since / untilstringPost time window: YYYY-MM-DD, ISO timestamp, or relative 24h / 7d / 2w. since is inclusive, until exclusive
sortstringsearchPosts: latest (default) or top
langstringsearchPosts: language code, e.g. en
author / mentionsstringsearchPosts: only posts by / mentioning this handle or DID
domainstringsearchPosts: only posts linking to this domain
feedFilterstringuserPosts: posts_and_author_threads (default, like the app's Posts tab), posts_with_replies, posts_no_replies, posts_with_media
includeRepostsbooleanuserPosts: include reposts (isRepost: true), default false
skipAdultContentbooleanDrop posts labeled porn / sexual / nudity / graphic-media (default true; skipped posts aren't charged)
maxItemsPerSourceintegerCap per query or account (default 100)
maxItemsintegerHard cap per run. Use it to control cost

Examples:

{ "mode": "searchPosts", "queries": ["apify", "#opensource"], "since": "7d", "lang": "en", "maxItemsPerSource": 25 }
{ "mode": "searchPosts", "queries": ["\"my brand\"", "#mybrand"], "onlyNewSinceLastRun": true, "stateKey": "brand-hourly" }
{ "mode": "userPosts", "accounts": ["bsky.app", "https://bsky.app/profile/apify.com"], "onlyNewSinceLastRun": true, "includeReposts": true }
{ "mode": "followers", "accounts": ["apify.com"], "maxItemsPerSource": 1000 }

Output

One dataset item per result. Posts have the same shape in searchPosts and userPosts:

{
"type": "post",
"uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mwolmfws5k2r",
"cid": "bafyreicquqnkjvugubmjyfiorc3nbrxuu7y4faicpulqmxb4ua73tpb564",
"postId": "3mwolmfws5k2r",
"url": "https://bsky.app/profile/bsky.app/post/3mwolmfws5k2r",
"text": "Happy opening day of hockey season, NHL fans! \n\nKeep up with all 1,344 regular season games with this starter pack of hockey writers.",
"createdAt": "2026-09-29T19:43:14.855Z",
"indexedAt": "2026-09-29T19:43:15.262Z",
"langs": ["en"],
"authorDid": "did:plc:z72i7hdynmk6r22z27h6tvur",
"authorHandle": "bsky.app",
"authorDisplayName": "Bluesky",
"authorAvatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreihwihm6kpd6zuwhhlro75p5qks5qtrcu55jp3gddbfjsieiv7wuka",
"authorVerified": false,
"replyCount": 72, "repostCount": 140, "likeCount": 983, "quoteCount": 17, "bookmarkCount": 56,
"isReply": false, "replyParentUri": null, "replyRootUri": null,
"isQuote": false, "quotedPost": null,
"hashtags": [], "mentions": [], "links": [],
"images": [], "video": null, "externalLink": null,
"labels": [],
"isRepost": false, "repostedByHandle": null, "repostedByDid": null, "repostedAt": null,
"mode": "userPosts",
"input": "bsky.app",
"scrapedAt": "2026-10-08T02:18:34.662Z"
}
FieldNotes
mentions[{did, handle}], with handles as written in the post
quotedPost{uri, url, authorHandle, text, available} for quote posts
images / video / externalLink[{url, thumb, alt}] / {playlist, thumbnail, alt} / {uri, title, description}
labelsModeration/self labels on the post
isRepost, repostedBy*, repostedAtuserPosts with includeReposts
changeType, firstSeenAtMonitor mode only ("new", and when this monitor first delivered it)
inputThe query or account this row came from

Profiles (type: "profile"): did, handle, displayName, description, url, avatar, banner, followersCount, followsCount, postsCount, listsCount, feedsCount, starterPacksCount, isLabeler, isBot, verified, pinnedPostUri, labels, createdAt, indexedAt. Followers / following (type: "follower" / "following"): ofHandle, ofDid, did, handle, displayName, description, url, avatar, isBot, verified, labels, createdAt, indexedAt.

Each run also writes an OUTPUT record to the default key-value store (free, not a dataset row): per query/account results, errors (e.g. "Account not found"), skip counts (opted-out, adult, outside date range, already delivered), Bluesky's hitsTotal for searches, monitor baseline / possibleGap flags, and whether a run limit was hit. Export the dataset as JSON, CSV, Excel or XML, or read it through the Apify API.

Monitor mode (pay only for new posts)

Set onlyNewSinceLastRun: true and schedule the Actor (e.g. every hour).

  • First run = baseline: current matching posts are delivered with changeType: "new".
  • Later runs deliver only posts this monitor hasn't delivered before. Engagement-count changes don't re-deliver a post.
  • Each query/account plus its filters has its own history, so changing lang or dates starts a fresh baseline for that combination. Use a different stateKey per schedule.
  • Gap warning: logged-out Bluesky search returns at most the newest 100 posts per query. If all 100 are new, older new posts may have been missed. OUTPUT then shows possibleGap: true, and you should schedule that monitor more often. For account feeds, the Actor pages back to where the last run stopped (up to maxItemsPerSource new posts).
  • If a run is cut short by maxItems, your spending limit, or an API error, the undelivered posts are offered again next run.
  • State lives in the named key-value store bluesky-public-data-state. It holds only post IDs and timestamps, never post text.

Pricing (pay per result)

$0.001 per result ($1 per 1,000 dataset items), plus a $0.00005 start fee per run.

  • One result = one post, profile, follower or followed account in the dataset. Nothing else is ever written to the dataset.
  • Errors, accounts not found, opted-out accounts, adult-filtered posts and already-delivered posts produce no rows, so they cost nothing.
  • Monitor runs with nothing new cost only the $0.00005 start fee.

Examples: 1,000 posts from a keyword search across several queries ≈ $1. A brand monitor that runs hourly and finds about 20 new posts per hour delivers about 480 posts a day ≈ $0.48/day (plus 24 start fees ≈ $0.0012). Without de-duplication, re-pulling the newest 100 posts every hour would be 2,400 rows a day. Set maxItems and maxItemsPerSource to cap any run. The Actor also stops cleanly at your run's maximum charge limit.

Limits (Bluesky's, not ours)

  • Search: max 100 posts per query per run. Bluesky doesn't allow logged-out clients to page further in post search. Use narrower queries, date windows (since/until), or monitor mode on a schedule.
  • Search can be switched off by Bluesky. Bluesky sometimes disables anonymous post search under heavy load. The query then shows an error in OUTPUT, and no rows (and no charges) are produced. Account feeds, profiles and follower lists are not affected.
  • Bluesky's search matching, language detection and top ranking are Bluesky's own. Results can differ slightly from the app.
  • Follower/following lists contain only accounts that are currently viewable. Deleted, deactivated and opted-out accounts are skipped, so counts can be lower than followersCount.
  • No private data: no DMs, no mutes or blocks, no likes lists, nothing that needs a login.

Compliance

  • Uses only Bluesky's official public AppView API without authentication, as documented in API Hosts and Auth and Rate Limits. No scraping, no login, no proxies.
  • Read-only: it never posts, likes, follows or messages anyone, in line with the Bluesky Developer Guidelines.
  • Honors the !no-unauthenticated self-label ("discourage apps from showing my account to logged-out users"), as Bluesky recommends. Those accounts' posts and profiles are never returned.
  • Polite by design: at most 3 concurrent requests and 4 requests per second, exponential backoff on 429/5xx, and an identifying User-Agent.
  • You are responsible for how you use the data. Bluesky content belongs to its authors. Honor deletion requests, and follow applicable privacy laws (e.g. GDPR) and the terms of wherever you republish it.

Contact

Questions, bug reports, or a takedown/deletion request (including from Bluesky): unrealevents22@gmail.com You can also open an issue on the Actor's Issues tab.

FAQ

Why did a search return fewer than 100 posts? Opted-out accounts and (by default) adult-labeled posts are removed. OUTPUT shows the counts. Bluesky may also have fewer matches (hitsTotal).

Can I get more than 100 posts for one keyword? Run several narrower date windows (since/until) or language filters, or use monitor mode on a schedule to collect continuously.

Do I need a Bluesky account, cookies or proxies? No.

A search query failed with HTTP 403. Bluesky has temporarily disabled anonymous search. Try again later. Nothing was charged for that query.

Run locally (no Apify account needed)

npm install
mkdir -p storage/key_value_stores/default
echo '{"mode":"searchPosts","queries":["#bluesky"],"maxItemsPerSource":10}' > storage/key_value_stores/default/INPUT.json
node src/main.js # results in storage/datasets/default, OUTPUT in storage/key_value_stores/default
npm test # offline tests on recorded API responses