# Bluesky Scraper: Posts, Profiles, Followers, Threads, Likes (`ctriolab/bluesky-scraper`) Actor

Scrape Bluesky without login or API keys: author feeds, post threads and replies, profiles, followers and following, likes, reposts and quotes. Accepts bsky.app URLs, handles and DIDs. Clean flat rows with engagement counts, links, mentions, hashtags and images.

- **URL**: https://apify.com/ctriolab/bluesky-scraper.md
- **Developed by:** [Ctrio Lab](https://apify.com/ctriolab) (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.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.
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?

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, Followers, Threads, Likes

Scrape **Bluesky** without a login, cookies or API keys. This Actor reads the public AT Protocol AppView API that the Bluesky web app itself uses for logged-out visitors, and returns clean, flat rows that are ready for Excel, Google Sheets, BI tools or an AI agent.

**What you can get**

| Mode | Input | One row per |
|---|---|---|
| Author feeds | handles, DIDs or profile URLs | post (optionally reposts) |
| Post threads | post URLs or `at://` URIs | post and each reply (with depth) |
| Profiles | handles, DIDs or profile URLs | profile with follower, following and post counts |
| Followers / Following | handles, DIDs or profile URLs | profile |
| Likes / Reposts of a post | post URLs | profile (likes include `likedAt`) |
| Quotes of a post | post URLs | quoting post |

**Why this Actor**

- No Bluesky account needed, nothing to configure. Paste `bsky.app` links, `@handles` or `did:plc:` DIDs in any mix.
- Clean fields: text, created time, author, reply / repost / like / quote counts, languages, links, mentions (DID and handle), hashtags, images with alt text, video, link card, quoted post, reply parent and root.
- Caps per input and per run, so you never pay for more than you asked for.
- Polite and stable: one connection, automatic retries with exponential backoff on HTTP 429 and 5xx, and it waits as long as Bluesky asks (`Retry-After`, `RateLimit-*` headers).
- Respects users: accounts and posts that ask not to be shown to logged-out people are skipped (see "Data source and terms").
- Fails fast on bad input with a clear message, before any request or charge.

### How to use it

1. Fill in one or more sections: **Author feeds**, **Profiles**, **Followers of**, **Following of**, **Post threads**, **Likes / Reposts / Quotes of posts**. You can combine them in one run.
2. Set **Max results per input** and **Max results in total**.
3. Click **Start**, then download the dataset as JSON, CSV, Excel or use the API.

#### Input examples

Last 200 posts (with replies and reposts) from three accounts:

```json
{
  "authorFeeds": ["bsky.app", "@jay.bsky.team", "https://bsky.app/profile/pfrazee.com"],
  "authorFeedFilter": "posts_with_replies",
  "includeReposts": true,
  "maxItemsPerInput": 200
}
```

A post and its replies, plus who liked and reposted it:

```json
{
  "postUrls": ["https://bsky.app/profile/bsky.app/post/3mwolmfws5k2r"],
  "threadDepth": 10,
  "likesOf": ["https://bsky.app/profile/bsky.app/post/3mwolmfws5k2r"],
  "repostsOf": ["https://bsky.app/profile/bsky.app/post/3mwolmfws5k2r"],
  "maxItemsPerInput": 500
}
```

First 1,000 followers of an account, with their own follower counts:

```json
{
  "followersOf": ["bsky.app"],
  "enrichProfiles": true,
  "maxItemsPerInput": 1000
}
```

Profiles from a mixed list:

```json
{
  "profiles": ["bsky.app", "did:plc:ragtjsm2j2vknwkz3zp4oxrd", "https://bsky.app/profile/atproto.com"]
}
```

#### Accepted identifiers

- Handles: `bsky.app`, `@name.bsky.social`, custom domains like `nytimes.com`. A bare name like `jay` is read as `jay.bsky.social`.
- DIDs: `did:plc:...`, `did:web:...`
- Profile URLs: `https://bsky.app/profile/<handle or DID>`
- Post URLs: `https://bsky.app/profile/<handle or DID>/post/<id>` or `at://<did>/app.bsky.feed.post/<id>`

### Output

Every row has `type` (`post` or `profile`), `source` (`author_feed`, `thread`, `profile`, `followers`, `following`, `likes`, `reposts`, `quotes`), the `input` it came from and `scrapedAt`.

Post row example:

```json
{
  "type": "post",
  "postType": "reply",
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv462yda4k2l",
  "id": "3mv462yda4k2l",
  "url": "https://bsky.app/profile/bsky.app/post/3mv462yda4k2l",
  "text": "Thank you for coming to today's Apple event! You can find these and more Apple products at your nearest grocery store, farmers' market, or orchard.",
  "createdAt": "2026-09-09T18:27:44.595Z",
  "authorHandle": "bsky.app",
  "authorDisplayName": "Bluesky",
  "authorDid": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "replyCount": 4,
  "repostCount": 4,
  "likeCount": 220,
  "quoteCount": 3,
  "bookmarkCount": 8,
  "langs": ["en"],
  "links": ["https://go.bsky.app/AjuvkDc"],
  "mentions": [],
  "hashtags": [],
  "images": [],
  "imageCount": 0,
  "video": null,
  "externalLink": { "uri": "https://go.bsky.app/AjuvkDc", "title": "Apple Experts", "description": "Join the conversation", "thumb": "https://cdn.bsky.app/img/feed_thumbnail/plain/..." },
  "quotedPost": null,
  "replyParentUri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv45zmynys2l",
  "replyRootUri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv3zcjaijk22",
  "labels": [],
  "source": "author_feed",
  "input": "bsky.app",
  "scrapedAt": "2026-10-03T09:28:27.541Z"
}
```

Images come as `images: [{ url, thumb, alt, width, height }]`, videos as `video: { playlist, thumbnail, alt }`, quoted posts as `quotedPost: { uri, url, text, authorHandle }`. Other post fields: `cid`, `indexedAt`, `authorAvatar`, `authorUrl`, `replyParentUrl`, `replyRootUrl`, `threadRootUri` and `threadDepth` (threads, 0 = the post you entered), `repostedByHandle`, `repostedByDid`, `repostedAt` (reposts in author feeds), `subjectPostUri`, `subjectPostUrl` (quotes).

Profile row example:

```json
{
  "type": "profile",
  "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "handle": "bsky.app",
  "url": "https://bsky.app/profile/bsky.app",
  "displayName": "Bluesky",
  "description": "official Bluesky account (check username) ...",
  "followersCount": 35128563,
  "followsCount": 15,
  "postsCount": 865,
  "createdAt": "2023-04-12T04:53:57.057Z",
  "verified": false,
  "labels": [],
  "source": "profile",
  "input": "bsky.app"
}
```

Follower, following, liker and reposter rows also have `relation`, `subjectHandle`, `subjectDid`, `subjectPostUri`, `subjectPostUrl` and `likedAt` where relevant. Their counts are `null` unless **Add counts and bio to follower / like lists** is on.

A run summary (items per input, skipped inputs with reasons, HTTP statistics) is saved in the key-value store as `RUN_SUMMARY`.

### Pricing

Pay per result: **$1.00 per 1,000 results** plus a tiny Actor start fee. One row in the dataset = one result. Duplicates within an input, skipped hidden content and failed inputs are not charged. Invalid input stops the run before anything is charged.

### Limits you should know

- **No keyword search.** Bluesky currently switches post search off for logged-out clients. This Actor does not log in or use another host to get around that, so keyword search is not offered. To follow a topic, track the accounts that post about it with author feeds.
- Threads contain the replies Bluesky shows on the post page; very large threads are trimmed by Bluesky.
- Deleted posts and accounts, blocked replies and hidden content are not returned.

### Data source and terms

- Data comes only from Bluesky's public AppView API (`public.api.bsky.app`, as listed in the Bluesky developer docs "API Hosts and Auth"). These endpoints are documented for unauthenticated use. The Actor never logs in, never uses accounts, cookies or proxies, and never writes anything to Bluesky (no likes, follows or posts).
- Bluesky's Terms of Service do not prohibit reading public data through the API. The Bluesky Developer Guidelines forbid spam and automated interactions; this Actor is read-only.
- Rate limits are respected: requests are sent one at a time with a pause, `Retry-After` and `RateLimit-*` headers are honoured, and blocked requests (HTTP 403) are never retried or disguised.
- Accounts and posts labelled `!no-unauthenticated` (users who asked Bluesky not to show their content to logged-out people), `!hide` or `!takedown` are skipped, including replies under them.
- Results are a snapshot at `scrapedAt`. If a user deletes a post or account later, you should delete it from your copy too; re-run the Actor to refresh.
- You are responsible for using the data lawfully (for example GDPR / CCPA for personal data) and for respecting user preferences. Bluesky has proposed "User Intents" signals for AI training and bulk datasets; check them if you build such datasets.

### FAQ

**Do I need a Bluesky account or app password?** No.

**Can I search posts by keyword?** Not at the moment. Bluesky switched post search off for logged-out clients and we do not work around it. Use author feeds, threads, quotes and likes instead.

**How fast is it?** Around 1,000 posts or profiles in 5 to 10 seconds for feeds and follower lists (100 per request).

**Why are some accounts skipped?** The account was not found (deleted, deactivated, mistyped) or its owner asked Bluesky to hide it from logged-out visitors. The reason is in the log and in `RUN_SUMMARY`.

**Can I monitor new posts?** Yes. Schedule the Actor with author feeds and a small `maxItemsPerInput`.

**Can I use it from an AI agent (MCP)?** Yes, call it through the Apify MCP server or the Apify API like any other Actor.

**Something is wrong or missing?** Open an issue on the Actor page with the run link and we will look at it.

# Actor input Schema

## `authorFeeds` (type: `array`):

Posts from these accounts, newest first. Use handles (bsky.app, @name.bsky.social), DIDs (did:plc:...) or profile URLs (https://bsky.app/profile/...).

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

Same tabs as on a Bluesky profile page.

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

Also save posts the account reposted (postType = repost, with repostedBy fields).

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

One row per account with bio, follower, following and post counts. Handles, DIDs or profile URLs.

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

List the followers of these accounts (newest first). Handles, DIDs or profile URLs.

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

List the accounts these accounts follow. Handles, DIDs or profile URLs.

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

Post URLs (https://bsky.app/profile/<handle>/post/<id>) or at:// URIs. Saves the post and its replies.

## `includeThreadRoot` (type: `boolean`):

Save the post you entered as the first row (threadDepth 0). Turn off to get replies only.

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

How many levels of replies to follow. 1 = direct replies only.

## `likesOf` (type: `array`):

List the accounts that liked these posts, with likedAt.

## `repostsOf` (type: `array`):

List the accounts that reposted these posts.

## `quotesOf` (type: `array`):

List the posts that quote these posts.

## `enrichProfiles` (type: `boolean`):

Followers, following, likers and reposters come with handle, name, bio and avatar. Turn this on to also get followersCount, followsCount and postsCount (one extra request per 25 accounts, slower).

## `maxItemsPerInput` (type: `integer`):

Cap for each query, account or post. 0 = no cap.

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

Cap for the whole run. 0 = no cap (the run still stops at your max charge).

## `maxRetries` (type: `integer`):

Retries on HTTP 429 and 5xx with exponential backoff. The actor waits as long as Bluesky asks (Retry-After and RateLimit headers).

## Actor input object example

```json
{
  "authorFeeds": [
    "bsky.app"
  ],
  "authorFeedFilter": "posts_no_replies",
  "includeReposts": false,
  "profiles": [],
  "followersOf": [],
  "followingOf": [],
  "postUrls": [],
  "includeThreadRoot": true,
  "threadDepth": 10,
  "likesOf": [],
  "repostsOf": [],
  "quotesOf": [],
  "enrichProfiles": false,
  "maxItemsPerInput": 20,
  "maxItems": 1000,
  "maxRetries": 6
}
```

# Actor output Schema

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

All saved posts and profiles, one row each.

## `posts` (type: `string`):

Post rows (author feeds, threads, quotes).

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

Profile rows (profiles, followers, following, likers, reposters).

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

Items, skipped inputs, errors and HTTP statistics per input.

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ctriolab/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/pJ00IifADEojadMgS/builds/130sLNBIzwMJtZlFO/openapi.json
