Bluesky Scraper โ€” Posts, Profiles & Followers, No Login avatar

Bluesky Scraper โ€” Posts, Profiles & Followers, No Login

Pricing

from $0.55 / 1,000 posts

Go to Apify Store
Bluesky Scraper โ€” Posts, Profiles & Followers, No Login

Bluesky Scraper โ€” Posts, Profiles & Followers, No Login

Scrape Bluesky without login for brand monitoring and audience research: search posts, account posts, profiles, followers and following lists, post likers and reposters, people search, reply threads and custom feeds, as clean JSON.

Pricing

from $0.55 / 1,000 posts

Rating

0.0

(0)

Developer

Cliqto Media

Cliqto Media

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

14 days ago

Last modified

Features

Limited permissions

Share

Bluesky Scraper reads public Bluesky data with no login: search posts, account posts, profiles, followers and following lists, who liked or reposted a post, people search, reply threads and custom feeds, returned as clean JSON rows.

Use it for social listening, audience research, lead lists or AI pipelines: the smallest run is one search query, such as apify, which returns up to 30 recent posts in a few seconds for about $0.03.

Bluesky Scraper: search queries, handles, post links and feed links in; posts, profiles, followers and likers out as clean rows. No login.

Table of contents

What it does

Bluesky Scraper reads the public Bluesky AppView API (AT Protocol) without an account, a password, cookies or proxies. It has ten modes:

ModeYou giveYou get
searchPostsKeywords, #hashtags, phrases, operators like from:bsky.app, lang:en, domain:github.comUp to 100 matching posts per query, latest or top
authorFeedHandles, DIDs or profile linksPosts from each account, newest first, with optional replies and reposts
profilesHandles, DIDs or profile linksOne profile row per account: bio, follower/following/post counts, avatar, pinned post
threadPost links or at:// URIsThe post, its parent posts and the replies below it, with threadRole and threadDepth
feedCustom feed links or list linksPosts from the feed or list
followersHandles, DIDs or profile linksThe accounts that follow each account
followingHandles, DIDs or profile linksThe accounts each account follows
postLikersPost links or at:// URIsThe accounts that liked each post, with the like time
postRepostersPost links or at:// URIsThe accounts that reposted each post
searchProfilesA name or keywordBluesky accounts that match it

An account, post or feed that does not exist returns a not_found row instead of silently disappearing, so the number of answers always matches your input.

Good fit

  • Track what people post about a brand, product, hashtag or domain on Bluesky.
  • Export the followers of an account, or the people who liked or reposted a post, for audience research or lead lists.
  • Collect the posts and profiles of a list of accounts, or pull a full reply thread for discussion analysis.
  • Feed clean, typed Bluesky rows into an AI agent, a spreadsheet or a database.

๐Ÿ‘‰ Try it now: open the input form

How it works

  1. You choose a mode and give search queries, account handles, post links or feed links.
  2. The Actor reads the public Bluesky API page by page, at most four requests per second, until it reaches your maxItems or the end of the list. It does not log in and does not get around any Bluesky limit.
  3. It saves one row per post, profile or account in the Dataset, a not_found, invalid_input or error row for every value that produced no data, and a run summary (RUN_SUMMARY) with a status for each input value.

๐Ÿ‘‰ Try it now: open the input form

Quick start

Input form: box 1 is the mode, box 2 the search queries, box 3 the limit per query

  1. Open the Input tab. Keep What to scrape on Search posts (box 1 in the picture).

  2. Add a word to Search queries, e.g. apify (box 2).

  3. Keep Max results per query, account, post or feed at the prefilled 30 (box 3; the runtime default is 100).

  4. Click Start at the bottom of the page (on your own copy of the form the button may read Save & start).

  5. Open the Posts view in the Output tab (box 1) and check the post text (box 2), the date and the likes (box 3). The author handle is under All fields or in the JSON.

    Output table: box 1 is the Posts view, box 2 the post text, box 3 the created time and like count

Expected result: up to 30 of the newest posts that match the query, one row per post.

๐Ÿ‘‰ Try it now: open the input form

Use cases

Buyer jobInput pathOutput entityKey fieldsNext step
Brand or keyword listeningmode: searchPosts, queriesposttext, createdAt, likeCount, author.handle, linksSchedule the Actor, export to Sheets or a database
Audience researchmode: followers, handlesaccounthandle, displayName, description, createdAtEnrich with profiles mode, CRM import
Engagement and topic accountsmode: postLikers, postReposters (postUrls) or searchProfiles (queries)accounthandle, displayName, likedAt, descriptionContest draws, influencer and outreach lists
Account and influencer researchmode: authorFeed or profiles, handlespost, profiletext, isRepost, followersCount, postsCountCompare accounts, CRM import
Discussion and feed analysismode: thread (postUrls) or feed (feedUrls)postthreadRole, threadDepth, text, hashtagsTopic analysis, AI summarisation

๐Ÿ‘‰ Try it now: open the input form

Input

Input fields

FieldPlain nameTypeRequiredDefault / prefillAllowed valuesEffectExample
modeWhat to scrapestringyessearchPoststhe ten modes abovePicks the list that is read"followers"
queriesSearch queriesstring[]for searchPosts, searchProfilesprefill ["apify"]up to 100One search per line["#bluesky", "from:bsky.app lang:en"]
handlesHandlesstring[]for authorFeed, profiles, followers, followingnonehandle, @handle, DID, profile link; up to 1,000 for profiles, 100 otherwiseAccounts to read; a bare name becomes name.bsky.social["bsky.app", "did:plc:z72i7hdynmk6r22z27h6tvur"]
postUrlsPost linksstring[]for thread, postLikers, postRepostersnonebsky.app post links or at:// URIs; up to 100Posts to read["https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l"]
feedUrlsFeed or list linksstring[]for feednoneโ€ฆ/feed/โ€ฆ or โ€ฆ/lists/โ€ฆ links; up to 50Feeds or lists to read["https://bsky.app/profile/bsky.app/feed/whats-hot"]
maxItemsMax results per query, account, post or feedintegerno100, prefill 301โ€“5,000Cap of posts or accounts for each input value; ignored in profiles500
sinceFrom datestringnononeYYYY-MM-DD or ISO date-time, UTCPost modes: only posts created on or after it"2026-10-01"
untilTo datestringnononesame; must be after sincePost modes: only posts created before it"2026-10-08"
searchSortSearch orderstringnolatestlatest, topOrder of post search results"top"
languagePost languagestringnoalllanguage code, e.g. en, pt-BRPost search only"en"
includeRepliesInclude repliesbooleannotrueDrop replies in search and account posts when offfalse
includeRepostsInclude repostsbooleannofalseAccount posts and feeds: also return reposts, marked isReposttrue
threadDepthReply depthintegerno100โ€“100Reply levels below the post; 0 = post and parents only3

Lines pasted with several values are split, and duplicates are removed. A value that cannot be read (e.g. not a handle!) returns an invalid_input row, and the other values are still processed. A wrong field combination, such as followers with no handles or since after until, stops the run at once with a clear message and no rows.

Search operators

Post search accepts Bluesky's own operators inside the query text. Checked live on 2026-10-28:

OperatorExampleFinds
from:from:bsky.appPosts by one account
mentions:mentions:bsky.appPosts that mention an account (plural mentions; mention: finds nothing)
domain:domain:github.comPosts that link to a site
lang:lang:enPosts in a language
#tag#opendataPosts with a hashtag
since:since:2026-10-01Posts from a date
"โ€ฆ""open data"An exact phrase
-wordapify -jobLeave out posts with a word

Operators can be combined: from:bsky.app lang:en #update.

Interacting settings

WhenInput AInput BEffective behavior
Search with a lone hashtag and datesqueries: ["#ai"]since / untilSent to Bluesky as a tag search, which is the only form it accepts with dates
Account posts with dateshandlessinceReading stops once the feed reaches posts older than since
Account posts with strict filtersincludeReplies: false, since / untilโ€”If fewer than 1 in 100 feed items match after 1,000 items, the account is stopped as partial with a warning. Use a search with from:handle instead
Post searchmaxItemsโ€”Search never returns more than 100 posts per query, whatever maxItems says
Account listsfollowers, following, postLikers, postReposters, searchProfilessince, until, language, includeReplies, includeReposts, searchSortPost filters are ignored; maxItems caps the accounts per input

Mode names from other Bluesky scrapers

Other ActorThis Actor
mode: search-postsmode: searchPosts
mode: postsmode: authorFeed
mode: profilesmode: profiles
mode: threads / post-urlsmode: thread
mode: followers / followingmode: followers / following
mode: search-people / search-usersmode: searchProfiles
likers, repostersmode: postLikers, postReposters
sortsearchSort
maxResultsmaxItems

Input examples

Search posts about a topic

{
"mode": "searchPosts",
"queries": ["apify", "#webscraping"],
"maxItems": 50,
"searchSort": "latest",
"language": "en"
}

Expected output: up to 50 post rows per query with text, author, likeCount, links, hashtags.

Followers of an account

{
"mode": "followers",
"handles": ["bsky.app"],
"maxItems": 500
}

Expected output: up to 500 account rows with relation: "follower", handle, displayName, description.

Who liked a post

{
"mode": "postLikers",
"postUrls": ["https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l"],
"maxItems": 200
}

Expected output: up to 200 account rows with relation: "liker" and likedAt.

Recent posts from accounts

{
"mode": "authorFeed",
"handles": ["bsky.app", "@jay.bsky.team"],
"since": "2026-10-01",
"includeReposts": true,
"maxItems": 200
}

Profiles

{
"mode": "profiles",
"handles": ["bsky.app", "jay.bsky.team", "https://bsky.app/profile/pfrazee.com"]
}

A reply thread

{
"mode": "thread",
"postUrls": ["https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l"],
"threadDepth": 3,
"maxItems": 200
}

๐Ÿ‘‰ Try it now: open the input form

Output

Every row goes to the default Dataset and has recordType: post, profile, account or status. The Output tab has Posts, Profiles, Accounts and Not found & errors views. Views choose columns, not rows: status rows appear in every view with empty data columns. To get only one type, filter by recordType in your export or code. The run summary is in the key-value store as RUN_SUMMARY.

Output entities and surfaces

SituationWhereResult shapeWhat to do
Normal resultDataset, Posts / Profiles / Accounts viewspost, profile or account rowOpen, export or read through the API
Nothing foundDataset, Not found & errors viewstatus row with not_foundCheck the handle, link or query
Search stopped at 100 posts, or time ran outRUN_SUMMARY in the key-value storepartial input with a messageNarrow the query (dates, from:) or raise the run timeout
Bluesky error after retriesDataset status row with error, plus RUN_SUMMARYError code and messageRun again later; contact support if it repeats

Post fields

FieldMeaningTypeCan be empty?
uri, cidAT Protocol identifiers of the poststringcid can be null
urlbsky.app link to the poststringrarely null
authordid, handle, displayName, avatar, urlobjectname and avatar can be null
textPost textstringempty for image-only posts
createdAt, indexedAtCreated (by the author's record) and indexed by Bluesky, ISO 8601stringcan be null
langsDeclared languagesstring[]often empty
replyCount, repostCount, likeCount, quoteCount, bookmarkCountCounts at fetch timenumbernull when Bluesky does not report it (never a fake 0)
isReply, replyTo, rootPostReply status, parent and root postboolean, objectobjects are null for top-level posts
isRepost, repostedBy, repostedAtRepost marker in account posts and feedsboolean, object, stringnull when not a repost
isPinnedPinned on the author's profileboolean
isQuote, quotedPostQuoted post: uri, url, authorHandle, textboolean, objectnull when not a quote
embedTypeimages, video, external, record, recordWithMediastringnull without an embed
imagesurl, thumbnailUrl, alt, width, heightobject[]empty without images
videoplaylistUrl, thumbnailUrl, altobjectnull without a video
externalLinkLink card: url, title, descriptionobjectnull without a card
linksAll links in the text and the link cardstring[]
hashtags, mentionsTags and mentioned accounts (did, handle)array
labelsModeration labelsstring[]
threadRole, threadDepthanchor, parent or reply, and level (thread mode)string, numbernull outside thread mode
mode, input, fetchedAt, sourceWhich input produced the row and whenstring

Profile fields

did, handle, displayName, description, url, avatar, banner, followersCount, followsCount, postsCount, listsCount, feedsCount, starterPacksCount, isLabeler, verifiedStatus, pinnedPostUrl, labels, createdAt, indexedAt, plus mode, input, fetchedAt, source.

Account fields

Rows from followers, following, postLikers, postReposters and searchProfiles. Bluesky lists do not carry follower or post counts; run the handles through profiles mode when you need them.

FieldMeaningTypeCan be empty?
relationfollower, following, liker, reposter or search_matchstringno
positionPlace in the list as Bluesky returned it, from 1numberno
did, handleAccount identifiersstringno
displayName, descriptionName and biostringnull when not set
url, avatarProfile link and avatar imagestringavatar can be null
verifiedStatusBluesky verification statusstringnull when not verified
labelsModeration labelsstring[]
createdAt, indexedAtWhen the account was created and indexedstringcan be null
likedAtWhen the like was made (postLikers only)stringnull in other modes
mode, input, fetchedAt, sourceWhich input produced the row and whenstring

Field names from other Bluesky scrapers

Other ActorThis Actor
authorHandle, authorName, authorDid, authorAvatarauthor.handle, author.displayName, author.did, author.avatar
langlangs
mediaUrls, hasMediaimages[].url, video.playlistUrl, embedType
embedUrl, embedTitleexternalLink.url, externalLink.title (also in links)
scrapedAtfetchedAt

Status rows

statusWhenerrorCode examples
not_foundThe account, post, feed or list does not existNOT_FOUND
invalid_inputThe value cannot be read as a handle, link or queryINVALID_INPUT
errorBluesky failed or refused the request after retriesRATE_LIMITED, SOURCE_FORBIDDEN, SOURCE_UNAVAILABLE

A source failure is never shown as "no results": it is an error row and the run status says so.

Run summary (RUN_SUMMARY)

FieldMeaning
statusCOMPLETE, PARTIAL, NO_RESULTS, CAPPED, ERROR, INVALID_INPUT or CANCELLED
errorCodeSet when the whole run failed for one reason, e.g. INVALID_INPUT or RUN_TIMEOUT_TOO_SHORT; otherwise null
messageOne plain-English sentence about the result
countsposts, profiles, accounts, notFound, errors, invalidInputs, duplicatesSkipped, chargedPosts, chargedProfiles, chargedAccounts, chargedEmptyResults, unprocessedInputs
inputsOne entry per input value (input, status, items, message): complete, partial, no_results, not_found, error, invalid_input or skipped, with the row count
warningsPlain-English notes, e.g. the 100-per-query search limit

Output example

A fictional post, trimmed to the main fields:

{
"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)"
}

A fictional account row from followers mode:

{
"recordType": "account",
"status": "complete",
"mode": "followers",
"input": "author.example",
"relation": "follower",
"position": 1,
"did": "did:plc:cccccccccccccccccccccccc",
"handle": "reader.example",
"displayName": "Sample Reader",
"description": "Data journalist. Open data, maps, charts.",
"url": "https://bsky.app/profile/reader.example",
"verifiedStatus": null,
"createdAt": "2024-11-20T08:00:00.000Z",
"likedAt": null,
"fetchedAt": "2026-10-28T10:00:00.000Z",
"source": "Bluesky AppView API (AT Protocol)"
}

null means Bluesky did not provide the value; it never stands for zero.

๐Ÿ‘‰ Try it now: open the input form

Pricing

Pricing unit

You pay per event. Platform usage (compute, storage) is included in the price.

EventFreeBronzeSilverGoldPlatinumDiamond
Post row$0.0009$0.00085$0.0007$0.00055$0.00055$0.00055
Profile row$0.0027$0.0026$0.0021$0.0016$0.0016$0.0016
Account row$0.0009$0.0009$0.0007$0.00055$0.00055$0.00055
Empty result$0.0005$0.0005$0.0005$0.0005$0.0005$0.0005
Actor start$0.0045$0.0045$0.0045$0.0045$0.0045$0.0045
  • Post, profile or account row: one per data row written.
  • Empty result: one per input value that produced no data: a search with no matches, an account with no matching posts or no followers, 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_RESULTS with 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: error rows (Bluesky failed), skipped inputs, duplicate rows and duplicate input values (the same account, post or feed written another way, e.g. @Name.bsky.social and its profile link).

A run whose input is rejected before any request (e.g. profiles without handles) costs only the start fee.

Cost examples (Free plan prices)

ScenarioInputCharged eventsCost
Prefill search1 query, 30 postsstart + 30 postsabout $0.03
1,000 posts from accounts2 accounts ร— 500start + 1,000 postsabout $0.90
1,000 followers1 account, maxItems: 1000start + 1,000 accountsabout $0.90
100 profiles100 handlesstart + 100 profilesabout $0.27
Search with no matches1 querystart + 1 empty result$0.005

Spending limit

Set Maximum cost per run in the run options; the smallest allowed value is $0.0072, enough for the start and one row of any type. The Actor checks the limit before every row; when the next row would exceed it, the run stops with status CAPPED and keeps everything written so far.

๐Ÿ‘‰ Try it now: open the input form

Limits

  • No private data: no DMs, no muted or blocked lists, nothing that needs login. DMs will never be supported.
  • Post search: up to 100 posts per query, because Bluesky serves further search pages only to logged-in clients. The Actor does not try to get around this. Split the topic into narrower queries or date ranges to see more.
  • People search: Bluesky returns only the best matches for a name or keyword, often fewer than maxItems.
  • Follower, following, liker and reposter lists: up to 5,000 accounts per input value (maxItems), read in pages of 100. An input that reaches maxItems while Bluesky has more is complete, and its message in RUN_SUMMARY says that the list goes on.
  • 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 use the same sparse stop.
  • Speed at 256 MB (tests on 2026-10-28): one account's 1,075 posts in about 7 s (build 0.1.9); 10,000 posts from two accounts (5,000 each) in about 1.5 minutes; 5,000 followers in about 17 s. Large jobs need a longer run timeout than the default 300 s; an input cut by the timeout is marked partial with its row count.
  • since / until compare the post's createdAt (for reposts, repostedAt). Account feeds are ordered by when Bluesky indexed the post, so posts imported later with an older date can be outside the scanned range.
  • Counts (likes, reposts, followers) are a snapshot at fetch time.

๐Ÿ‘‰ Try it now: open the input form

API, export and integrations

  • API: start a run with POST https://api.apify.com/v2/acts/cliqtomedia~bluesky-scraper/runs and the JSON input above; read rows from the run's default Dataset.
  • Export: the Dataset downloads as JSON, CSV, Excel, XML or RSS from the Output tab or the API.
  • Scheduling: use Apify Schedules, e.g. a daily search with since set to yesterday.
  • Webhooks / no-code: Make, Zapier, n8n, Google Sheets and webhooks connect through the Apify integrations tab; the next system reads url, text or handle from each row.

Use with AI agents (MCP)

  • MCP server with only this Actor: https://mcp.apify.com?tools=cliqtomedia/bluesky-scraper.
  • Smallest useful input: {"mode": "searchPosts", "queries": ["apify"], "maxItems": 30}.
  • Key fields: text (post text), author.handle (who posted), createdAt (when), likeCount (engagement), url (link to cite). Account rows add relation and handle.
  • A small search finishes in a few seconds and costs about $0.03. At most 100 posts per search query; not_found and error rows explain missing data.

๐Ÿ‘‰ Try it now: open the input form

Troubleshooting

Search stops at 100 posts

Symptom: you set maxItems above 100, a query returns 100 posts, its entry in inputs is partial and there is a warning about 100 posts per query. With maxItems of 100 or less the query is complete. Reason: Bluesky serves only the first search page without login. Action: add operators (lang:en, from:, domain:) or a narrower since / until.

An account returns no posts or no followers

Check the account's entry in inputs in RUN_SUMMARY. not_found means the handle does not exist. no_results means the filters matched nothing or the list is empty; partial with the sparse-scan warning means the filters matched fewer than 1 in 100 items. Loosen since / until, turn on replies or reposts, or use search with from:handle.

Followers stop before the full list

Symptom: the input's message in RUN_SUMMARY says "Stopped at maxItems". Action: raise maxItems (up to 5,000) and the run timeout.

The run ends as PARTIAL with skipped inputs

The run hit its timeout. Raise the timeout in the run options or split the input into smaller runs.

The run fails with RUN_TIMEOUT_TOO_SHORT

The run timeout leaves no time to fetch data and save the summary. Set the timeout to at least 60 seconds; the default 300 seconds is enough for small runs. Nothing is charged except the start.

Rows with error status

Bluesky failed or refused the request after retries (RATE_LIMITED, SOURCE_FORBIDDEN, SOURCE_UNAVAILABLE). These rows are free; run the failed values again later.

๐Ÿ‘‰ Try it now: open the input form

FAQ

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.

Can I export the followers of a Bluesky account?

Yes. Use followers mode with the handle and set maxItems up to 5,000 per account. following mode returns the accounts it follows.

Why do account rows have no follower counts?

Bluesky's follower and like lists return only basic account data. Put the handles into profiles mode to get followersCount, postsCount and the rest.

Why is search limited to 100 posts per query?

Bluesky serves further search pages only to logged-in clients. The Actor respects that and tells you in the run summary when a query hit the limit.

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.

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. Sources: the Bluesky API directory, the search API reference and the Bluesky Terms of Service.

๐Ÿ‘‰ Try it now: open the input form

๐Ÿ‘‰ Try it now: open the input form

Support

Use the Issues tab of this Actor for bugs, questions and feature requests. Include the run ID, the mode and the error text. Never post tokens, cookies or private data.

๐Ÿ‘‰ Try it now: open the input form