# Bluesky Social Scraper (`scrapyx/bluesky-social-scraper`) Actor

Profiles, posts, followers, follows, user search and full reply threads from Bluesky, via the public AT Protocol AppView API. No login, no API key, cursor pagination followed to the end.

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

## Pricing

from $2.10 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Bluesky Social Scraper

Profiles, posts, followers, follows, user search and **full reply threads**
from Bluesky — through the public AT Protocol AppView API.

No login, no API key, no OAuth, no rate-limit token. Cursor pagination is
followed to the end unless you cap it.

### Modes

| Mode | Reads | Returns |
| --- | --- | --- |
| `profiles` | `actors` | full profile records, 25 per request |
| `authorFeed` | `actors` | a user's posts, paginated |
| `followers` | `actors` | accounts following a user |
| `follows` | `actors` | accounts a user follows |
| `searchActors` | `query` | user search results |
| `thread` | `postUris` | a post plus every reply, flattened with depth |

Post search is **not** offered — see the first known limit below.

### Input

`actors` accepts whatever you have to hand — all three of these are the same
account:

```json
{ "mode": "profiles",
  "actors": ["@bsky.app", "did:plc:z72i7hdynmk6r22z27h6tvur",
             "https://bsky.app/profile/bsky.app"] }
```

`postUris` accepts an `at://` URI or a `bsky.app` post URL; when the URL
carries a handle instead of a DID, the DID is resolved for you first.

```json
{ "mode": "thread",
  "postUris": ["https://bsky.app/profile/alice.bsky.social/post/3labc123xyz"] }
```

### What you get

Three record types share one dataset, told apart by `recordType`:

- **`QUERY_SUMMARY`** — one per input: the XRPC method called, pages
  fetched, rows returned, whether a cap bound, and which actors upstream
  silently dropped.
- **`PROFILE` / `POST`** — one per row, upstream's object passed through
  verbatim (`handle`, `displayName`, `followersCount`, `record`, `likeCount`,
  `embed`, …) plus the envelope fields.
- **`ERROR`** — one per input that failed *or* was silently dropped, so every
  input maps to at least one row.

Thread rows additionally carry `threadDepth` and `parentUri`, so the reply
tree is reconstructable from the flat dataset. A 234-reply thread came back
as 280 posts across 7 depth levels in testing.

### Known limits — read these before you rely on the output

**Post search is not available and this actor does not pretend otherwise.**
`app.bsky.feed.searchPosts` returns `403` with an HTML body to
unauthenticated callers. It was measured from **three exits on three
continents** — Indonesia, Brazil and the United States, via three different
CDN edges — and blocked in all three, while every other method on the same
host answered normally in the same session. So it is not geo-gating and
**pinning a proxy country does not help**. The mode is therefore absent from
the mode list rather than offered and broken. Use `searchActors` for user
search (unaffected), or `authorFeed` to read a known account's posts. An API
caller that passes `searchPosts` explicitly still gets an ERROR row
explaining this rather than a silent empty run.

**`profiles` mode: upstream silently drops actors it cannot resolve.**
`getProfiles` answers HTTP 200 with *fewer profiles than you asked for* and
no error field — measured: 4 requested, 2 returned, response body containing
only `profiles`. (The single-actor endpoint answers `400 Profile not found`
for the same input, so the batch endpoint is strictly less honest.) This
actor diffs requested against returned and emits an `actor_not_returned`
ERROR row per dropped actor, and lists them in the summary's
`actorsSilentlyDropped`.

**Follower lists can be enormous.** `bsky.app` alone has ~34.8M followers.
`followers` mode without `maxItems` will page until it finishes. Set a cap.

**`pageLimit` above 100 is refused up front.** Upstream's ceiling is 100 —
101 returns `400 integer too big`, 0 returns `400 integer too small`.

**Post records are not rendered.** `record.text` is the raw post text and
`embed`/`facets` are passed through as upstream sends them; links, mentions
and rich text are yours to resolve.

### Technical

Public AppView (`public.api.bsky.app/xrpc/`), HTTP only, no browser. No WAF
on the methods used: 4/4 TLS profiles identical. `bsky.social` is the PDS and
answers `401 AuthMissing` for these methods — it is a different role, not a
fallback.

`robots.txt` checked on **both** `public.api.bsky.app` and `bsky.app`: no
`ClaudeBot`/`anthropic-ai` group and no blanket disallow on either.

Full recon trail, including how each ceiling was bisected, is in
`CRAWLING_METHOD.md`.

# Actor input Schema

## `mode` (type: `string`):

Which kind of data to fetch. 'profiles', 'authorFeed', 'followers' and 'follows' read the 'Actors' field; 'searchActors' reads 'Search query'; 'thread' reads 'Post URIs or URLs'. Post search is deliberately not offered: Bluesky's public API refuses it for unauthenticated callers (verified from three continents), so it would fail every time.

## `actors` (type: `array`):

Accounts to fetch. Accepts a handle (alice.bsky.social, with or without a leading @), a DID (did:plc:...), or a bsky.app profile URL copied from the browser. Used by the 'profiles', 'authorFeed', 'followers' and 'follows' modes.

## `query` (type: `string`):

Search terms, used by the 'searchActors' mode. A query with no matches returns an empty result honestly rather than falling back to unfiltered results.

## `postUris` (type: `array`):

Used by the 'thread' mode. Accepts an at:// URI (at://did:plc:.../app.bsky.feed.post/<rkey>) or a bsky.app post URL. When a URL carries a handle rather than a DID, the DID is resolved automatically first.

## `pageLimit` (type: `integer`):

How many rows to ask for per API call. Upstream's ceiling is 100 (bisected: 101 returns HTTP 400 'integer too big', 0 returns 'integer too small'), so leaving this at 100 makes the fewest requests for the same data.

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

Optional cap on total data rows across the whole run, shared across every actor or query in the input. Leave empty to follow pagination to the end. Note some accounts have millions of followers — a cap is strongly recommended for the 'followers' mode.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once. Cursor pagination within a single actor is inherently sequential, so this only speeds up runs that list several actors.

## `minRequestInterval` (type: `number`):

The honest speed control: the shortest gap between two request starts. The API is public and unauthenticated; this is routine politeness pacing rather than a defensive measure.

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

Residential with no country pin is the cloud default. No WAF was observed on any method this actor uses (4/4 TLS profiles identical), so the proxy is routine hygiene rather than a bypass.

## Actor input object example

```json
{
  "mode": "profiles",
  "actors": [
    "bsky.app"
  ],
  "postUris": [],
  "pageLimit": 100,
  "maxConcurrency": 4,
  "minRequestInterval": 0.25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/bluesky-social-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/bluesky-social-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 '{}' |
apify call scrapyx/bluesky-social-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/bluesky-social-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/iBPZD04jgmI3X1oV4/builds/b5DH0Uv3v4ziQwGL3/openapi.json
