# Bluesky Scraper - Posts, Profiles, Followers (`cirkit/bluesky-scraper`) Actor

Export Bluesky posts, profiles, followers and following lists from any public account: post text, likes, reposts, replies, quotes, images, video, link cards, hashtags, mentions, follower counts and verification. Find accounts by keyword. No login needed.

- **URL**: https://apify.com/cirkit/bluesky-scraper.md
- **Developed by:** [Crikit](https://apify.com/cirkit) (community)
- **Categories:** Social media, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.70 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### What is Bluesky Scraper?

**Bluesky Scraper** lets you export posts, profiles, followers and following lists from any public Bluesky account, helping you track what accounts are posting, measure how their audience reacts, and build lists of the people around them. No login, no app password, no session cookie.

- 🔍 **Track any account's timeline:** pull a full post history newest first, with likes, reposts, replies, quotes and bookmarks on every row, plus the permalink to the post itself.
- 📈 **Measure real engagement, not vanity counts:** every row carries the author's follower count and an engagement-per-follower figure, so a 40-like post from a 500-follower account stops looking smaller than a 40-like post from a 500,000-follower account.
- 🧑‍🤝‍🧑 **Export a social graph:** get the full followers or following list of an account, one row per person, with handle, display name, bio and avatar.
- 🔎 **Find accounts you do not know yet:** search Bluesky by keyword and get matching accounts back with their follower, following and post counts already filled in.
- 🖼 **Keep the media:** image URLs with alt text, video playlist URLs, link-card titles and descriptions, quoted posts, mentions, hashtags and outbound links, all as ordinary columns.
- 🗓 **Narrow before you pay:** cap the run, cap it per account, restrict to a date range, drop reposts, or take only posts that carry media.

Bluesky's own app shows you one profile at a time and gives you nothing to export. **Bluesky Scraper** runs many accounts in parallel, follows the cursor to the end of the list instead of stopping at the first page, and hands you a clean table or CSV. It is priced at **$0.70 per 1,000 results with no per-run start fee**, which is the cheapest Bluesky scraper on the Apify Store.

### What data does Bluesky Scraper extract?

Every row, whatever its type:

- 🆔 Account DID and handle (`did`, `handle`)
- 🏷 Display name and bio (`displayName`, `description`)
- 👥 Follower count, following count and post count (`followersCount`, `followsCount`, `postsCount`)
- ✅ Verification status (`isVerified`, `verifiedStatus`)
- 🖼 Avatar and banner image URLs
- 📅 Account creation date (`accountCreatedAt`)
- 📋 Lists, custom feeds and starter packs the account publishes
- 🔗 Profile URL, ready to click
- 🚩 Moderation labels on the account (`accountLabels`)
- ⏱ `scrapedAt` timestamp on every row

For each post:

- 📝 Full post text and its length
- 🔗 Post permalink and the AT-URI (`postUrl`, `postUri`, `postCid`, `postRkey`)
- ❤️ Likes, 🔁 reposts, 💬 replies, 💭 quotes and 🔖 bookmarks
- 📊 Total engagement and engagement per follower (`totalEngagement`, `engagementRate`)
- 🕐 Publish time and index time (`postedAt`, `postIndexedAt`)
- 🔁 Repost flag and who reposted it (`isRepost`, `repostedByHandle`)
- 💬 Reply flag, the parent post and the thread root (`isReply`, `replyToPostUrl`, `threadRootUri`)
- 💭 Quote flag, the quoted post's link, author and text
- 📸 Image URLs with alt text (`images`, `imageCount`)
- 🎬 Video playlist and thumbnail URLs
- 🃏 Link card URL, title, description and thumbnail
- \#️⃣ Hashtags, @mentions as handles and as DIDs, and outbound links
- 🌍 Post language codes (`languages`)

For each follower or following entry:

- 👤 The person's DID, handle, display name, bio and avatar
- ↔️ Which account they were pulled from and in which direction (`relatedToHandle`, `relationship`)

### How to use Bluesky Scraper

1. Click **Try for free** and open the input form.
2. Paste one or more accounts into **Bluesky profiles**. A handle (`nytimes.com`), an @handle, a DID, or a profile link all work. No list of handles yet? Type a keyword into **Keywords to find accounts** instead.
3. Choose **What to scrape**: posts, profiles, followers or following.
4. Set **Max results**. You are charged per row, so this is your budget control.
5. Click **Start** and export the results as CSV, Excel, JSON or XML, or pull them from the Apify API.

### Input

```json
{
  "profiles": ["nytimes.com", "@bsky.app", "https://bsky.app/profile/pfrazee.com"],
  "resultsType": "posts",
  "maxItems": 1000,
  "maxItemsPerProfile": 200,
  "postFilter": "posts_with_replies",
  "includeReposts": true,
  "fromDate": "2026-01-01",
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Keyword discovery instead of a handle list:

```json
{
  "searchTerms": ["climate scientist", "urban planning"],
  "resultsType": "profiles",
  "maxProfilesPerKeyword": 50,
  "maxItems": 100
}
```

| Field | What it does |
|---|---|
| `profiles` | Accounts to scrape. Handle, @handle, DID or profile URL. A post URL resolves to its author. |
| `searchTerms` | Keywords. Bluesky matches them against handles, display names and bios. |
| `resultsType` | `posts`, `profiles`, `followers` or `following`. |
| `maxItems` | Total row cap across every account. `0` means no limit. |
| `maxItemsPerProfile` | Per-account cap, so one busy account cannot use the whole budget. |
| `postFilter` | `posts_with_replies`, `posts_no_replies`, `posts_with_media` or `posts_and_author_threads`. |
| `includeReposts` | Off drops reposts, so you only pay for original content. |
| `fromDate`, `toDate` | Date window on the post's publish time, `YYYY-MM-DD`. |
| `maxProfilesPerKeyword` | How many accounts each keyword may contribute. Defaults to 20. |

### Output

One row per result. Trimmed to the interesting fields:

```json
{
  "type": "post",
  "handle": "nytimes.com",
  "displayName": "The New York Times",
  "followersCount": 1344713,
  "postsCount": 42326,
  "isVerified": true,
  "postUrl": "https://bsky.app/profile/nytimes.com/post/3mumkqrekwj2e",
  "text": "Breaking News: The Trump administration released new rules that could revoke tax-exempt status from schools ...",
  "postedAt": "2026-09-03T13:32:04.498Z",
  "likeCount": 9,
  "repostCount": 3,
  "replyCount": 0,
  "quoteCount": 1,
  "bookmarkCount": 0,
  "totalEngagement": 13,
  "engagementRate": 0.00001,
  "isRepost": false,
  "isReply": false,
  "isQuote": false,
  "embedType": "link",
  "externalUrl": "https://nyti.ms/4zUnBas",
  "externalTitle": "Trump Administration Renews Push to Strip Schools of Tax-Exempt Status",
  "links": [],
  "mentionHandles": ["theathleticfc.bsky.social"],
  "hashtags": [],
  "profileUrl": "https://bsky.app/profile/nytimes.com",
  "scrapedAt": "2026-09-03T13:32:11.004Z",
  "...": "and 45 more columns"
}
```

The dataset ships with three ready-made table views, so you are not handed a wall of JSON:

- **Overview:** handle, name, followers, publish time, post text, likes, reposts, replies, link.
- **Profiles:** handle, name, followers, following, posts, verified, bio, join date, profile link.
- **Engagement:** handle, followers, likes, reposts, replies, quotes, total engagement, engagement per follower, media type, link.

### How much does it cost to scrape Bluesky?

**$0.0007 per result, which is $0.70 per 1,000 rows.** There is no start fee and no monthly minimum, so a run that returns 12 rows costs less than a cent.

| Results | Cost |
|---|---|
| 100 | $0.07 |
| 1,000 | $0.70 |
| 10,000 | $7.00 |
| 100,000 | $70.00 |

Every other Bluesky scraper on the Apify Store charges between **$1.00 and $8.00 per 1,000 results**, and most add a per-run start fee of $0.005 to $0.09 on top. This one is a single flat per-row price, and Apify's own volume discounts apply to it automatically on paid plans.

### Measured coverage

From the QA runs behind version 0.1: **516 post rows, 373 follower and following rows and 11 profile rows locally**, plus **2,451 rows across five runs on the Apify platform itself**, covering news accounts, personal accounts, keyword searches, accounts with zero posts and handles that do not exist. Every platform row carried all 69 columns, no duplicates and no missing timestamps.

| Field | Post rows | Notes |
|---|---|---|
| `postUrl`, `postUri`, `postedAt` | 100% | Every row is addressable and timestamped. |
| Likes, reposts, replies, quotes, bookmarks | 100% | Numbers, including a real `0`, never null. |
| `handle`, `displayName`, `avatarUrl` | 100% | |
| `followersCount`, `postsCount` | 100% | Includes reposts of other people's posts, whose authors are resolved in a second lookup. |
| `engagementRate` | 100% | |
| `isVerified` | 100% | A true or false, never null. |
| `text` | 99% | The 1% are image-only or video-only posts with no caption. |
| `languages` | 39% | Bluesky's clients only tag some posts. |
| `links` | 24% | Present when the post body contains a link. |
| `mentionHandles` | 14% | Present when the post mentions somebody. |

| Field | Follower and following rows |
|---|---|
| `did`, `handle`, `relatedToHandle` | 100% |
| `avatarUrl` | 98% |
| `displayName` | 64% (many accounts never set one) |
| `description` | 57% (many accounts have no bio) |

Embed types seen across those 516 posts: 290 link cards, 135 with no embed, 63 image posts, 18 videos, 10 quote posts including quote-plus-media combinations. All of them parsed into columns.

Speed, measured on the Apify platform rather than on a laptop: **100 posts in 6 seconds end to end, 300 posts across 3 accounts in 5 seconds, 2,000 followers in 35 seconds.** A run that lands on a slow proxy exit can take several times longer for the same rows, which is why the cap is on results and not on time.

### Limits

Worth knowing before you buy:

- **Keyword search covers accounts, not posts.** Bluesky's public API stopped serving post search to unauthenticated callers, and this Actor does not ask you for a password or a session token, so it does not offer keyword post search. Any scraper that promises it is either logging in as somebody or is broken. What you can do is find accounts by keyword and then export their posts, which this Actor does in a single run.
- **Followers and following are returned newest first** and Bluesky offers no way to sort or filter them, so a 1.3 million-follower account has to be walked in order.
- **Hashtags are parsed but rare.** None of the 516 QA rows carried one, because hashtag use on Bluesky is uncommon. The column is tested against the protocol's own facet shape.
- **Display names and bios are genuinely missing** on roughly a third of follower rows. That is the account's own choice, not a gap in extraction.
- **Deactivated and deleted accounts return no data.** The run still ends successfully and writes one row explaining which account and why, so a scheduled job can tell "nothing there" apart from "something broke".
- **Private data is not available at all.** Bluesky has no private posts, no private follower lists and no direct-message API on the public AppView. Nothing here is scraped from behind a login.

### FAQ

#### Is it legal to scrape Bluesky?

Bluesky Scraper reads only public data, through Bluesky's own public AT Protocol AppView, the same interface the bsky.app website uses when you are not logged in. It sends no credentials and it accesses nothing that requires a login. Scraping public data is generally lawful in the US and the EU, but posts are written by people, so if you collect, store or publish them you are responsible for your own compliance with the GDPR, the CCPA and Bluesky's terms. If you are unsure, talk to a lawyer, and read Apify's [ethical web scraping](https://blog.apify.com/what-is-ethical-web-scraping-and-how-do-you-do-it/) guide.

#### Can I use Bluesky Scraper as an API?

Yes. Every Actor on Apify is an API endpoint. Start a run, poll it, and read the dataset with the [Apify API](https://docs.apify.com/api/v2), or use the JavaScript, Python or CLI clients.

#### Can I use this in Python?

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("cirkit/bluesky-scraper").call(run_input={
    "profiles": ["nytimes.com"],
    "resultsType": "posts",
    "maxItems": 500,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["postedAt"], item["likeCount"], item["text"])
```

#### How do I schedule Bluesky Scraper to run automatically?

Open the Actor, go to **Schedules**, and add a cron schedule: hourly, daily, weekly, whatever the account's posting rhythm needs. Every run writes to its own dataset, and a `fromDate` keeps each run to the window you care about.

#### Can I integrate Bluesky Scraper with other apps?

Yes. Apify connects to Zapier, Make, n8n, Slack, Google Sheets, Google Drive, GitHub and Airtable, and can push finished runs to any HTTP endpoint through webhooks.

#### Why did I get fewer results than expected?

The usual reasons, in order of likelihood: **Max results** capped the run, **Max results per account** capped a single account, the **date range** excluded most of the history, **Which posts** was set to a narrow filter such as media-only, or the account simply has fewer posts than you thought. The run log names which of these applied.

#### What happens when a handle does not exist?

The run finishes successfully and writes one row with `isNoResults: true` and a `noResultsReason` that says what happened. Handles on Bluesky change whenever somebody moves their account to a new domain, so this is common and it should not break your pipeline. Give one good account and one bad one and you get the good one's data plus a warning in the log.

#### Can I scrape the replies to a post, or who liked it?

Not in this version. Bluesky's public API does expose both, so if you need them, ask through the Issues tab and say which one matters. Version 0.1 covers posts, profiles, followers and following.

#### Can I get a field that is not listed?

Probably. The AT Protocol exposes more per record than any dataset needs, so open an issue on the Actor's Issues tab describing the field and it can usually be added.

#### Do I need a Bluesky account or an app password?

No. That is the point. Nothing here asks for credentials, so there is no account of yours to rate limit, suspend or lock out.

### You might also like

- [Telegram Channel Scraper](https://apify.com/cirkit/telegram-channel-scraper) for public Telegram channel history, views and reactions
- [YouTube Comments Scraper](https://apify.com/cirkit/youtube-comments-scraper) for comment threads and engagement
- [Pinterest Pins Scraper](https://apify.com/cirkit/pinterest-pins-scraper) for pins, boards and saves
- [Twitch Search Scraper](https://apify.com/cirkit/twitch-search-scraper) for live streams, channels and categories

### Changelog

**0.1** (2026-09-03) First release. Posts, profiles, followers and following. Keyword account discovery with follower counts filled in. Five embed types parsed, reposted authors resolved so no engagement column is null, and cursor pagination that ends on a missing cursor rather than on a short page.

# Actor input Schema

## `profiles` (type: `array`):

Accounts to scrape. Accepts a handle (nytimes.com), an @handle (@nytimes.com), a DID (did:plc:eclio37ymobqex2ncko63h4r), or a profile link (https://bsky.app/profile/nytimes.com). A link to a single post works too, and is read as its author.

## `searchTerms` (type: `array`):

Optional. Finds Bluesky accounts whose handle, display name or bio matches the keyword, then scrapes them like any other account. Use this when you do not have a list of handles yet.

## `resultsType` (type: `string`):

Posts exports each account's timeline. Profiles returns one row per account with follower, following and post counts. Followers and Following export an account's social graph, one row per person.

## `maxItems` (type: `integer`):

Hard cap on the total number of rows returned across every account. Set to 0 for no limit. You are charged per row, so this is also your budget control.

## `maxItemsPerProfile` (type: `integer`):

Optional per-account cap, so one busy account cannot use up the whole budget. Leave empty to let the total cap decide.

## `postFilter` (type: `string`):

Only applies when scraping posts. Bluesky itself decides what each option means: replies included, replies excluded, media only, or the account's own threads.

## `includeReposts` (type: `boolean`):

Only applies when scraping posts. On, a repost is returned as a row with the original author in the profile columns and the reposting account in repostedByHandle. Off, reposts are dropped and you only pay for original content.

## `fromDate` (type: `string`):

Optional. Only applies when scraping posts. Skips posts published before this date. Format YYYY-MM-DD.

## `toDate` (type: `string`):

Optional. Only applies when scraping posts. Skips posts published after this date. The whole day is included. Format YYYY-MM-DD.

## `maxProfilesPerKeyword` (type: `integer`):

How many accounts each keyword may contribute. Only applies when Keywords is used. Defaults to 20.

## `proxyConfiguration` (type: `object`):

Proxy settings. Bluesky's public API answers the Apify datacenter proxy exactly as it answers any other address, so the default is the cheapest option. Leave the group unset.

## Actor input object example

```json
{
  "profiles": [
    "nytimes.com",
    "@bsky.app",
    "https://bsky.app/profile/pfrazee.com"
  ],
  "searchTerms": [
    "real estate",
    "climate scientist"
  ],
  "resultsType": "posts",
  "maxItems": 100,
  "postFilter": "posts_with_replies",
  "includeReposts": true,
  "fromDate": "2026-01-01",
  "toDate": "2026-06-30",
  "maxProfilesPerKeyword": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Everything this run collected as the Overview table: handle, audience size, post text, likes, reposts, replies and the permalink.

## `profiles` (type: `string`):

The account-level view: handle, display name, followers, following, post count, verification, bio and join date.

## `engagement` (type: `string`):

The same rows ranked for reach analysis: likes, reposts, replies, quotes, total engagement and engagement per follower.

## `resultsCsv` (type: `string`):

The full result set as CSV, ready for a spreadsheet or a BI tool.

## `consoleRun` (type: `string`):

The Apify Console page for this run, including the live log.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "profiles": [
        "nytimes.com"
    ],
    "resultsType": "posts",
    "maxItems": 100,
    "maxProfilesPerKeyword": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("cirkit/bluesky-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "profiles": ["nytimes.com"],
    "resultsType": "posts",
    "maxItems": 100,
    "maxProfilesPerKeyword": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("cirkit/bluesky-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "profiles": [
    "nytimes.com"
  ],
  "resultsType": "posts",
  "maxItems": 100,
  "maxProfilesPerKeyword": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call cirkit/bluesky-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cirkit/bluesky-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zK4chBYfY4bbeCnOC/builds/tiXqbHe8Kn0Aa6JdO/openapi.json
