# Bluesky Scraper – Posts, Profiles, Replies & Followers (`chris_elder/bluesky-scraper`) Actor

Scrape Bluesky via the official public API: keyword search, user posts, replies, profiles, followers and following, with likes, reposts, links and bio emails. Date, language and likes filters (filtered posts free), only-new monitoring. No login. $0.50 per 1,000 posts.

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

## Pricing

from $0.50 / 1,000 posts

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bluesky Scraper – Posts, Profiles, Replies & Followers

Export **Bluesky** data from the official public API: keyword search results, every post from any account, full reply threads, profiles (followers, bio, emails and links in bio) and follower/following lists. Filter by date, language, likes, keywords, mentions or linked domain, and use **only-new mode** to monitor topics or accounts on a schedule and pay only for new posts.

✅ Pay per result: **$0.50 per 1,000 posts** · 👥 **$0.30 per 1,000 follower/following accounts** · 🆓 Filtered-out posts and missing accounts are never charged · 🔑 No login, app password or API key · ⏱️ Start fee only $0.00005

[▶ Try it with one click](#how-to-use-it) · [📄 Real example output](#output-example-from-a-real-run) · [💲 Pricing](#how-much-does-it-cost-to-scrape-bluesky) · [🔌 API & integrations](#integrations-api-ai-agents-make-zapier) · [❓ FAQ](#faq) · [🛟 Support](#support-reliability-and-feature-requests)

### What does Bluesky Scraper do?

- 🔎 **Search posts** by keyword, hashtag or phrase (Bluesky search syntax works, e.g. `from:`, `#tag`, quotes), sorted by latest or top.
- 👤 **Account feeds**: all posts from any handle, with or without replies, reposts or media only.
- 💬 **Reply threads**: paste post URLs and get the post plus all replies, flattened with depth and parent.
- 🪪 **Profiles**: display name, bio, followers, following, post count, created date, avatar, banner, and **emails and links found in the bio**.
- 👥 **Followers and following** of any account, plus **people search** by keyword (find journalists, founders, developers...).
- 📊 **Engagement on every post**: likes, reposts, replies, quotes, bookmarks and a total `engagement` number for sorting.
- 🔗 **Entities pulled out**: links, hashtags, mentions, emails, images, video, link card and quoted post.
- 🎯 **Filters that save money**: date range, language, keywords, exclude keywords, minimum likes/reposts, no replies, no reposts, mentions, linked domain. Skipped posts cost nothing.
- 🔁 **Only new posts** for monitoring: the Actor remembers what it has already returned, so a daily or hourly schedule charges only for what is new.

### Use cases

- **Brand and PR monitoring**: track mentions of your brand, product or competitors and get only new posts each day.
- **Lead generation**: search people by keyword, collect profiles with bio emails and links, export followers of a niche account.
- **Researchers and journalists**: archive topics, accounts or full reply threads with dates and engagement.
- **Marketers and creators**: find top posts on a topic, compare accounts by followers and engagement.
- **AI and RAG builders**: feed clean post text, links and metadata into an LLM pipeline or vector store.

### How to use it

1. Click **Try for free**. The prefill searches "open data" (20 posts) and reads the official `bsky.app` account (10 posts + profile) in a few seconds for about **$0.016**.
2. Add your own search queries, handles (`bsky.app`, `@bsky.app` or a profile URL), post URLs, or accounts whose followers you want.
3. Optional: add a date range, language, keywords or a minimum like count.
4. Run it, then export the **Posts** or **Profiles & accounts** table as JSON, CSV or Excel, or read it by API.
5. For monitoring, tick **Only new posts** and add a schedule.

#### Input example

```json
{
  "searchQueries": ["open data", "#opensource"],
  "searchSort": "latest",
  "maxPostsPerQuery": 100,
  "handles": ["bsky.app"],
  "maxPostsPerHandle": 50,
  "dateFrom": "7 days",
  "language": "en",
  "minLikes": 5,
  "onlyNew": false
}
```

Other options: `authorFeedFilter`, `includeProfiles`, `postUrls`, `maxRepliesPerPost`, `followersOf`, `followingOf`, `userSearchQueries`, `maxAccountsPerList`, `dateTo`, `keywords`, `excludeKeywords`, `minReposts`, `excludeReplies`, `excludeReposts`, `mentions`, `domain`.

#### Output example (from a real run)

Post row (run on 25 Oct 2026; some empty fields left out):

```json
{
  "type": "post",
  "url": "https://bsky.app/profile/bsky.app/post/3mykozpeyv22w",
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mykozpeyv22w",
  "text": "this was the exact use case we had in mind while we were building it",
  "createdAt": "2026-10-23T17:24:06.492Z",
  "langs": ["en"],
  "authorHandle": "bsky.app",
  "authorDisplayName": "Bluesky",
  "authorUrl": "https://bsky.app/profile/bsky.app",
  "likeCount": 106,
  "repostCount": 3,
  "replyCount": 4,
  "quoteCount": 0,
  "bookmarkCount": 1,
  "engagement": 113,
  "isReply": true,
  "isRepost": false,
  "links": [],
  "hashtags": [],
  "images": [],
  "externalLink": null,
  "quotedPostUrl": null,
  "source": "handle",
  "sourceValue": "bsky.app"
}
```

Profile row:

```json
{
  "type": "profile",
  "handle": "bsky.app",
  "displayName": "Bluesky",
  "url": "https://bsky.app/profile/bsky.app",
  "description": "official Bluesky account (check username👆)\n\nBugs, feature requests, feedback: support@bsky.app",
  "emails": ["support@bsky.app"],
  "links": [],
  "followersCount": 35373117,
  "followsCount": 24,
  "postsCount": 896,
  "createdAt": "2023-04-12T04:53:57.057Z",
  "verified": false
}
```

| Field | Meaning |
|---|---|
| `type` | `post`, `profile`, `account` (follower/following/people-search row) or `error` (free) |
| `url`, `uri`, `createdAt` | Web link, AT Protocol URI, ISO date (UTC) |
| `text`, `langs` | Post text and declared languages |
| `likeCount`, `repostCount`, `replyCount`, `quoteCount`, `bookmarkCount`, `engagement` | Engagement numbers |
| `isReply`, `replyToUri`, `threadRootUri`, `isRepost`, `repostedBy` | Post context |
| `links`, `hashtags`, `mentionedDids`, `emails` | Entities found in the post |
| `images`, `videoUrl`, `externalLink`, `quotedPostUrl` | Media, link card and quoted post |
| `followersCount`, `followsCount`, `postsCount`, `emails`, `links` | Profile numbers and contacts from the bio |
| `ofHandle`, `relation` | For account rows: whose list and `follower`/`following`/`search` |
| `source`, `sourceValue`, `matchedKeywords` | Which input produced the row |

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

| Event | Price | When |
|---|---|---|
| Post | **$0.0005** | Each post saved ($0.50 per 1,000) |
| Profile | **$0.001** | Each full profile row (handles, if enabled) |
| Account | **$0.0003** | Each follower / following / people-search row ($0.30 per 1,000) |
| Actor start | $0.00005 per GB of memory | Once per run (default 256 MB) |
| Filtered out, not found, failed | **$0** | Never charged |

Worked examples:

| Job | Arithmetic | Cost |
|---|---|---|
| One-click example (30 posts + 1 profile) | 30 × $0.0005 + 1 × $0.001 | **≈ $0.016** |
| 10,000 posts for a keyword | 10,000 × $0.0005 | **$5.00** |
| 5,000 followers of a niche account | 5,000 × $0.0003 | **$1.50** |
| Daily brand monitor, ~100 new posts a day, 30 days | 3,000 × $0.0005 + 30 starts | **≈ $1.50 / month** |

Set *Maximum cost per run* in Apify and the Actor stops cleanly at your limit.

### Integrations (API, AI agents, Make, Zapier)

Run and get results in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/chris_elder~bluesky-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchQueries":["open data"],"maxPostsPerQuery":50,"dateFrom":"24 hours"}'
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("chris_elder/bluesky-scraper").call(run_input={"handles": ["bsky.app"], "maxPostsPerHandle": 100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item.get("createdAt"), item.get("likeCount"), item.get("url"))
```

- AI agents (MCP): `https://mcp.apify.com/?tools=chris_elder/bluesky-scraper`
- Make, Zapier and n8n: use the Apify "Run Actor" module. Pair **Only new posts** with an Apify schedule and a webhook to push new posts to Slack, Sheets or a database.

### Limits and good to know

- Uses Bluesky's public AppView API. With `searchSort: latest` the Actor pages back in time until your limit or date range is reached; `top` returns up to 100 posts per query (Bluesky's own limit for logged-out search). Search is not a complete archive; very old posts may be missing.
- Accounts that hide their content from logged-out users, deleted posts and blocked threads are not returned.
- Follower/following rows come from the list endpoint and carry no follower counts; add those handles to `handles` if you need full profiles.
- Image and video links point to Bluesky's CDN.

### Is it legal?

The Actor reads only public data through Bluesky's own public API, the same data anyone can see on bsky.app without logging in. It does not log in, post, follow or bypass any access control, and respects accounts that opt out of logged-out visibility. Posts and profiles contain personal data (names, handles, emails), so you are responsible for a lawful basis for your use (for example GDPR) and for respecting Bluesky's terms. See Apify's guide: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

### FAQ

**Do I need a Bluesky account or app password?** No. Nothing to log in to.

**How do I monitor a keyword or brand?** Add it to `searchQueries` with `searchSort: latest`, tick *Only new posts* and schedule the Actor hourly or daily. Only new matching posts are returned and charged.

**Can I get posts from a date range?** Yes. `dateFrom` and `dateTo` accept a date (`2026-10-01`) or relative time (`7 days`, `24 hours`). For search queries the dates are passed to Bluesky's search too.

**How do I get a full reply thread?** Put the post URL in `postUrls`. You get the post and replies with depth, up to `maxRepliesPerPost`.

**Why did a handle return an error row?** The handle does not exist, was renamed, or the account hides itself from logged-out viewers. Error rows are free.

**Can it download the images?** It returns image URLs. Pass them to [Bulk File Downloader](https://apify.com/chris_elder/bulk-file-downloader) to save the files or a ZIP.

### Support, reliability and feature requests

- Missing accounts, filtered posts and failed requests are free.
- Every release is tested on real public accounts; the example input works.
- Issues answered within 2 business days via the Actor **Issues** tab.
- Breaking input or price changes are announced in this README first.

Built by GPT Shop Ltd with AI-assisted development; every release is reviewed and tested.

### More tools from GPT Shop Ltd

[Telegram Channel Scraper](https://apify.com/chris_elder/telegram-channel-scraper) · [Bulk File Downloader](https://apify.com/chris_elder/bulk-file-downloader) · [HTTP Request Sender](https://apify.com/chris_elder/http-request-sender) · [RSS Feed Reader](https://apify.com/chris_elder/rss-feed-reader) · [all tools](https://apify.com/chris_elder)

# Actor input Schema

## `searchQueries` (type: `array`):

Bluesky post search, one query per line. Supports Bluesky search syntax: "exact phrase", from:handle, #hashtag, -exclude. Each query returns up to "Max posts per search query".

## `searchSort` (type: `string`):

Latest returns newest posts first; Top returns the most engaged posts Bluesky finds for the query.

## `maxPostsPerQuery` (type: `integer`):

Stops each search after this many saved posts. Filtered posts do not count and are free.

## `handles` (type: `array`):

One per line: bsky.app, @nasa.gov, alice (becomes alice.bsky.social), did:plc:..., or https://bsky.app/profile/alice.bsky.social. Returns a profile row and the newest posts of each account.

## `maxPostsPerHandle` (type: `integer`):

Newest first. Set 0 to get only profile rows.

## `authorFeedFilter` (type: `string`):

Bluesky's own author-feed filter.

## `includeProfiles` (type: `boolean`):

One row per handle with bio, followers, following, posts count, created date, plus emails and links found in the bio.

## `postUrls` (type: `array`):

One per line: https://bsky.app/profile/<handle>/post/<id> or at:// URIs. Returns the post and its reply thread (up to 10 levels deep).

## `maxRepliesPerPost` (type: `integer`):

Replies returned per post URL (Bluesky returns up to a few hundred per thread).

## `followersOf` (type: `array`):

One handle or profile URL per line. Each follower is one account row (charged as Account).

## `followingOf` (type: `array`):

One handle or profile URL per line. Each followed account is one account row.

## `userSearchQueries` (type: `array`):

Find accounts whose name, handle or bio matches, e.g. "data journalist" or "shopify". Each match is one account row with bio emails and links.

## `maxAccountsPerList` (type: `integer`):

Caps each followers list, following list and user search.

## `dateFrom` (type: `string`):

Optional. YYYY-MM-DD, an ISO date-time, or relative like "7 days" / "24 hours". Search uses it server-side; author feeds stop paging when older posts are reached.

## `dateTo` (type: `string`):

Optional. YYYY-MM-DD or relative like "1 day".

## `language` (type: `string`):

Optional two-letter code, e.g. en, de, ja. Posts tagged with another language are skipped (free).

## `keywords` (type: `array`):

Optional, case-insensitive. Posts without a match are skipped and NOT charged. Matches are returned in matchedKeywords.

## `excludeKeywords` (type: `array`):

Optional, case-insensitive. Skipped posts are not charged.

## `minLikes` (type: `integer`):

Optional. Posts with fewer likes are skipped (free).

## `minReposts` (type: `integer`):

Optional. Posts with fewer reposts are skipped (free).

## `excludeReplies` (type: `boolean`):

Skip posts that are replies (free).

## `excludeReposts` (type: `boolean`):

In author feeds, skip posts the account reposted from others (free).

## `mentions` (type: `string`):

Optional, applies to keyword search only.

## `domain` (type: `string`):

Optional, applies to keyword search only, e.g. nytimes.com. Useful for brand and link tracking.

## `onlyNew` (type: `boolean`):

Remembers the newest post time per search query and handle in a named key-value store in your account, so scheduled runs return and charge only new posts.

## Actor input object example

```json
{
  "searchQueries": [
    "open data"
  ],
  "searchSort": "latest",
  "maxPostsPerQuery": 20,
  "handles": [
    "bsky.app"
  ],
  "maxPostsPerHandle": 10,
  "authorFeedFilter": "posts_with_replies",
  "includeProfiles": true,
  "maxRepliesPerPost": 100,
  "maxAccountsPerList": 500,
  "minLikes": 0,
  "minReposts": 0,
  "excludeReplies": false,
  "excludeReposts": false,
  "onlyNew": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

## `summary` (type: `string`):

No description

# 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 = {
    "searchQueries": [
        "open data"
    ],
    "maxPostsPerQuery": 20,
    "handles": [
        "bsky.app"
    ],
    "maxPostsPerHandle": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("chris_elder/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 = {
    "searchQueries": ["open data"],
    "maxPostsPerQuery": 20,
    "handles": ["bsky.app"],
    "maxPostsPerHandle": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("chris_elder/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 '{
  "searchQueries": [
    "open data"
  ],
  "maxPostsPerQuery": 20,
  "handles": [
    "bsky.app"
  ],
  "maxPostsPerHandle": 10
}' |
apify call chris_elder/bluesky-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,chris_elder/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/2NoHvDUgnQd7pQYdE/builds/8KbWEHvkFk39jlzyu/openapi.json
