# Bluesky Scraper (`donjuan_mime/bluesky-scraper`) Actor

Scrape Bluesky posts by search, profile, feed, or link: text, likes, reposts, replies, images, links, hashtags, and author stats. No login needed.

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

## Pricing

$0.50 / 1,000 items

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

## Bluesky Scraper: posts, replies, and author stats from search, profiles, and feeds

Bluesky Scraper collects public Bluesky posts from search queries, profiles, custom feeds, hashtag pages, and single post links. For every post you get the text, publish time, language, likes, reposts, replies, and quotes, the images, video, link card, and quoted post, the hashtags, mentions, and links in the text, and the author's profile with follower and post counts. Turn on replies to get each post's full reply thread, one item per reply, with its parent and depth.

You don't need a Bluesky account, an API key, or any code. Add search terms, profiles, or links, press **Start**, and download the results as JSON, CSV, Excel, XML, or HTML. Or pull them into your app through the Apify API.

### What can you do with Bluesky data?

- **Monitor a topic or brand.** Search for your product, a hashtag, or a phrase and see who talks about it and how people react.
- **Follow accounts.** Collect every post from a list of journalists, politicians, companies, or competitors, with likes and reposts for each.
- **Analyze conversations.** Pull the full reply tree of a post to study how a discussion unfolds and who joins it.
- **Find voices worth following.** Every post comes with the author's followers and post count, so you can rank accounts by reach.
- **Track custom feeds.** Bluesky's custom feeds curate posts on a topic. Scrape one to see what it surfaces right now.
- **Build datasets for research.** Post text, language tags, and timestamps make clean input for topic, sentiment, or language analysis.

### What data does Bluesky Scraper extract?

| Group | Fields |
|---|---|
| Post | type (post or reply), AT URI, CID, bsky.app URL, text, publish time (ISO 8601, UTC), language |
| Engagement | likes, reposts, replies, quotes |
| Author | DID, handle, display name, profile URL, avatar, bio, followers, following, post count, join date |
| Media | images (full size, thumbnail, alt text), video (HLS playlist, thumbnail), link card (URL, title, description, thumbnail) |
| Quoted post | URI, URL, text, author handle |
| Text entities | hashtags, mentions (DID and handle), links |
| Threads | the post a reply answers, the thread's first post, and for collected replies the post they came from and their depth |

### How to scrape Bluesky

1. Open the actor and go to the **Input** tab.
2. Add search queries, profiles, or Bluesky links. You can mix all three.
3. Set **Posts per source**, and turn on **Include replies** if you want the reply threads.
4. Press **Start**. When the run finishes, open the **Output** tab to preview the table, or press **Export** to download the data.

### Input

| Field | Type | Description |
|---|---|---|
| `searchQueries` | array of strings | Search terms. Bluesky's search syntax works: `#ukraine`, `from:bsky.app`, `"exact phrase"`. |
| `sort` | string | `latest` or `top` for search results. |
| `since`, `until` | string | Limit search to posts created in this window. Use `2026-10-01` or `2026-10-01T00:00:00Z`. |
| `lang` | string | Limit search to one language, for example `en`, `de`, or `uk`. |
| `profiles` | array of strings | Handles (`bsky.app`), @handles, DIDs, or profile links. |
| `startUrls` | array | Links to posts, profiles, custom feeds, search pages, or hashtag pages on bsky.app, or `at://` URIs. |
| `maxItems` | integer | The maximum number of posts from each search query, profile, or start URL. |
| `includeReplies` | boolean | Also collect the reply thread of every post that has replies. |
| `maxRepliesPerPost` | integer | The maximum number of replies from each thread. |
| `includeAuthorStats` | boolean | Add each author's bio, followers, following, post count, and join date. On by default. |
| `blueskyHandle`, `blueskyAppPassword` | string | Optional login, needed only to search past the first 100 posts. Use an app password, never your main password. |
| `proxyCountryCode` | string | A two-letter country code such as `US` to use Apify residential proxy in that country. `None` uses Apify datacenter proxy. |

#### Input example

```json
{
  "searchQueries": ["#golang", "climate lang:de"],
  "sort": "latest",
  "since": "2026-10-01",
  "profiles": ["bsky.app", "@jay.bsky.team"],
  "startUrls": [
    { "url": "https://bsky.app/profile/bsky.app/feed/whats-hot" },
    { "url": "https://bsky.app/hashtag/ukraine" }
  ],
  "maxItems": 100,
  "includeReplies": true,
  "maxRepliesPerPost": 50,
  "includeAuthorStats": true,
  "proxyCountryCode": "None"
}
```

### Output

Each post or reply becomes one item in the dataset. The **Overview** table shows the essentials, **Authors** lists who wrote each post, and **Threads** lays replies out with their depth and parent.

#### Output example

A real post from the official Bluesky account. Long links are shortened.

```json
{
  "type": "post",
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mk3sdiixhs2n",
  "cid": "bafyreigfkidcu5cvf5w72mhhirpoa2r5ulm2yi5dp3oagnup5udhibnmfe",
  "url": "https://bsky.app/profile/bsky.app/post/3mk3sdiixhs2n",
  "text": "Read David Imel's summary of ATmosphereConf, a recent gathering of folks building on the AT Protocol: ...",
  "createdAtISO": "2026-04-22T15:20:37.187Z",
  "lang": "en",
  "likeCount": 1277,
  "repostCount": 154,
  "replyCount": 108,
  "quoteCount": 24,
  "author": {
    "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
    "handle": "bsky.app",
    "displayName": "Bluesky",
    "url": "https://bsky.app/profile/bsky.app",
    "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r2...",
    "description": "official Bluesky account (check username👆)\n\nBugs, feature requests, feedback: support@bsky.app",
    "followersCount": 35296124,
    "followsCount": 22,
    "postsCount": 880,
    "createdAtISO": "2023-04-12T04:53:57.057Z"
  },
  "externalLink": {
    "url": "https://davidimel.substack.com/p/bluesky-is-looking-for-its-myspace",
    "title": "Bluesky is doubling down",
    "description": "Let's see if a feed-builder is it.",
    "thumb": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:z72i7h..."
  },
  "hashtags": [],
  "mentions": [],
  "links": ["https://davidimel.substack.com/p/bluesky-is-looking-for-its-myspace"],
  "input": "bsky.app"
}
```

A reply collected with **Include replies** also has:

```json
{
  "type": "reply",
  "replyTo": {
    "parentUri": "at://did:plc:.../app.bsky.feed.post/3my5hclvrs226",
    "rootUri": "at://did:plc:.../app.bsky.feed.post/3my5hclvrs226"
  },
  "threadOf": "at://did:plc:.../app.bsky.feed.post/3my5hclvrs226",
  "depth": 1
}
```

#### Output fields

| Field | Description |
|---|---|
| `type` | `post`, or `reply` for a post that answers another one. Replies show up in search and profiles too, not only in threads. |
| `uri`, `cid` | The post's AT Protocol URI and content hash. Use `uri` as a unique key. |
| `url` | Link to the post on bsky.app. |
| `text` | The post text. |
| `createdAtISO` | When the post was written, in UTC. |
| `lang` | The first language the author tagged the post with. Some posts have none. |
| `likeCount`, `repostCount`, `replyCount`, `quoteCount` | Engagement counts at the time of the run. |
| `author` | DID, handle, display name, profile URL, and avatar. With `includeAuthorStats`: bio, followers, following, post count, and join date. |
| `images`, `video`, `externalLink` | Media attached to the post. |
| `quote` | The post this post quotes. |
| `hashtags`, `mentions`, `links` | Entities in the text. |
| `replyTo` | For replies: the post answered (`parentUri`) and the thread's first post (`rootUri`). |
| `threadOf`, `depth` | For replies collected with `includeReplies`: the post whose thread they came from and how deep they sit in it. |
| `input` | The search query, profile, or start URL that found the item. |

### How many posts can I get?

- **Profiles and custom feeds** page through without limits we hit in testing: 400 posts from a feed in 4 requests.
- **Search** returns up to 100 posts per query without a login, because Bluesky only answers logged-out search one page deep. Add more specific queries, use `since` and `until`, or add a Bluesky handle and app password to go further.
- **Replies** come from Bluesky's thread view, up to 20 levels deep. Bluesky leaves out blocked and deleted replies.

The `lang`, `since`, and `until` filters apply to search results, not to replies collected with **Include replies**.

### How fast is it?

The actor talks to Bluesky's public API without a browser. In our tests, one search, two profiles, a custom feed, and a hashtag page gave 649 posts in 14 seconds, author stats included. It runs in 128 MB of memory.

### How much does it cost?

You pay per item in the dataset. Posts and replies count the same. With **Include replies** on, a run can return many more items than **Posts per source**, so set **Replies per post** to cap it.

### Export and integrations

The results go into an Apify dataset. From there you can download them as JSON, CSV, Excel, XML, HTML, or RSS, send them to Google Sheets, Slack, Zapier, Make, and other tools through Apify integrations, schedule runs to track a topic over time, or read them through the Apify API.

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

### FAQ

#### Do I need a Bluesky account?

No. Everything works logged out. A login helps in one case: search past the first 100 posts per query.

#### Is my app password safe?

The input field is marked secret, so Apify encrypts it and hides it in the run. The actor uses it only to open a session with Bluesky. Create a separate app password for the actor in Bluesky's settings and revoke it any time.

#### Why does a search return at most 100 posts?

Bluesky answers logged-out search one page deep. Split the topic into narrower queries or date windows, or add a login.

#### Why are some fields missing?

Bluesky sends only what a post has. A post without images has no `images`, and a post without a language tag has no `lang`. Author stats are missing when an account was deleted or deactivated.

#### Can I scrape private or blocked content?

No. The actor reads only what Bluesky's public API returns. Blocked and deleted replies don't show up.

#### Is it legal to scrape Bluesky?

The actor collects public data through Bluesky's public API. Posts and profiles still contain personal data, so make sure you have a legitimate reason to collect them and follow the GDPR and other laws that apply to you. If you're not sure, ask a lawyer.

### Disclaimer

This actor is provided as is, without warranty. You're responsible for how you use it and for following Bluesky's terms of service and the laws that apply to you. Bluesky isn't affiliated with this actor.

# Actor input Schema

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

Search Bluesky posts for these terms. Bluesky's search syntax works, for example "#ukraine", "from:bsky.app", or ""exact phrase"". Without a Bluesky login, search returns up to 100 posts per query.

## `sort` (type: `string`):

Order of search results.

## `since` (type: `string`):

Only search posts created on or after this date, as YYYY-MM-DD or 2026-10-01T00:00:00Z.

## `until` (type: `string`):

Only search posts created before this date, as YYYY-MM-DD or 2026-10-15T00:00:00Z.

## `lang` (type: `string`):

Only search posts in this language, as a two-letter code such as en, de, or uk.

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

Profiles to take posts from: a handle (bsky.app), an @handle, a DID, or a https://bsky.app/profile/... link. Reposts are left out; the author's own replies are kept.

## `startUrls` (type: `array`):

Bluesky links: a post, a profile, a custom feed (https://bsky.app/profile/bsky.app/feed/whats-hot), a search page, or a hashtag page.

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

The maximum number of posts from each search query, profile, or start URL. Replies collected with "Include replies" come on top.

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

Also scrape the reply thread of every post that has replies. Each reply is its own dataset item with its parent and depth.

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

The maximum number of replies to scrape from each post's thread.

## `includeAuthorStats` (type: `boolean`):

Add each author's bio, followers, follows, post count, and join date. Costs one extra request per 25 new authors.

## `blueskyHandle` (type: `string`):

Optional. Your Bluesky handle, used with an app password to search past the first 100 posts. Everything else works without it.

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

Optional. An app password from Bluesky Settings > Privacy and security > App passwords. Never use your main password.

## `proxyCountryCode` (type: `string`):

Two-letter country code (US, DE, ...) to use Apify residential proxy in that country. "None" uses Apify datacenter proxy.

## Actor input object example

```json
{
  "searchQueries": [
    "golang"
  ],
  "sort": "latest",
  "maxItems": 100,
  "includeReplies": false,
  "maxRepliesPerPost": 50,
  "includeAuthorStats": true,
  "proxyCountryCode": "None"
}
```

# Actor output Schema

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

One item per Bluesky post or reply.

# 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 = {
    "searchQueries": [
        "golang"
    ],
    "maxItems": 100,
    "maxRepliesPerPost": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("donjuan_mime/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 = {
    "searchQueries": ["golang"],
    "maxItems": 100,
    "maxRepliesPerPost": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("donjuan_mime/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 '{
  "searchQueries": [
    "golang"
  ],
  "maxItems": 100,
  "maxRepliesPerPost": 50
}' |
apify call donjuan_mime/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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