# Bluesky Scraper — Unlimited Posts | $1/1K (`datawell/bluesky-scraper`) Actor

Keyword search past the 100-post cap that stops other Bluesky scrapers. Posts, profiles, followers, threads, likes and reposts with engagement, hashtags and images. Follower counts the API omits are filled in. No login. For brand monitoring, social listening and research.

- **URL**: https://apify.com/datawell/bluesky-scraper.md
- **Developed by:** [datawell](https://apify.com/datawell) (community)
- **Categories:** Social media
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$1.00 / 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.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 — Posts, Profiles, Followers & Threads

Extract public data from **Bluesky** (bsky.app) without an account, an API key,
or a login. Search posts by keyword, pull account timelines, collect follower
lists, expand entire reply threads, and see exactly who liked or reposted a post.

One Actor covers twelve extraction modes. Results arrive as flat rows — ready for
CSV, Excel, JSON, or straight into your own pipeline via the API.

***

### Why this scraper

**Keyword search actually paginates.** Bluesky's public API returns HTTP 403 as
soon as you paginate a post search with a cursor — a
[known, still-open bug](https://github.com/bluesky-social/atproto/issues/3583).
Scrapers built the obvious way stop dead at 100 results. This one walks a moving
time window instead, so a search for 10,000 posts returns 10,000 posts.

**Follower counts are filled in.** Bluesky strips `followersCount` and
`postsCount` out of account searches, follower lists, and like lists — the exact
numbers you need to rank accounts by reach. This Actor fetches them back
automatically.

**No account, no tokens, no proxy setup.** Everything runs against Bluesky's
official public endpoints.

***

### What you can extract

| Mode | What you get |
|---|---|
| **Search posts by keyword** | Every public post matching a query — filter by language, date range, author, hashtag, linked domain or URL |
| **Posts from specific accounts** | An account's timeline: posts only, with replies, media only, or author threads |
| **Profile details** | Follower counts, bio, post totals, join date, verification status |
| **Search for accounts** | Find accounts by name or handle, with follower counts attached |
| **Followers** | Everyone who follows an account |
| **Following** | Every account someone follows |
| **Thread with all replies** | A whole conversation, flattened, with a depth column per reply |
| **Likes** | The accounts that liked a specific post |
| **Reposts** | The accounts that reposted a specific post |
| **Quote posts** | Posts quoting a specific post |
| **Custom feeds** | Posts from any feed generator |
| **Lists** | Posts from a curated list |

***

### Quick start

1. Pick a mode from the **What to scrape** dropdown.
2. Fill in the field it asks for — a search query, an account, or a post URL.
3. Set **Maximum results**.
4. Click **Start**.
5. Download from the **Dataset** tab as CSV, Excel, JSON, or XML.

***

### Example inputs

**Track a topic over the last week**

```json
{
  "mode": "searchPosts",
  "searchQuery": "climate policy",
  "lang": "en",
  "since": "2026-08-01T00:00:00Z",
  "sort": "latest",
  "maxItems": 5000
}
```

**Monitor brand mentions with links to your domain**

```json
{
  "mode": "searchPosts",
  "searchQuery": "acme",
  "domain": "acme.com",
  "maxItems": 1000
}
```

**Audit an account's audience**

```json
{
  "mode": "followers",
  "actors": ["nbcnews.com"],
  "maxItems": 10000
}
```

**Pull several accounts' timelines at once**

```json
{
  "mode": "authorFeed",
  "actors": ["bsky.app", "nbcnews.com", "jay.bsky.team"],
  "authorFeedFilter": "posts_no_replies",
  "maxItems": 3000
}
```

**Expand a full discussion**

```json
{
  "mode": "postThread",
  "postUris": ["https://bsky.app/profile/bsky.app/post/3mseeq5rllc2q"],
  "threadDepth": 10,
  "maxItems": 500
}
```

**Find accounts posting about a niche**

```json
{
  "mode": "searchActors",
  "searchQuery": "climate journalist",
  "maxItems": 500
}
```

***

### Input reference

| Field | Applies to | Notes |
|---|---|---|
| **What to scrape** | all | Chooses the mode. Fields below apply selectively. |
| **Search query** | post search, account search | Quote a phrase for exact matching: `"climate policy"` |
| **Accounts** | timelines, profiles, followers, following | Handle (`alice.bsky.social`), DID, or a profile URL |
| **Post URLs or URIs** | thread, likes, reposts, quotes | Accepts normal `bsky.app` links — no need to hunt for AT-URIs |
| **Maximum results** | all | Split evenly when several accounts or posts are given |
| **Sort order** | post search | `latest` (newest first) or `top` (most engaged) |
| **Language filter** | post search | Two-letter code: `en`, `de`, `pt`, `ja` … |
| **Posted after / before** | post search | ISO format: `2026-08-01T00:00:00Z` |
| **Only from this account** | post search | Restrict a keyword search to one author |
| **Mentioning this account** | post search | Only posts mentioning that account |
| **Hashtags** | post search | Without the `#` sign |
| **Linking to domain / URL** | post search | Find posts sharing a specific site or link |
| **Which posts** | timelines | Posts only, with replies, media only, or author threads |
| **Reply depth** | thread | How many levels of replies to follow |
| **Add follower counts** | accounts | On by default; one extra request per 25 accounts |
| **Delay between requests** | all | Raise to 1–2 s for very large runs |

***

### Output examples

**A post** (from keyword search, timelines, quotes, or threads):

```json
{
  "url": "https://bsky.app/profile/dorothealange.mastodon.ozioso.online.ap.brid.gy/post/3msgbcrssfew2",
  "text": "Sanitary facilities for migratory workers. Ditch bank camp. Squatters near Arvin, Kern County, California\n\n#Arvin #KernCounty #California #photography\n\nhttps://www.loc.gov/pictures/item/2017763258/",
  "createdAt": "2026-08-06T14:36:31.000Z",
  "authorHandle": "dorothealange.mastodon.ozioso.online.ap.brid.gy",
  "authorDisplayName": "Dorothea Lange",
  "likeCount": 1,
  "repostCount": 2,
  "replyCount": 0,
  "quoteCount": 0,
  "langs": ["en"],
  "hashtags": ["arvin", "kerncounty", "california", "photography"],
  "links": ["https://www.loc.gov/pictures/item/2017763258/"],
  "imageUrls": ["https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:ls2ap.../bafkrei..."],
  "imageAlts": ["The image shows a solitary wooden structure in an expansive field..."],
  "isReply": false,
  "uri": "at://did:plc:ls2ap3pxsotp3pgwjf7o633l/app.bsky.feed.post/3msgbcrssfew2"
}
```

**An account** (from profiles, account search, followers, likes, reposts):

```json
{
  "handle": "nbcnews.com",
  "url": "https://bsky.app/profile/nbcnews.com",
  "displayName": "NBC News",
  "description": "News updates from around the 🌎, all day, every day.",
  "followersCount": 258510,
  "followsCount": 149,
  "postsCount": 22704,
  "createdAt": "2023-05-31T01:09:28.017Z",
  "verifiedStatus": "valid",
  "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:wmho.../bafkrei...",
  "did": "did:plc:wmho6q2uiyktkam3jsvrms3s"
}
```

***

### Field reference

#### Post fields

| Field | Type | Description |
|---|---|---|
| `url` | string | Clickable bsky.app link to the post |
| `text` | string | Full post text |
| `createdAt` | string | When the author posted it (ISO 8601) |
| `indexedAt` | string | When Bluesky indexed it |
| `langs` | array | Language codes declared by the author |
| `authorHandle` | string | Author's handle |
| `authorDisplayName` | string | Author's display name |
| `authorDid` | string | Author's permanent identifier |
| `authorAvatar` | string | Author's avatar image URL |
| `likeCount` | number | Likes at scrape time |
| `repostCount` | number | Reposts at scrape time |
| `replyCount` | number | Replies at scrape time |
| `quoteCount` | number | Quote posts at scrape time |
| `bookmarkCount` | number | Bookmarks at scrape time |
| `hashtags` | array | Hashtags, without the `#` |
| `links` | array | External URLs in the post |
| `mentionDids` | array | Identifiers of mentioned accounts |
| `imageUrls` | array | Full-size image URLs |
| `imageAlts` | array | Alt text for each image |
| `externalUri` | string | Link preview target |
| `externalTitle` | string | Link preview title |
| `quotedPostUri` | string | The post being quoted, if any |
| `quotedPostText` | string | Text of the quoted post |
| `quotedPostAuthor` | string | Handle of the quoted author |
| `isReply` | boolean | Whether the post replies to another |
| `replyParentUri` | string | The post it replies to |
| `replyRootUri` | string | Root of the conversation |
| `isRepost` | boolean | Timelines only — repost rather than original |
| `repostedBy` | string | Who reposted it |
| `threadDepth` | number | Thread mode only — 0 is the root post |
| `labels` | array | Moderation labels |
| `uri`, `cid` | string | Permanent record identifiers |

#### Account fields

| Field | Type | Description |
|---|---|---|
| `handle` | string | Account handle |
| `url` | string | Clickable profile link |
| `displayName` | string | Display name |
| `description` | string | Bio text |
| `followersCount` | number | Followers |
| `followsCount` | number | Accounts followed |
| `postsCount` | number | Total posts |
| `createdAt` | string | Account creation date |
| `verifiedStatus` | string | Verification state |
| `avatar`, `banner` | string | Image URLs |
| `isLabeler` | boolean | Whether it runs a moderation service |
| `listsCount`, `feedgensCount`, `starterPacksCount` | number | Things the account publishes |
| `pinnedPostUri` | string | Pinned post, if any |
| `subjectHandle` | string | Follower modes — whose list this row came from |
| `likedPostUri`, `likedAt` | string | Likes mode — which post, and when |
| `repostedPostUri` | string | Reposts mode — which post |
| `did` | string | Permanent account identifier |

***

### Performance

Measured on Apify with 512 MB:

| Run | Results | Time |
|---|---|---|
| Keyword search | 1,000 posts | ~24 s |
| Keyword search | 150 posts | ~1 s |
| Follower list | 300 accounts | ~5 s |

Large historical searches scale roughly linearly — the time window steps
backwards one page at a time.

***

### Scheduling

Use Apify **Schedules** to run this on a cron and build a time series: daily
brand mentions, weekly follower growth, hourly monitoring of a hashtag. Combine
with **Webhooks** to push new results straight into Slack, a database, or your
own endpoint whenever a run finishes.

***

### Limitations

- **Public data only.** Private accounts, direct messages, and blocked content
  are out of reach.
- **Keyword search reaches back in time windows.** Very large historical pulls
  take proportionally longer than recent ones.
- **Counts are snapshots.** Likes and reposts reflect the moment of scraping.
- **Bluesky throttles heavy use.** If a large run slows down, raise **Delay
  between requests** to 1–2 seconds.
- **Deleted and moderated posts** may appear as gaps in threads.

***

### FAQ

**Do I need a Bluesky account?**
No. Everything runs against public endpoints.

**Can I get more than 100 posts from a keyword search?**
Yes. That limit is what stops most Bluesky scrapers — this one paginates by time
window and keeps going.

**Why are follower counts zero in some other scrapers?**
Bluesky omits them from account searches and follower lists. This Actor fetches
them separately and fills them in.

**Can I paste a normal Bluesky link?**
Yes, for both accounts and posts. AT-URIs work too.

**What formats can I export?**
CSV, Excel, JSON, XML, or straight from the Apify API.

**Can I scrape several accounts in one run?**
Yes. The result budget is divided evenly between them.

**How far back can I search?**
As far as Bluesky's search index goes. Use **Posted after / before** to target a
specific period.

***

### Legal and responsible use

This Actor reads publicly available data through Bluesky's official public API.
It does not bypass authentication, access private content, or evade technical
restrictions.

You remain responsible for how you use the results. If your data concerns people
in the EU, GDPR obligations apply to you as the controller — including lawful
basis, purpose limitation, and the rights of the people whose posts you collect.
Personal data should not be republished or resold without a proper legal basis.

# Actor input Schema

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

Pick what you want to collect. The fields below change depending on this choice. Each option below says which extra field it needs — fill that one in, the rest are ignored.

## `searchQuery` (type: `string`):

Keyword or phrase to search for. Used in 'Search posts' and 'Search for accounts'. Supports quotes for exact phrases, e.g. "climate policy".

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

Handles (alice.bsky.social), DIDs, or profile URLs. Used in 'Posts from accounts', 'Profile details', 'Followers' and 'Follows'.

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

Post URLs (https://bsky.app/profile/.../post/...) or AT-URIs (at://did:plc:.../app.bsky.feed.post/...). Used in 'Thread', 'Likes', 'Reposts' and 'Quotes'.

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

Total number of records to collect. When several accounts or posts are given, the budget is split evenly between them.

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

'latest' returns newest first, 'top' returns most engaged with.

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

Two-letter language code, e.g. 'en', 'de', 'pt'. Leave empty for all languages.

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

Only posts created after this moment. Format: 2026-01-31T00:00:00Z

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

Only posts created before this moment. Format: 2026-01-31T00:00:00Z

## `author` (type: `string`):

Restrict the keyword search to a single account's posts.

## `mentions` (type: `string`):

Only posts that mention this account.

## `tag` (type: `array`):

Only posts carrying these hashtags. Enter without the # sign.

## `domain` (type: `string`):

Only posts linking to this domain, e.g. 'github.com'.

## `url` (type: `string`):

Only posts linking to this exact URL.

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

Controls whether replies, reposts and media posts are included.

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

How many levels of replies to follow.

## `feedUri` (type: `array`):

The at:// URI of the feed. Open the feed on bsky.app and copy its link — this Actor converts a bsky.app link automatically. The prefilled value is Bluesky's own Discover feed.

## `listUri` (type: `array`):

The at:// URI of the list. Open the list on bsky.app and copy its link — this Actor converts a bsky.app link automatically. The prefilled value is Bluesky's own Bluesky-team list.

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

Bluesky omits follower and post counts when returning account search results, follower lists and likes. Leave this on to fetch them — costs one extra request per 25 accounts.

## `requestDelaySeconds` (type: `integer`):

Seconds to wait between API calls. Raise this if you hit rate limits on very large runs.

## `sessionToken` (type: `string`):

Not required. Reserved for future support of endpoints that need authentication.

## Actor input object example

```json
{
  "mode": "searchPosts",
  "searchQuery": "climate",
  "actors": [
    "bsky.app"
  ],
  "postUris": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l"
  ],
  "maxItems": 100,
  "sort": "latest",
  "lang": "en",
  "since": "2024-06-01",
  "until": "2024-12-31",
  "authorFeedFilter": "posts_no_replies",
  "threadDepth": 6,
  "feedUri": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"
  ],
  "listUri": [
    "at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3mrfuuvunx42u"
  ],
  "enrichProfiles": true,
  "requestDelaySeconds": 0
}
```

# Actor output Schema

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

Scraped posts or accounts, one row each. Post modes return text, engagement counts, hashtags, links and media; account modes return handle, display name, follower counts and bio.

## `resultsCsv` (type: `string`):

The same rows as a CSV download, ready for Excel or Google Sheets.

# 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 = {
    "searchQuery": "python",
    "actors": [
        "bsky.app"
    ],
    "postUris": [
        "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l"
    ],
    "feedUri": [
        "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"
    ],
    "listUri": [
        "at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3mrfuuvunx42u"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datawell/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 = {
    "searchQuery": "python",
    "actors": ["bsky.app"],
    "postUris": ["at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l"],
    "feedUri": ["at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"],
    "listUri": ["at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3mrfuuvunx42u"],
}

# Run the Actor and wait for it to finish
run = client.actor("datawell/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 '{
  "searchQuery": "python",
  "actors": [
    "bsky.app"
  ],
  "postUris": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l"
  ],
  "feedUri": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"
  ],
  "listUri": [
    "at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/app.bsky.graph.list/3mrfuuvunx42u"
  ]
}' |
apify call datawell/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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