# Bluesky Scraper — Posts, Profiles, Followers & Search (`ethanteague/bluesky-scraper`) Actor

Scrape Bluesky posts, profiles, followers/following, full threads, and keyword search into clean flat JSON/CSV. Profiles and feeds need no login; keyword search uses your free app password.

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

## Pricing

from $0.50 / 1,000 post scrapeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## Bluesky Scraper — Posts, Profiles, Followers & Search

Scrape **Bluesky** posts, profiles, followers, full reply threads, and keyword search results into clean, flat JSON — ready for CSV/Excel export, dashboards, lead lists, research datasets, or AI agents.

Built on Bluesky's official AT Protocol API. **Profiles, user feeds, followers, and threads need no login at all.** Keyword post search uses your free Bluesky app password (Bluesky blocks anonymous search platform-wide — any tool that claims otherwise is proxying your requests through someone else's account).

### What you can scrape

| Mode | What you get | Login needed? |
|---|---|---|
| **User posts** | Latest posts from any user's feed (reposts optional, reply filtering) | No |
| **Profiles** | Bio, display name, follower/following/post counts, avatar, dates | No |
| **Followers / Following** | Full follower and following lists as profile records | No |
| **Threads** | A post plus its entire reply tree from any post URL | No |
| **Keyword search** | Posts matching any query, sorted by latest or top, with date/language filters | Free app password |

### Why this scraper

- **Flat output, no nesting headaches** — every item exports to CSV in one click. Author fields, like counts, image URLs, links, and quoted posts are all top-level columns.
- **Honest engineering** — official AT Protocol endpoints, polite rate limiting with automatic backoff and retries. No fragile HTML parsing that breaks every redesign.
- **Transparent pay-per-event pricing** — you pay per post/profile scraped, nothing else. 1,000 posts ≈ $0.50.
- **Built for AI agents** — clean input schema, predictable output, works out of the box through the Apify API and MCP.
- **Failures don't eat your money** — you're only charged for items actually delivered to the dataset.

### Quick start

1. Enter one or more **user handles** (`bsky.app`, `@someone.bsky.social`, or a profile URL) — or paste **post URLs** to scrape threads.
2. Choose how many posts you want and whether to include profiles, followers, or reposts.
3. Click **Start** and export the dataset as JSON, CSV, Excel, or feed it to the API.

#### Unlocking keyword search (2 minutes, free)

Bluesky requires authentication for keyword search. In the Bluesky app or website: **Settings → Privacy and security → App passwords → Add app password.** Copy the generated password into this actor's *Bluesky app password* field and put your handle in *Bluesky identifier*. Never use your main account password — app passwords can be revoked anytime and can't manage your account. Your credentials are encrypted by Apify, used only to call Bluesky's search endpoint, and never logged.

### Example input

```json
{
  "userHandles": ["bsky.app", "https://bsky.app/profile/atproto.com"],
  "maxPostsPerUser": 200,
  "includeProfiles": true,
  "includeFollowers": true,
  "maxFollowersPerUser": 500,
  "postThreadUrls": ["https://bsky.app/profile/someone.bsky.social/post/3mqc36slinc2m"],
  "searchQueries": ["ai agents"],
  "maxPostsPerQuery": 300,
  "searchSort": "latest"
}
```

### Example output

A post item (trimmed):

```json
{
  "type": "post",
  "uri": "at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.feed.post/3mp2n77vwzc2d",
  "url": "https://bsky.app/profile/atproto.com/post/3mp2n77vwzc2d",
  "authorHandle": "atproto.com",
  "authorDisplayName": "AT Protocol Developers",
  "authorDid": "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
  "text": "We're very pumped to announce that the reference PDS now has an account management page!",
  "createdAt": "2026-06-24T19:22:03.650Z",
  "replyCount": 4,
  "repostCount": 87,
  "likeCount": 312,
  "quoteCount": 9,
  "isReply": false,
  "images": [{ "url": "https://cdn.bsky.app/img/feed_fullsize/...", "alt": "Screenshot" }],
  "externalLink": { "uri": "https://atproto.com/blog/...", "title": "PDS account management" },
  "source": "author_feed",
  "sourceHandle": "atproto.com"
}
```

A profile item:

```json
{
  "type": "profile",
  "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "handle": "bsky.app",
  "displayName": "Bluesky",
  "description": "official Bluesky account",
  "followersCount": 1274531,
  "followsCount": 12,
  "postsCount": 918,
  "createdAt": "2023-04-12T04:53:57.057Z",
  "profileUrl": "https://bsky.app/profile/bsky.app",
  "source": "input"
}
```

Every post also carries `indexedAt`, `langs`, `labels`, `parentUri` (for replies), `videoThumbnail`, and quoted-post fields when present. Reposts (when enabled) are marked `isRepost: true` with the original author.

### Pricing

Pay only for what lands in your dataset:

| Event | Price |
|---|---|
| Actor start | $0.001 per run |
| Post scraped | $0.0005 (= **$0.50 per 1,000 posts**) |
| Profile scraped (incl. follower/following records) | $0.001 (= $1.00 per 1,000 profiles) |

Examples: a competitor's last 500 posts + profile ≈ **$0.25**. Followers of five accounts, 1,000 each ≈ **$5.00**. Set a *Maximum total charge* on any run to hard-cap spend — the actor stops gracefully at the limit.

### Use it via API

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~bluesky-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userHandles": ["bsky.app"], "maxPostsPerUser": 50}'
```

Works with the [Apify JavaScript and Python clients](https://docs.apify.com/api/client/js/), scheduled runs, webhooks, and integrations (Google Sheets, Make, Zapier, LangChain).

#### AI agents & MCP

This actor is agent-friendly: strict input schema, flat deterministic output, and it's callable as a tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) — point your agent at it and ask for "the last 100 posts from @handle as a table."

### Rate limits, freshness & fair use

Data comes live from Bluesky's API at request time — nothing cached, nothing stale. The actor paginates politely (automatic backoff on rate limits) and typically sustains thousands of items per minute. Only public data is collected: public posts, public profiles, public graphs. Keyword search runs under your own account credentials against the official endpoint.

### FAQ

**Is scraping Bluesky allowed?** This actor reads public data through Bluesky's official, documented AT Protocol API — the same endpoints any Bluesky client uses — at polite request rates. Keyword search additionally runs under your own credentials. You're responsible for complying with applicable laws and Bluesky's terms for your use case.

**Why do some profiles show `handle.invalid`?** That's Bluesky's marker for accounts with broken handle verification; the actor automatically falls back to DID-based profile URLs so the records stay usable.

**Can I use search operators?** Yes — operators that work in the Bluesky app's search box (like `from:handle`) work in `searchQueries` too.

**My account is on a self-hosted PDS.** Set *PDS service URL* in the Advanced section; everything else works the same.

**Something broke or you need another mode?** Open an issue on this actor's **Issues** tab — I typically respond within a day. Planned next: lists, feed generators, starter packs, and post likers/quoters.

# Actor input Schema

## `userHandles` (type: `array`):

Bluesky users to scrape — handles (<code>bsky.app</code>, <code>@someone.bsky.social</code>), profile URLs (<code>https://bsky.app/profile/...</code>), or DIDs. For each user the actor scrapes the profile and/or their posts, depending on the options below. <b>No login needed.</b>

## `maxPostsPerUser` (type: `integer`):

How many of each user's most recent posts to scrape. Set to 0 to skip posts and get profiles only.

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

Push a profile item (bio, follower counts, etc.) for every user handle.

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

When on, reposts appearing in a user's feed are included (the item's author is the original poster).

## `authorPostsFilter` (type: `string`):

Filter applied to each user's feed.

## `includeFollowers` (type: `boolean`):

Collect each user's followers (as profile items with source = followers\_of).

## `includeFollows` (type: `boolean`):

Collect who each user follows (as profile items with source = follows\_of).

## `maxFollowersPerUser` (type: `integer`):

Cap for each list (applies separately to followers and following).

## `postThreadUrls` (type: `array`):

Post URLs (<code>https://bsky.app/profile/handle/post/xyz</code>) or AT-URIs (<code>at://did/app.bsky.feed.post/xyz</code>). The post and its reply tree are scraped.

## `threadDepth` (type: `integer`):

How many levels of nested replies to fetch.

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

Keyword searches, e.g. <code>ai agents</code>. Advanced operators from the Bluesky app work too (e.g. <code>from:handle</code>). Leave empty to skip search.

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

How many posts to collect per search query.

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

Sort order of search results.

## `searchSince` (type: `string`):

Only posts after this time, e.g. <code>2026-01-01T00:00:00Z</code>. Optional.

## `searchUntil` (type: `string`):

Only posts before this time. Optional.

## `searchLanguage` (type: `string`):

Two-letter language code filter, e.g. <code>en</code>. Optional.

## `blueskyIdentifier` (type: `string`):

Your Bluesky handle or the email you log in with. Only used to unlock keyword search; never stored.

## `blueskyAppPassword` (type: `string`):

An app password from Settings → Privacy and security → App passwords. NOT your main password. Encrypted by Apify; never logged.

## `pdsService` (type: `string`):

Only change this if your Bluesky account lives on a self-hosted PDS.

## Actor input object example

```json
{
  "userHandles": [
    "bsky.app"
  ],
  "maxPostsPerUser": 100,
  "includeProfiles": true,
  "includeReposts": false,
  "authorPostsFilter": "posts_no_replies",
  "includeFollowers": false,
  "includeFollows": false,
  "maxFollowersPerUser": 200,
  "threadDepth": 6,
  "maxPostsPerQuery": 100,
  "searchSort": "latest",
  "pdsService": "https://bsky.social"
}
```

# 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 = {
    "userHandles": [
        "bsky.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ethanteague/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 = { "userHandles": ["bsky.app"] }

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

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

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

```

## CLI example

```bash
echo '{
  "userHandles": [
    "bsky.app"
  ]
}' |
apify call ethanteague/bluesky-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ethanteague/bluesky-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ouMZtiraqVL8PGIoZ/builds/BwQkFHiQtGhX4wYeg/openapi.json
