Bluesky Scraper — Posts, Profiles, Feeds & Threads, No Login avatar

Bluesky Scraper — Posts, Profiles, Feeds & Threads, No Login

Pricing

from $0.55 / 1,000 posts

Go to Apify Store
Bluesky Scraper — Posts, Profiles, Feeds & Threads, No Login

Bluesky Scraper — Posts, Profiles, Feeds & Threads, No Login

Scrape Bluesky without login: search posts, account posts, profiles with follower counts, full reply threads and custom feeds. Text, likes, reposts, hashtags, mentions, links and image alt text in 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

3

Total users

2

Monthly active users

5 days ago

Last modified

Categories

Share

Scrape public Bluesky posts, profiles, custom feeds, lists and reply threads with no login, and get clean JSON rows with text, engagement counts, links, mentions, hashtags, image alt text, quoted posts and author details.

Use it for social listening, research datasets, brand monitoring 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 (100 posts cost about $0.09 on the Free plan, less on paid plans).

Bluesky Scraper: search queries, handles, post links and feed links in; full post data and clean rows out. No login.

Table of contents

What it does

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

ModeYou giveYou get
searchPostsKeywords, #hashtags, phrases, operators like from:bsky.app, lang:en, domain:example.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

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.
  • Collect the posts and profiles of a list of accounts for research or lead lists.
  • Pull a full reply thread for sentiment or discussion analysis.
  • Feed clean, typed Bluesky rows into an AI agent, a spreadsheet or a database.

Not a fit / limits

  • No private data: no DMs, no muted or blocked lists, nothing that needs login.
  • No followers or following lists in this version.
  • Search returns at most 100 posts per query, because Bluesky blocks further search pages without login. The Actor does not try to get around this.
  • Counts (likes, reposts, followers) are a snapshot at fetch time.

Quick start

  1. Open the Input tab.
  2. Keep What to scrape on Search posts and add a word to Search queries, e.g. apify.
  3. Keep Max posts per query at the prefilled 30 (the runtime default is 100).
  4. Click Start.
  5. Open the Posts view in the Output tab and check text, author.handle and likeCount.

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

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
Account researchmode: authorFeed, handles, sinceposttext, isRepost, replyCount, externalLinkCompare accounts, build datasets
Influencer or lead listmode: profiles, handlesprofiledisplayName, description, followersCount, postsCountCRM import
Discussion analysismode: thread, postUrlspostthreadRole, threadDepth, text, replyToSentiment or topic analysis
Curated topic monitoringmode: feed, feedUrlsposttext, hashtags, images[].altAI summarisation

Input

Input fields

FieldPlain nameTypeRequiredDefault / prefillAllowed valuesEffectExample
modeWhat to scrapestringyessearchPostssearchPosts, authorFeed, profiles, thread, feedPicks the list that is read"authorFeed"
queriesSearch queriesstring[]for searchPostsprefill ["apify"]up to 100One search per line["#bluesky", "from:bsky.app lang:en"]
handlesHandlesstring[]for authorFeed, profilesnonehandle, @handle, DID, profile link; up to 1,000 (100 for authorFeed)Accounts to read; a bare name becomes name.bsky.social["bsky.app", "did:plc:z72i7hdynmk6r22z27h6tvur"]
postUrlsPost linksstring[]for threadnonebsky.app post links or at:// URIs; up to 100Threads 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 posts per inputintegerno100, prefill 301–5,000Cap for each query, account, thread or feed; ignored in profiles500
sinceFrom datestringnononeYYYY-MM-DD or ISO date-time, UTCOnly posts created on or after it"2026-10-01"
untilTo datestringnononesame; must be after sinceOnly posts created before it"2026-10-08"
searchSortSearch orderstringnolatestlatest, topOrder of search results"top"
languagePost languagestringnoalllanguage code, e.g. en, pt-BRSearch 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 profiles with no handles or since after until, stops the run at once with a clear message and no rows.

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
Any modemaxItemssearchSearch never returns more than 100 posts per query, whatever maxItems says

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.

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
}

Output

Every row goes to the default Dataset and has recordType: post, profile or status. The Output tab has Posts, Profiles and Not found & errors views. Views choose columns, not rows: in a posts run the Profiles view shows the same rows with empty profile columns, and status rows appear in the Posts view with empty post 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.

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.

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, notFound, errors, invalidInputs, duplicatesSkipped, chargedPosts, chargedProfiles, 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)"
}

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

Pricing and limits

Pricing unit

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

EventFree planStarter (Bronze)Scale (Silver)Business (Gold) and higher
Post row$0.0009$0.00085$0.0007$0.00055
Profile row$0.0027$0.0026$0.0021$0.0016
Empty result$0.0005$0.0005$0.0005$0.0005
Actor start$0.001$0.001$0.001$0.001
  • Post row / profile row: one per data row written.
  • Empty result: one per input value that produced no data: a search query with no matches, an account with no matching posts, 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 posts 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.028
1,000 posts from accounts2 accounts × 500start + 1,000 postsabout $0.90
100 profiles100 handlesstart + 100 profilesabout $0.27
Search with no matches1 querystart + 1 empty result$0.0015

Spending limit

Set Maximum cost per run in the run options. 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.

Limits

  • Search: up to 100 posts per query (Bluesky's limit without login). Split the topic into narrower queries or date ranges to see more.
  • 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: the same sparse stop applies.
  • Speed at 256 MB: one account's 1,030 posts in about 5 s; 10,000 account posts took about 2–3 minutes in tests. 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.

Switching from another Bluesky scraper

Field names differ from other Bluesky Actors. Typical mapping:

Other ActorThis Actor
mode: search-postsmode: searchPosts
mode: postsmode: authorFeed
mode: profilesmode: profiles
mode: threads / post-urlsmode: thread
sortsearchSort
maxResultsmaxItems
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

Extra here: image alt text, video, quoted post, repost details, thread roles, custom feeds and lists, and not_found rows.

API 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.
  • Integrations: Make, Zapier, n8n, Google Sheets and webhooks connect through the Apify integrations tab.

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).
  • 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.

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

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; 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.

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.

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.

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.

Can I scrape followers or DMs?

Not in this version. DMs are private and will never be supported.

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.

Learn more

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.