# Bluesky Scraper - Posts, Profiles, Threads & Feeds (No Login) (`rjmacarthy/bluesky-scraper`) Actor

Scrape public Bluesky posts, replies, full threads, custom feeds (Discover), post search and profiles with likes, reposts, quotes and follower counts. Official public API, no login or app password, no start fee. Only-new mode for monitoring.

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

## Pricing

from $1.50 / 1,000 post or replies

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, Profiles, Threads & Feeds (No Login)

Scrape public **Bluesky** data from one Actor: posts and replies from any account, full **reply threads** flattened with depth, **custom feeds** such as Discover, **post search** by keyword, and **profiles** with follower, following and post counts. It reads Bluesky's official public API (the AppView behind bsky.app), so you need **no login, no app password and no cookies**, and runs don't break when the website changes. **No start fee**: you pay only per post or profile you get. Other Bluesky scrapers charge $0.004-$0.005 per run start or split posts, profiles and comments into separate Actors. This one does all of it, plus an **only-new-posts mode** for scheduled monitoring.

### What data can you get?

| Item | Fields |
|---|---|
| **Post** (posts, replies, thread replies, feed and search results) | text, created/indexed time, languages, **likes, reposts, replies, quotes, bookmarks**, author handle/DID/name/avatar, reply parent and thread root, thread depth, repost info (who reposted it and when), pinned flag, images (full size, thumbnail, alt text), video (HLS playlist, thumbnail), link card (URL, title, description), quoted post, hashtags, mentioned handles, links, labels, bsky.app URL |
| **Profile** | DID, handle, display name, bio, avatar, banner, **followers, following, posts**, number of custom feeds/lists/starter packs, verification status, pinned post, account creation date, labels, bsky.app URL |

Every item has `type` (`post` or `profile`), a stable `id` (post AT URI or DID), `url`, `sourceInput` (which input produced it) and `scrapedAt`. Missing values are `null`, never omitted.

### Use cases

- **Brand and competitor monitoring**: schedule daily runs on your own and your competitors' accounts and get only new posts with engagement counts.
- **Social listening**: search posts for a product, topic or #hashtag and export them to Sheets or your data warehouse.
- **Conversation analysis**: pull every reply under a viral post, with depth and parent, for sentiment or community research.
- **Influencer and media research**: compare follower and post counts of news outlets, creators or companies.
- **Trend tracking**: snapshot the Discover feed or any topic feed (science, tech, sports) every hour.
- **AI and NLP datasets**: clean, structured post text with languages, hashtags and links.

### How to use

1. Pick **What to scrape** (`mode`).
2. Fill the input list that mode uses: **Handles**, **Post URLs**, **Feeds** or **Search queries**.
3. Set **Max items** and run. Results land in the dataset; per-input status is in `RUN_SUMMARY`.

**Latest posts from accounts** (the prefill):

```json
{ "mode": "posts", "handles": ["bsky.app"], "maxItems": 15 }
```

**Only an account's own posts from the last 7 days, no replies or reposts:**

```json
{ "mode": "posts", "handles": ["nytimes.com", "theguardian.com"], "filter": "posts_no_replies", "includeReposts": false, "sinceDate": "7 days", "maxPostsPerAuthor": 50 }
```

**Profiles with follower counts:**

```json
{ "mode": "profiles", "handles": ["nytimes.com", "@washingtonpost.com", "https://bsky.app/profile/npr.org"] }
```

**All replies to a post:**

```json
{ "mode": "thread", "postUrls": ["https://bsky.app/profile/bsky.app/post/3mzhhvhjfwk22"], "threadDepth": 6, "maxItems": 200 }
```

**The Discover feed, or any custom feed:**

```json
{ "mode": "feed", "feeds": ["discover", "https://bsky.app/profile/bsky.app/feed/whats-hot"], "maxItems": 100 }
```

**Search posts** (latest or top, optional language and date):

```json
{ "mode": "search", "searchQueries": ["artificial intelligence"], "searchSort": "latest", "searchLanguage": "en", "maxItems": 100 }
```

**Search accounts by keyword:**

```json
{ "mode": "searchProfiles", "searchQueries": ["astronomy"], "maxItems": 50 }
```

**Monitoring:** add `"onlyNew": true` and a `"stateKey"` per schedule. The Actor remembers which posts it already returned (in a named key-value store) and outputs and charges only new ones. In Posts mode it stops paging as soon as it reaches posts it has seen before.

Handles can be written as `bsky.app`, `@name.bsky.social`, a bare `name` (means `name.bsky.social`), a DID (`did:plc:...`) or a `https://bsky.app/profile/...` URL.

### Output example

Real items from a local run (`samples/sample-output.json`), trimmed:

```json
{
  "type": "post",
  "id": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mzhhvhjfwk22",
  "url": "https://bsky.app/profile/bsky.app/post/3mzhhvhjfwk22",
  "authorHandle": "bsky.app",
  "authorDisplayName": "Bluesky",
  "text": "We're seeing 200% of our usual data traffic—and climbing! Thanks to all of you for making this a night to remember",
  "langs": ["en"],
  "createdAt": "2026-11-04T04:03:40.249Z",
  "replyCount": 176,
  "repostCount": 787,
  "likeCount": 10187,
  "quoteCount": 194,
  "bookmarkCount": 105,
  "isReply": false,
  "replyParentUri": null,
  "depth": null,
  "isRepost": false,
  "embedType": "images",
  "images": [{
    "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreiczrhhklt4wsquxzgudjxd7w4rch4tcbb7fkedhg56h3bfvsla2v4",
    "thumbnail": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreiczrhhklt4wsquxzgudjxd7w4rch4tcbb7fkedhg56h3bfvsla2v4",
    "alt": "Steeply climbing chart with the number 25.8 Gb/s superimposed."
  }],
  "externalLink": null,
  "quotedPostUri": null,
  "hashtags": [],
  "mentions": [],
  "links": [],
  "sourceInput": "bsky.app",
  "scrapedAt": "2026-11-04T12:30:22.488Z"
}
```

```json
{
  "type": "profile",
  "id": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "handle": "bsky.app",
  "displayName": "Bluesky",
  "followersCount": 35521500,
  "followsCount": 36,
  "postsCount": 916,
  "feedsCount": 7,
  "verifiedStatus": "none",
  "createdAt": "2023-04-12T04:53:57.057Z",
  "url": "https://bsky.app/profile/bsky.app"
}
```

The dataset has three views: **Posts**, **Post media and links** and **Profiles**.

### Pricing

Pay per event, no start fee, no subscription:

| Event | Price | When |
|---|---|---|
| `post` | **$0.0015** | each post, reply, thread reply, feed or search result in the output |
| `profile` | **$0.002** | each profile in the output |

Examples: **1,000 posts ≈ $1.50**, 100 profiles ≈ $0.20, the prefill (15 posts) ≈ $0.02. Platform compute is included. Invalid inputs, not-found accounts, skipped duplicates and posts filtered out by date or only-new mode are never charged. Set **Maximum cost per run** in the run options and the Actor stops cleanly at that budget.

### Tips and limits

- **Max items** counts everything in the run; **Max items per input** stops one big account, feed or thread from using it all up.
- Author feeds go back through an account's whole history; use `sinceDate` or limits for big accounts.
- **Search** runs on Bluesky's search endpoint (`api.bsky.app`). Bluesky currently allows it without login, but it blocks some IP ranges with HTTP 403. If that happens the run says so for that query (other inputs still finish); enabling Apify Proxy may help. Results come from Bluesky's search index (it reports at most 10,000 hits per query); for big topics use narrower queries or `sinceDate`.
- **Feeds**: public feed generators work without login. Feeds that personalise per user (e.g. "Following") return their logged-out version or nothing.
- **Threads**: replies are output level by level (all direct replies first), so a limit keeps the most relevant part. Deleted or blocked replies are skipped and counted in `RUN_SUMMARY`. Very large threads may be cut off by Bluesky at the chosen depth.
- Accounts that hide their content from logged-out visitors, and deleted posts, are reported as `not-found`.
- Follower/following **lists** are intentionally not supported (they would harvest data about private individuals). DMs are not accessible.
- Counts are live at scrape time; they change.
- Requests are paced to at most 5 per second (Bluesky allows about 3,000 per 5 minutes per IP) and 429s are retried with the server's `Retry-After`.

### FAQ

**Do I need a Bluesky account or app password?** No. Everything comes from Bluesky's public, unauthenticated API.

**Is it legal to scrape Bluesky?** The Actor only reads data that Bluesky publishes openly to logged-out visitors through its official public API. It does not log in, bypass access controls or collect follower lists, emails or phone numbers. You are responsible for how you use the data, including compliance with GDPR and other privacy laws and Bluesky's terms. Don't use it to profile private individuals.

**Why is a post with my keyword missing from search?** Bluesky's search index decides what it returns. Try `"searchSort": "top"`, a different phrasing, or search operators like `from:handle` or `#hashtag`.

**Do I need a proxy?** Normally no. If you get HTTP 403/429, enable Apify Proxy (automatic or datacenter). Residential proxies are not supported: if the input selects the RESIDENTIAL group, the Actor logs a warning and uses Apify datacenter proxy instead.

**How does only-new mode work with several schedules?** Each `stateKey` has its own memory. Use one key per schedule. The memory forgets posts not seen for 90 days.

**What does `depth` mean?** In Thread mode, 0 is the post you asked for, 1 a direct reply, 2 a reply to a reply, and so on. Use `replyParentUri` to rebuild the tree.

### Integrations

- **API**: run it and get the items in one call:

  ```bash
  curl -X POST "https://api.apify.com/v2/acts/rjmacarthy~bluesky-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"mode":"posts","handles":["bsky.app"],"maxItems":15}'
  ```

- **Schedules and webhooks**: run hourly or daily with `onlyNew` and push new posts to your pipeline when the run finishes.

- **Make, Zapier, n8n**: send new posts or profile stats to Google Sheets, Airtable, Slack or a database without code.

- **AI agents (MCP)**: use the Actor as a tool through the Apify MCP server.

- Python and JavaScript clients (`apify-client`) for programmatic runs.

### Changelog

- **0.1** (Nov 2026): first release. Posts (filters, reposts, pinned), profiles, threads with depth, custom feeds incl. Discover, post search, account search, only-new monitoring and date filter.

### More scrapers by the same developer

- [AI Visibility Tracker: ChatGPT, Perplexity, Gemini, Claude](https://apify.com/rjmacarthy/ai-search-visibility-tracker)
- [AliExpress Scraper - Product Search, Prices, Ratings & Details](https://apify.com/rjmacarthy/aliexpress-scraper)
- [Bilibili Scraper - Videos, Search, Popular & Uploader Stats](https://apify.com/rjmacarthy/bilibili-scraper)
- [Google Ads Transparency Scraper](https://apify.com/rjmacarthy/google-ads-transparency-scraper)
- [Internshala Scraper - Internships & Jobs in India](https://apify.com/rjmacarthy/internshala-scraper)
- [Roblox Games Scraper - Players, Visits, Charts & Stats](https://apify.com/rjmacarthy/roblox-games-scraper)

Browse all my Actors on [my Apify Store profile](https://apify.com/rjmacarthy).

### Support

Found a bug or need a field? Open an issue on the Actor's **Issues** tab and I'll take a look.

# Actor input Schema

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

Posts: the posts tab of each account in "Handles". Profiles: profile details for "Handles". Thread: each post in "Post URLs" with its replies, flattened with depth. Feed: posts from the custom feeds in "Feeds". Search: posts matching "Search queries". Search accounts: profiles matching "Search queries".

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

For the Posts and Profiles modes. Bluesky handles (bsky.app, name.bsky.social, @nytimes.com), DIDs (did:plc:...) or profile URLs (https://bsky.app/profile/bsky.app). A bare name like "jay" means jay.bsky.social.

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

For the Thread mode. Post links like https://bsky.app/profile/bsky.app/post/3mzhhvhjfwk22, or at:// post URIs. The post and its replies are output, replies level by level.

## `feeds` (type: `array`):

For the Feed mode. Custom feed links like https://bsky.app/profile/bsky.app/feed/whats-hot, at://did:.../app.bsky.feed.generator/<id> URIs, or "discover" for Bluesky's Discover feed. Public feeds work without login; feeds that personalise by account return their logged-out version.

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

For the Search posts and Search accounts modes. Keywords, "exact phrases", #hashtags, or Bluesky search operators such as from:nytimes.com, domain:example.com or lang:en.

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

Maximum number of items (posts or profiles) for the whole run. 0 = no limit (careful: big accounts have tens of thousands of posts).

## `maxPostsPerAuthor` (type: `integer`):

Maximum items per handle, feed, query or thread, so one big input does not use up "Max items (total)". 0 = no per-input limit.

## `sinceDate` (type: `string`):

Skip posts older than this date (e.g. 2026-10-01 or "7 days"). In Posts mode, reposts count by the time they were reposted, and the Actor stops paging once it reaches older posts. Search passes it to Bluesky as the "since" filter.

## `filter` (type: `string`):

Which posts of each account to return.

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

Include posts the account reposted (marked isRepost, with repostedByHandle and repostedAt). Turn off to get only the account's own posts.

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

How many levels of replies to fetch below each post. 1 = direct replies only.

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

Order of post search results.

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

Only search results in this language (two-letter code like en, de, ja, pt). Leave empty for all languages.

## `onlyNew` (type: `boolean`):

Skip posts already output by an earlier run with the same "State key", so scheduled runs only return (and charge for) new posts. Works for the Posts, Thread, Feed and Search modes.

## `stateKey` (type: `string`):

Name of this monitor. Use a different key for each schedule that should keep its own memory of seen posts.

## `stateStoreName` (type: `string`):

Named key-value store that keeps the seen-post memory between runs.

## `maxRequestsPerSecond` (type: `integer`):

Request rate towards the Bluesky API (at most 5; Bluesky allows about 3,000 requests per 5 minutes per IP). Lower it if you see HTTP 429 retries.

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

Not needed normally: the Bluesky API is open. Enable Apify Proxy (datacenter) if your runs are rate-limited or post search returns HTTP 403. Residential proxies are not supported: if you pick the RESIDENTIAL group, the Actor logs a warning and uses Apify datacenter proxy instead.

## Actor input object example

```json
{
  "mode": "posts",
  "handles": [
    "bsky.app"
  ],
  "postUrls": [
    "https://bsky.app/profile/bsky.app/post/3mzhhvhjfwk22"
  ],
  "feeds": [
    "discover"
  ],
  "searchQueries": [
    "climate change"
  ],
  "maxItems": 15,
  "maxPostsPerAuthor": 0,
  "filter": "posts_and_author_threads",
  "includeReposts": true,
  "threadDepth": 6,
  "searchSort": "latest",
  "onlyNew": false,
  "stateKey": "default",
  "stateStoreName": "bluesky-scraper-state",
  "maxRequestsPerSecond": 4,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Posts and replies with engagement counts.

## `postMedia` (type: `string`):

Images, video, link cards, quotes, hashtags and mentions.

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

Profiles with follower, following and post counts.

## `allItems` (type: `string`):

Every field of every item.

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

Per-input status, counts, stop reasons, warnings and request counts.

# 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",
    "handles": [
        "bsky.app"
    ],
    "maxItems": 15,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rjmacarthy/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",
    "handles": ["bsky.app"],
    "maxItems": 15,
    "proxyConfiguration": { "useApifyProxy": False },
}

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

```

## MCP server setup

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