# Bluesky Scraper: Posts, Search, Profiles, Followers (`fernandosord/bluesky-scraper`) Actor

Scrape Bluesky without a login: posts from any profile, keyword and hashtag search, single posts with their reply threads, and profiles with follower, following and post counts. Each post has text, likes, reposts, replies, quotes, hashtags, mentions, links, full-size images and quoted posts.

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

## Pricing

from $1.00 / 1,000 post or profiles

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

This Bluesky scraper collects posts from any Bluesky profile, keyword or hashtag search results, single posts with their reply threads, and profiles with follower, following and post counts. It reads Bluesky's public AT Protocol API, so it needs no Bluesky account, app password or cookie.

- **Profiles:** paste handles (`bsky.app`, `@jay.bsky.team`), DIDs or `bsky.app/profile/...` links. Posts come newest first, with reposts and replies on or off.
- **Search:** keywords, phrases, hashtags like `#python`, and Bluesky's own search operators such as `from:bsky.app` or `domain:github.com`. Thousands of results per query, newest first.
- **Threads:** paste post URLs, or turn on replies for every post collected, to get the conversation underneath.
- **Profiles mode:** one row per account with bio, avatar, follower, following and post counts, blue check status and join date. Followers and Following modes list who follows an account, or who it follows.

### What data you get

Posts mode returns one item per post:

| Field | What it holds |
|---|---|
| `text`, `createdAt`, `url`, `uri` | The post, when it was written, its bsky.app link and AT URI |
| `authorHandle`, `authorDisplayName`, `authorDid`, `authorAvatar` | Who wrote it |
| `likeCount`, `repostCount`, `replyCount`, `quoteCount`, `bookmarkCount` | Engagement counts |
| `langs`, `hashtags`, `mentions`, `links` | Languages, hashtags, mentioned accounts (handle and DID) and every link in the post |
| `images` | Full-size image URL, thumbnail, alt text, width and height for each picture |
| `video`, `external` | Video playlist and thumbnail, link card with title and description |
| `quotedPost`, `isQuote` | The quoted post with its author, text and counts |
| `isReply`, `replyParentUri`, `replyRootUri` | Whether it is a reply and what it replies to |
| `isRepost`, `repostedByHandle`, `repostedAt` | For reposts collected from a profile |
| `sourceType`, `source`, `threadRootUri`, `replyDepth` | Where the item came from (profile, search, post or reply) |

Profiles, Followers and Following modes return one item per account: `handle`, `displayName`, `did`, `url`, `description` (bio), `avatar`, `banner`, `followersCount`, `followsCount`, `postsCount`, `createdAt`, `verified`, `verifiedBy`, `pinnedPostUri`, `relation` and `relatedTo`.

### How to use it

1. Pick **What to scrape**: Posts, Profiles, Followers of each profile, or Accounts each profile follows.
2. Add profiles, search queries or post URLs.
3. Set **Max posts per profile or search query**, and optionally **Only posts newer than** (`2026-10-01` or `7 days`).
4. Run it and download JSON, CSV or Excel, or call it from the API, Make, Zapier, n8n or an AI agent through the Apify MCP server.

#### Example input

```json
{
  "mode": "posts",
  "profiles": ["bsky.app"],
  "searchQueries": ["#python"],
  "postUrls": ["https://bsky.app/profile/bsky.app/post/3myx7vphq3s2j"],
  "maxPostsPerSource": 20,
  "onlyPostsNewerThan": "30 days",
  "includeReposts": false,
  "includeThreadReplies": false
}
```

#### Example output (one post from a real run, image list shortened)

```json
{
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3myx7vphq3s2j",
  "url": "https://bsky.app/profile/bsky.app/post/3myx7vphq3s2j",
  "authorHandle": "bsky.app",
  "authorDisplayName": "Bluesky",
  "authorDid": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "text": "getting our Halloween decorations set up",
  "createdAt": "2026-10-28T16:58:02.827Z",
  "likeCount": 3571,
  "repostCount": 378,
  "replyCount": 223,
  "quoteCount": 125,
  "bookmarkCount": 202,
  "langs": ["en"],
  "hashtags": [],
  "mentions": [],
  "links": [],
  "embedType": "images",
  "images": [
    {
      "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreienhu3nspbm2f2v5q74arjtlvltdlgruzjvo6ndlggksp6l6zlf5a",
      "thumb": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreienhu3nspbm2f2v5q74arjtlvltdlgruzjvo6ndlggksp6l6zlf5a",
      "alt": "Gravestone reading:\n\nHere lies StumbleUpon\n2001 - 2018\n\"Remember websites?\"",
      "width": 570,
      "height": 494
    }
  ],
  "quotedPost": null,
  "isQuote": false,
  "isReply": false,
  "isRepost": false,
  "source": "bsky.app",
  "sourceType": "profile",
  "scrapedAt": "2026-10-30T02:26:39.856Z"
}
```

#### Example output in Profiles mode (real run, shortened)

```json
{
  "did": "did:plc:oky5czdrnfjpqslsw2a5iclo",
  "handle": "jay.bsky.team",
  "displayName": "Jay 🦋",
  "url": "https://bsky.app/profile/jay.bsky.team",
  "followersCount": 594399,
  "followsCount": 3994,
  "postsCount": 4185,
  "createdAt": "2022-11-17T06:31:40.296Z",
  "verified": true,
  "verifiedBy": ["bsky.app"],
  "relation": "profile"
}
```

### How much does it cost?

You pay per item saved: **$0.001 per post, reply or profile**, plus **$0.0005 once per run**. 1,000 posts cost $1.00 plus the start fee. Apify's free plan credit of $5 a month covers about 5,000 items.

Inputs that fail (a handle that does not exist, a deleted post, a link that is not Bluesky) cost nothing. They are listed with the reason in the `SUMMARY` record of the run's key-value store, next to the item count for every input.

### Use cases

- **Brand and topic monitoring:** schedule a search for your brand, product or hashtag with **Only posts newer than** set to `1 day`.
- **Audience research:** read the replies under a viral post, or list the followers of a competitor with their follower counts and bios.
- **Creator and lead lists:** look up a list of handles in Profiles mode to get follower counts, bios and verification in one table.
- **Datasets for analysis or AI:** collect posts with language codes, hashtags and links for sentiment analysis, topic modelling or RAG.

### FAQ

**Does search work without a login?**
Yes. Bluesky refuses search on its public API host, so the Actor sends search to `api.bsky.app`, which answers without a login. Without a login Bluesky only serves the first page of results, so for **Latest** order the Actor moves the time window back to the oldest post it has and asks again. That is how one query returns thousands of posts. **Top** order cannot be paged this way and returns at most 100 posts per query.

**Why do hashtag searches look different in the log?**
Bluesky rejects a date filter combined with a `#hashtag` query, so the Actor sends `#python` as the word `python` plus a hashtag filter. Bluesky returns the same posts either way.

**Can it get likes or the list of people who reposted?**
Not in this version. It gets the counts (likes, reposts, quotes, replies, bookmarks), not the accounts behind them.

**How many followers can it list?**
Set **Max followers or follows per profile**. Each page holds 100 accounts, plus one lookup per 25 accounts for counts and bios when **Add counts and bio** is on. Accounts with millions of followers take a long time to list in full.

**What about deleted posts or accounts?**
Bluesky posts are public. A post URL that points to a deleted post, or a handle that no longer exists, is skipped, costs nothing and is listed in `SUMMARY` with the reason.

**What about rate limits?**
Bluesky limits requests per IP. The Actor answers HTTP 429 by waiting until the limit resets and retrying, up to six times per request. Turn on Apify Proxy only if you see repeated retries in the log.

**Is this legal?**
It reads public data through Bluesky's documented public API. You are responsible for how you use it, especially personal data, under laws like GDPR.

### Other Actors by this developer

- [Telegram Channel Scraper](https://apify.com/fernandosord/telegram-channel-scraper): posts, views and reactions from public Telegram channels, no login.
- [Substack Scraper](https://apify.com/fernandosord/substack-scraper-posts): posts, likes, comments and full text from any Substack newsletter.
- [Google News Scraper](https://apify.com/fernandosord/google-news-rss-scraper): headlines by keyword, topic or country with the real publisher URL.
- [Google Trends Scraper](https://apify.com/fernandosord/google-trends-reliable): interest over time, by region and related searches.
- [Website Change Monitor](https://apify.com/fernandosord/website-change-monitor): get told when a web page changes.

# Actor input Schema

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

Posts: posts from profiles, search queries and post URLs (one item per post). Profiles: one item per profile with follower, following and post counts. Followers or Following: one item per account that follows, or is followed by, each profile.

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

Handles (bsky.app, @jay.bsky.team, or just jay for jay.bsky.social), DIDs (did:plc:...) or profile URLs (https://bsky.app/profile/bsky.app). In Posts mode the Actor reads each profile's posts, newest first.

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

Keywords, phrases in quotes or hashtags (#python) to search across all of Bluesky. Bluesky's search syntax works too, for example from:bsky.app or domain:github.com.

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

Single posts, as https://bsky.app/profile/<handle>/post/<id> links or at:// URIs. Turn on thread replies below to also get the replies under them.

## `maxPostsPerSource` (type: `integer`):

The Actor stops at this many posts for each profile and each search query. Replies fetched with thread replies are counted separately.

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

Hard cap on items saved by the whole run, posts, replies and profiles together. 0 means no cap.

## `onlyPostsNewerThan` (type: `string`):

Optional. A date (2026-01-31) or a relative period (7 days, 3 months). Older posts are skipped and paging stops. Reposts count from the time they were reposted.

## `includeReplies` (type: `boolean`):

When on, posts the profile wrote as replies to other people are included. Off by default, so you get the profile's own top-level posts.

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

When on, posts the profile reposted are included, with isRepost and the repost time.

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

Latest returns posts newest first and can page through thousands of results. Top returns Bluesky's most engaging matches, at most 100 per query.

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

Optional two-letter language code (en, ja, pt, de) to keep only search results in that language.

## `includeThreadReplies` (type: `boolean`):

Adds the reply thread under every post the Actor collects, as extra items marked sourceType reply. Costs one extra request per post that has replies, and every reply is an item.

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

Cap on replies saved under each post when thread replies are on, nested replies included.

## `maxProfilesPerAccount` (type: `integer`):

In Followers or Following mode, the Actor stops at this many accounts for each input profile. Bluesky lists the most recent follows first.

## `includeProfileCounts` (type: `boolean`):

Bluesky's follower lists leave out follower, following and post counts. When on, the Actor looks them up (one extra request per 25 accounts).

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

Optional. Bluesky's public API works without a proxy. Turn on Apify Proxy only if you see repeated HTTP 429 retries in the log.

## Actor input object example

```json
{
  "mode": "posts",
  "profiles": [
    "bsky.app"
  ],
  "maxPostsPerSource": 20,
  "maxItems": 1000,
  "includeReplies": false,
  "includeReposts": false,
  "searchSort": "latest",
  "includeThreadReplies": false,
  "maxRepliesPerPost": 50,
  "maxProfilesPerAccount": 200,
  "includeProfileCounts": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One item per post in Posts mode, newest first for each profile or search query.

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

One item per profile in the Profiles, Followers and Following modes.

# 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 = {
    "mode": "posts",
    "profiles": [
        "bsky.app"
    ],
    "maxPostsPerSource": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fernandosord/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 = {
    "mode": "posts",
    "profiles": ["bsky.app"],
    "maxPostsPerSource": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("fernandosord/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 '{
  "mode": "posts",
  "profiles": [
    "bsky.app"
  ],
  "maxPostsPerSource": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call fernandosord/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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