# Bluesky Scraper — Profiles, Posts, Followers (AT Protocol) (`dexterous_tripod/bluesky-social-graph-scraper`) Actor

Extract Bluesky profiles, posts, follower/following graphs, starter-pack members, and custom feeds via the public AT Protocol API. No login. One normalized output.

- **URL**: https://apify.com/dexterous\_tripod/bluesky-social-graph-scraper.md
- **Developed by:** [Max Miller](https://apify.com/dexterous_tripod) (community)
- **Categories:** Social media, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 profile scrapeds

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

## Bluesky Scraper — Profiles, Posts, Followers, Starter Packs & Feeds (AT Protocol)

**Scrape Bluesky at scale — no login, no API key, no rate-limit headaches.** Pull profiles, posts, follower and following graphs, starter-pack members, and custom-feed contents from Bluesky (bsky.app) through the official public AT Protocol API. Every mode returns one clean, normalized record shape ready for CSV, Excel, JSON, or your database.

Built for growth teams, researchers, and analysts who need Bluesky data now that X's API costs $100+/month. This is the only Bluesky actor that unifies profiles, posts, social graph, starter packs, **and** custom feeds in a single tool.

### Why this Bluesky scraper

- **No authentication.** It reads public data through Bluesky's public AppView. Nothing to log in to, no tokens to rotate, no account to get banned.
- **Fast and cheap.** Pure REST — no headless browser — so runs finish in seconds and cost pennies.
- **One schema for everything.** Profiles, posts, followers, following, starter-pack members, and feed posts all normalize into the same row format. No reshaping downstream.
- **Handles the messy parts.** Cursor pagination, deleted/suspended accounts, and rate limits are handled for you. A bad handle never kills a run.

### Use cases

#### Brand & competitor monitoring

Track any account's posts and engagement (likes, reposts, replies, quotes) over time. Watch competitors' follower growth. Pull a brand's latest posts into your dashboard or alerting.

#### Follower & audience analysis

Export the full follower or following list for any account to find overlap, spot influencers, size an audience, or build lookalike lists. Combine with the `profiles` mode to enrich each account with bio, follower count, and verification status.

#### Starter-pack & community research

Extract every member of any Bluesky starter pack — the fastest way to map a niche community or build a targeted outreach list. Great for finding the accounts that matter in a topic.

#### Trend & feed research

Pull the contents of any custom feed (e.g. "What's Hot") to study what's trending, harvest content ideas, or feed a research pipeline.

#### Academic & data science

Collect structured social-graph and post data for network analysis, NLP, or social-science research — reproducibly and at low cost.

### Input

| Field | Type | Description |
|---|---|---|
| `mode` | select | `profiles` · `posts` · `followers` · `following` · `starterpack` · `feed` |
| `handles` | string\[] | Handles (`bsky.app`) or DIDs (`did:plc:...`). `@` and profile URLs accepted. |
| `maxItemsPerHandle` | integer | Cap on records per handle (posts / followers / following / pack members). Default 100. |
| `feedUris` | string\[] | Custom-feed AT-URIs — only for `feed` mode. If empty, the actor discovers feeds published by `handles`. |

#### Example input

```json
{
  "mode": "followers",
  "handles": ["bsky.app", "jay.bsky.team"],
  "maxItemsPerHandle": 1000
}
```

Get an account's recent posts with engagement:

```json
{ "mode": "posts", "handles": ["bsky.app"], "maxItemsPerHandle": 200 }
```

Pull every member of an account's starter packs:

```json
{ "mode": "starterpack", "handles": ["pfrazee.com"], "maxItemsPerHandle": 500 }
```

Scrape a custom feed:

```json
{
  "mode": "feed",
  "feedUris": ["at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"],
  "maxItemsPerHandle": 100
}
```

### Output

Every dataset row has the same top-level shape. `recordType` tells you which detail
object (`profile`, `post`, or `starterpack`) is populated; the others are `null`.

#### Profile record (profiles / followers / following / starter-pack members)

```json
{
  "recordType": "profile",
  "mode": "followers",
  "sourceActor": "bsky.app",
  "scrapedAt": "2026-08-10T22:14:03.000Z",
  "status": "ok",
  "error": null,
  "profile": {
    "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
    "handle": "bsky.app",
    "displayName": "Bluesky",
    "description": "official bluesky account…",
    "avatar": "https://cdn.bsky.app/…",
    "banner": "https://cdn.bsky.app/…",
    "followersCount": 34467337,
    "followsCount": 11,
    "postsCount": 806,
    "createdAt": "2023-04-12T04:53:57.057Z",
    "indexedAt": "2025-…",
    "labels": [],
    "verified": false,
    "profileUrl": "https://bsky.app/profile/bsky.app"
  },
  "post": null,
  "starterpack": null
}
```

> Note: `followers` / `following` return Bluesky's basic profile view, so `followersCount`
> and `postsCount` are `null` for those rows. Run `profiles` mode on the handles to enrich
> them with full counts.

#### Post record (posts / feed)

```json
{
  "recordType": "post",
  "mode": "posts",
  "sourceActor": "bsky.app",
  "status": "ok",
  "post": {
    "uri": "at://did:plc:z72…/app.bsky.feed.post/3msqpusnigc2t",
    "cid": "bafy…",
    "rkey": "3msqpusnigc2t",
    "webUrl": "https://bsky.app/profile/bsky.app/post/3msqpusnigc2t",
    "text": "We also added thread numbering…",
    "createdAt": "2026-08-06T…",
    "langs": ["en"],
    "author": { "did": "did:plc:z72…", "handle": "bsky.app", "displayName": "Bluesky" },
    "replyCount": 52,
    "repostCount": 215,
    "likeCount": 1489,
    "quoteCount": 8,
    "bookmarkCount": 3,
    "isReply": false,
    "isRepost": false,
    "hasMedia": true,
    "labels": []
  }
}
```

#### Starter-pack record (starterpack)

```json
{
  "recordType": "starterpack",
  "mode": "starterpack",
  "sourceActor": "pfrazee.com",
  "starterpack": {
    "uri": "at://…/app.bsky.graph.starterpack/3m4j33zq7sz2m",
    "name": "Great people to be moots with",
    "creatorHandle": "pfrazee.com",
    "listUri": "at://…/app.bsky.graph.list/3m4j33zj5u62u",
    "joinedAllTimeCount": 412,
    "webUrl": "https://bsky.app/starter-pack/pfrazee.com/3m4j33zq7sz2m"
  }
}
```

A `starterpack` run emits one `starterpack` record per pack **plus** a `profile` record for
each pack member, so you get the summary and the full membership in one dataset.

#### Errors are data, not failures

If a handle is deleted, suspended, or never existed, you get a row with
`"status": "not_found"` instead of a crashed run:

```json
{ "recordType": "error", "sourceActor": "gone.bsky.social", "status": "not_found", "error": "Profile not found" }
```

### Pricing

Pay-per-result (pay-per-event): you're charged per record returned plus a small per-run
start fee. API-only compute keeps costs low. See the pricing tab for current rates.

### Legal & compliance

This actor reads **only public data** through Bluesky's official public AT Protocol
endpoints — the same data any logged-out visitor can see at bsky.app. It performs no
login, accesses no private content, and takes no authenticated actions. Respect Bluesky's
Terms of Service and applicable data-protection law (GDPR/CCPA) when processing personal data.

### FAQ

**Do I need a Bluesky account?** No. It uses the public API.

**Can it scrape private accounts?** Bluesky has no private profiles on the public AppView;
only public data is available.

**How do I get a feed's AT-URI?** Open the feed on bsky.app — or leave `feedUris` empty and
pass the creator's handle in `handles` to auto-discover their feeds.

**What's the max?** `maxItemsPerHandle` controls the cap. Pagination handles accounts with
millions of followers; set the cap to sample large accounts cheaply.

***

Keywords: Bluesky scraper, Bluesky API, AT Protocol data, Bluesky analytics, Bluesky followers export, Bluesky posts scraper, social media scraper, bsky.app data extraction.

# Actor input Schema

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

Which dataset to extract. profiles = account details; posts = an account's posts; followers = who follows an account; following = who an account follows; starterpack = members of an account's starter packs; feed = posts from a custom feed.

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

Bluesky handles (e.g. bsky.app) or DIDs (did:plc:...). @ and profile URLs are accepted too. Required for every mode except pure feed-URI runs.

## `maxItemsPerHandle` (type: `integer`):

Cap on records collected per handle (posts, followers, following, or starter-pack members). Ignored for the profiles mode. Use a small number to sample large accounts cheaply.

## `feedUris` (type: `array`):

Custom-feed generator AT-URIs, e.g. at://did:plc:.../app.bsky.feed.generator/whats-hot. Only used when mode = feed. If empty, the actor discovers feeds published by the handles above.

## Actor input object example

```json
{
  "mode": "profiles",
  "handles": [
    "bsky.app",
    "jay.bsky.team"
  ],
  "maxItemsPerHandle": 100,
  "feedUris": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"
  ]
}
```

# Actor output Schema

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

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

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

```

## MCP server setup

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