Bluesky Profile Scraper
Pricing
from $1.05 / 1,000 results
Bluesky Profile Scraper
Collect Bluesky profiles, posts, followers, follows and lists, or search for accounts by keyword. Get handles, DIDs, bios, profile counts, post text and engagement metrics in structured rows. Uses the public Bluesky API without login or a browser, with cursor-based pagination.
Pricing
from $1.05 / 1,000 results
Rating
0.0
(0)
Developer
Axiom Works
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Returns: one row per Bluesky profile, post, follower, follow or list with id, recordType, did, handle, displayName, createdAt, sourceUrl; profile counts and post text depend on the mode.
Input: {"mode": "profiles", "handles": ["bsky.app", "jay.bsky.team"], "maxItems": 10}
Best for: lead lists of Bluesky accounts, follower and audience analysis, social listening on an account's posts.
{"id": "did:plc:z72i7hdynmk6r22z27h6tvur","recordType": "profile","handle": "bsky.app","displayName": "Bluesky","followersCount": 35250704,"followsCount": 19,"postsCount": 870,"sourceUrl": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles?actors=bsky.app&actors=jay.bsky.team&actors=atproto.com"}
What does Bluesky Profile Scraper do?
Bluesky Profile Scraper is a Bluesky scraper and Bluesky API wrapper in one Actor. It reads the open, keyless public Bluesky API and returns clean rows you can export as JSON, CSV or Excel. You give it handles, DIDs or profile links, choose a mode, and it collects profile details, an account's posts, its followers, the accounts it follows, its lists, or the results of a keyword search for accounts.
There is no login and no browser involved, so runs are fast and cheap. The Actor sends about two to three requests per second, follows the cursor pagination the API provides, and retries politely when the API asks it to slow down. Every row carries the sourceUrl of the API call it came from and a stable id, so you can de-duplicate across runs.
Typical jobs are building lead lists of accounts in a niche, measuring how big an account is and who follows it, tracking what a brand or creator posts and how many likes and reposts it gets, and mapping which accounts an organisation follows.
What data can you get from Bluesky?
Each row has a recordType that tells you which fields to expect. Fields that do not apply to a type are empty.
| Field | Description | Example |
|---|---|---|
recordType | profile, post, follower, follow or list | profile |
id | Stable unique ID of the row | did:plc:z72i7hdynmk6r22z27h6tvur |
did | Decentralized ID of the account (the author for posts, the creator for lists) | did:plc:z72i7hdynmk6r22z27h6tvur |
handle | Account handle | bsky.app |
displayName | Public display name | Bluesky |
description | Profile bio, or list description | official Bluesky account |
avatar | Avatar image URL | https://cdn.bsky.app/img/avatar/... |
banner | Banner image URL (profiles mode) | https://cdn.bsky.app/img/banner/... |
pinnedPostUri | AT URI of the pinned post (profiles mode) | at://did:plc:.../app.bsky.feed.post/3l6oveex3ii2l |
labels | Moderation label values on the account (profiles mode) | [] |
listsCount | Lists the account created (profiles mode) | 18 |
feedGeneratorsCount | Custom feeds the account created (profiles mode) | 7 |
starterPacksCount | Starter packs the account created (profiles mode) | 15 |
isLabeler | Whether the account is a moderation labeler (profiles mode) | false |
profileUrl | Public bsky.app profile page (profiles mode) | https://bsky.app/profile/bsky.app |
createdAt | Account creation time, or post creation time | 2023-04-12T04:53:57.057Z |
verified | Verification status, null when absent from the response | none |
followersCount | Number of followers (profiles mode) | 35133356 |
followsCount | Number of accounts followed (profiles mode) | 15 |
postsCount | Number of posts (profiles mode) | 865 |
subjectDid | Account whose followers or follows were listed | did:plc:z72i7hdynmk6r22z27h6tvur |
subjectHandle | Handle of that account | bsky.app |
searchQuery | Query that matched the profile (searchProfiles mode) | journalist |
uri | at:// URI of the post or list | at://did:plc:.../app.bsky.feed.post/3mwolmfws5k2r |
cid | Content ID of the post | bafyrei... |
text | Post text | Happy opening day of hockey season |
likeCount | Likes on the post | 952 |
repostCount | Reposts of the post | 135 |
replyCount | Replies to the post | 70 |
quoteCount | Quote posts | 16 |
langs | Language codes declared on the post | ["en"] |
indexedAt | Post or list indexing time; list createdAt retains its existing indexing-time value | 2025-08-06T03:43:31.609Z |
trustedVerifierStatus | Account trusted verifier status, null when unavailable | valid |
images | Post image URLs and original alternative text, including media attached to quotes | [{"fullsize":"https://cdn.bsky.app/...","thumb":"https://cdn.bsky.app/...","alt":"A blue sky"}] |
videoPlaylist | Post video playback playlist URL | https://video.bsky.app/.../playlist.m3u8 |
externalUrl | Post link card destination URL | https://bsky.app |
quotedPostUri | Available quoted post AT URI | at://did:plc:.../app.bsky.feed.post/... |
hasMedia | True when the post has an embed such as an image, video, link card or quote | true |
isRepost | True when the feed entry is a repost by the target account | false |
repostedAt | When the account reposted it (reposts only) | 2026-09-30T13:30:00.000Z |
postUrl | bsky.app link to the post | https://bsky.app/profile/.../post/3mwolmfws5k2r |
name | List name (lists mode) | Hockey writers |
purpose | List purpose, curation or moderation | app.bsky.graph.defs#curatelist |
listItemCount | Number of accounts on the list | 120 |
accountType | Optional AI classification: person, brand, media, bot or other | brand |
sourceUrl | API endpoint the row came from | https://public.api.bsky.app/xrpc/... |
Follower and follow rows come from list endpoints that do not include follower, follow or post counts. Use profiles mode when you need those numbers. Search rows are also list items and do not include the counts either.
How to use Bluesky Profile Scraper
- Open the Actor in Apify Console and pick a Mode.
- Add one or more Handles. You can paste a handle such as
bsky.app, a DID, or a fullbsky.app/profile/...link. In searchProfiles mode, fill Search query instead. - Set Maximum results. This is the total across all handles, and it is the most important setting for followers and posts of large accounts.
- Click Start. When the run finishes, open the Output tab and export the dataset as JSON, CSV, Excel, XML or RSS.
Some tips. The profiles mode takes up to 25 handles per API call, so checking hundreds of accounts is quick. In posts mode, the default filter returns posts and reposts but not replies. The API only exposes a limited depth of an account's feed, so a very prolific account may return fewer posts than it has published. Follower lists of very large accounts run into the tens of millions of rows, so always set a sensible limit.
Input
For API and MCP calls, prefills are examples rather than automatic targets. Supply handles for account modes, or searchQuery with mode: "searchProfiles". The default mode is profiles; maxItems is one shared budget across every target, not a separate allowance for each handle. Three profile targets can therefore return three rows even when maxItems is 10. Search ignores handles, and postFilter affects only posts.
An empty handle list returns zero rows. Unknown accounts are skipped so another supplied account can still return data. If no rows are found, the final status identifies what to change: supply existing handles or profile URLs, broaden searchQuery, try another account or mode for an empty feed, or retry later and configure Apify Proxy for persistent blocks. A missing searchQuery in searchProfiles mode produces a validation message.
| Field | Type | Required | Description |
|---|---|---|---|
handles | array of strings | Yes (except searchProfiles) | Handles, DIDs, AT profile URIs or bsky.app profile URLs, e.g. @bsky.app, one per line. |
mode | string | No | profiles, posts, followers, follows, lists or searchProfiles. Default profiles. |
searchQuery | string | Only for searchProfiles | Keyword to find accounts by name, handle or bio. |
postFilter | string | No | posts_no_replies, posts_with_replies or posts_with_media. Default posts_no_replies. |
maxItems | integer | No | Total rows to return, 1-10000, default 50. |
enrichAccountType | boolean | No | Add an AI account type classification. Default false. |
proxyConfiguration | object | No | Proxy settings. Default: no proxy, because the public API is open. |
{"mode": "followers","handles": ["bsky.app"],"maxItems": 120,"enrichAccountType": false,"proxyConfiguration": { "useApifyProxy": false }}
List records also include the creatorβs displayName when available. Post embed fields are nullable: a text-only post has no image, video, external link, or quoted-post value. Image alternative text is copied verbatim and can be an empty string. An unavailable or blocked quoted post has no quotedPostUri.
Output
Every row is a flat JSON object. Here are real rows from a run: a profile and a post.
[{"id": "did:plc:oky5czdrnfjpqslsw2a5iclo","recordType": "profile","did": "did:plc:oky5czdrnfjpqslsw2a5iclo","handle": "jay.bsky.team","displayName": "Jay π¦","description": "Founder & Chief Innovation Officer @ Bluesky\n\nWorking on @attie.ai\n\nπ± πͺ΄ π³","createdAt": "2022-11-17T06:31:40.296Z","verified": "valid","sourceUrl": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles?actors=bsky.app&actors=jay.bsky.team&actors=atproto.com"},{"id": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mwolmfws5k2r","recordType": "post","handle": "bsky.app","text": "Happy opening day of hockey season, NHL fans!","createdAt": "2026-09-29T19:43:14.855Z","likeCount": 952,"repostCount": 135,"replyCount": 70,"isRepost": false}]
Results can be downloaded in JSON, CSV, Excel, XML or RSS from the dataset, or read through the API.
How much does it cost to scrape Bluesky?
Bluesky Profile Scraper is charged per result returned. You pay only for the rows saved, and maxItems caps how many that can be. When you turn on the optional AI account classification, an additional charge applies only for rows where the classification succeeded. See the Pricing tab for the current prices.
Use with AI agents (MCP)
Connect Bluesky Profile Scraper to Claude, Cursor or any MCP client through the Apify MCP server:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=axiomworks/bluesky-profile-scraper"}}}
Example prompts:
- "Get the follower count and bio of the Bluesky accounts bsky.app and atproto.com."
- "Fetch the latest 50 posts from jay.bsky.team on Bluesky with their like and repost counts."
- "Find Bluesky accounts matching 'data journalist' and list their handles and bios."
Integrations and API
Run Bluesky Profile Scraper from your own code with the Apify API. Set APIFY_TOKEN to your Apify API token (send it in the Authorization header, never in the URL).
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_API_TOKEN>")run = client.actor("axiomworks/bluesky-profile-scraper").call(run_input={"mode": "profiles", "handles": ["bsky.app"], "maxItems": 10})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item)
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });const run = await client.actor('axiomworks/bluesky-profile-scraper').call({ mode: 'posts', handles: ['bsky.app'], maxItems: 60 });const { items } = await client.dataset(run.defaultDatasetId).listItems();
cURL
curl -X POST "https://api.apify.com/v2/acts/axiomworks~bluesky-profile-scraper/run-sync-get-dataset-items" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"mode": "profiles", "handles": ["bsky.app"], "maxItems": 10}'
The Integrations tab connects the dataset to Make, Zapier, n8n, Google Sheets and webhooks, so you can schedule runs and send results on.
FAQ
Do I need a Bluesky account or API key? No. The Actor reads the open public API and needs no login.
Why did a run return 0 results? The handle may not exist, may be deactivated, or the account may have no data of the requested type. Check the spelling and try the profiles mode first. The log lists handles that were not found.
Why are some follower rows missing counts?
The follower, follow and search endpoints return a short profile view without counts. Run profiles mode on the handles you care about to get followersCount, followsCount and postsCount.
Are reposts included in posts mode?
Yes, with the default filter. They have isRepost set to true, and the handle and did fields are those of the original author, not the account you scraped.
Can I search post text by keyword? No. Keyword search of posts is not available on the public API. Account search by keyword is available through searchProfiles mode.
How does the AI account classification work?
When enabled, the bio and display name are sent to a third-party AI processor (Jev by TypeSafe), which picks one of person, brand, media, bot or other. If the processor fails, the row is still returned without accountType and no extra charge applies.
How fresh is the data? Every run reads live from the API, so counts and posts are current at run time.
Can I get the data on a schedule?
Yes. Use Apify schedules and compare runs by id.
Is it legal to scrape Bluesky?
The Actor collects only publicly available data from the open Bluesky API, with no login and no private content. Profile data can be personal data, so you are responsible for using it lawfully, for example under GDPR, and for respecting the people behind the accounts. This is not legal advice. This Actor is an independent tool and is not affiliated with or endorsed by Bluesky.
Feedback
Found a bug or need a field that isn't here? Open an issue on the Issues tab and we'll look at it.