# Bluesky Scraper: Posts, Search & Profiles, No Account Needed (`subtleasianmerch/bluesky-scraper`) Actor

Scrape Bluesky posts and profiles with no account or API key, from the official public API. Search posts, any account's posts, replies and custom feeds, with likes, reposts, images and videos. Schedule it to get only new posts.

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

## Pricing

from $0.50 / 1,000 posts

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, Search & Profiles, No Account Needed

Scrape **Bluesky** posts and profiles with no account, password or API key, through Bluesky's official public API. Brand teams, researchers and app builders get search results, any account's posts, replies and feeds, with likes and media.

- 🔓 **No account, password or API key:** Bluesky's own public API, as its docs describe.
- 🔎 **Four sources in one run:** post search, accounts, a post's replies, and custom feeds.
- 👤 **Full account history:** the profile and every post back to the first, reposts optional.
- 📊 **Every post's numbers:** likes, reposts, replies, quotes and bookmarks, plus images, videos and links.
- 🔔 **Only new posts:** scheduled runs with a monitor ID skip posts already delivered.
- 💸 **$0.50 per 1,000 posts or profiles.** Missing accounts, filtered posts and repeats are free.

**Try it:** the input comes pre-filled with an example. Click **Start** to see the latest posts mentioning "apify", plus Bluesky's own account and its posts, then add your own.

### What you get

**Four ways to read Bluesky** (mix them in one run):

| Source | What you get |
|---|---|
| **Search posts** | Bluesky's own post search, newest or top, optionally in one language and between two dates; up to 100 posts per search |
| **Accounts** | a profile row, then the account's posts, newest first: posts only, posts and replies, posts with media, or posts and the account's own threads |
| **Posts** | a post and its replies, down to the depth you choose |
| **Custom feeds** | the posts a feed shows, such as Discover, in the feed's order |

**For every post:**

- link, author (handle, name, avatar, DID) and text;
- when it was written and when Bluesky indexed it, in UTC;
- likes, reposts, replies, quotes and bookmarks;
- the full URLs of links, @mentions and #hashtags in the text;
- images and videos (with alt text and size), a link card, or the quoted post;
- the post it replies to, the thread's first post, and moderation labels;
- the search, account, post or feed it came from.

**For every account:** name, bio, avatar, banner, followers, following, posts count, when the account was created, whether it's verified, and its pinned post.

### Who it's for

- **Brand and social listening teams:** follow mentions of a brand, product or topic, and get new posts on a schedule.
- **Marketers and agencies:** compare accounts by followers and engagement, and see which posts get the most likes and reposts.
- **Researchers and journalists:** collect an account's history, a debate's replies, or a feed's posts for analysis.
- **Community managers:** track replies to announcements and what a custom feed is surfacing.
- **App builders:** feed posts into dashboards, alerts or spreadsheets through the Apify API.

### How much does it cost?

You pay **$0.50 per 1,000 posts or profiles** delivered ($0.0005 each), plus Apify's standard run start fee.

| Example | Delivered | Cost |
|---|---|---|
| The pre-filled example: search "apify" and the `bsky.app` account (a test run) | 40 posts, 1 profile | about $0.02 |
| 5 accounts, up to 500 posts each (a test run, 49 seconds) | 2,336 posts, 5 profiles | about $1.17 |
| Any 1,000 posts | 1,000 | $0.50 |

**Free:**

- accounts and posts that don't exist;
- posts outside your dates, reposts you didn't ask for, and other filtered posts;
- repeats within a run, such as a post found by two searches;
- with a monitor ID, posts delivered before.

Set a maximum cost per run in the run options. The run stops cleanly at that amount.

### How to use it

1. **Add what to read** under *What to read*, one item per line:
   - **Search posts:** words to search for, such as `climate summit` or `#buildinpublic`;
   - **Accounts:** a handle (`nytimes.com`, `name.bsky.social`), `@handle`, a DID, or a profile link;
   - **Posts (with their replies):** links like `https://bsky.app/profile/bsky.app/post/3mz4cvpstb22v`;
   - **Custom feeds:** links like `https://bsky.app/profile/bsky.app/feed/whats-hot`.
2. **Set limits and filters** under *Limits and filters*:
   - most posts per search, account, post or feed (default 100);
   - a date range;
   - search order (Latest or Top) and language;
   - which posts from accounts, reposts, profiles and reply depth.
3. **For new posts only,** enter a **Monitor ID** under *Monitoring*, such as `brand-watch`, and schedule the Actor, for example hourly. Keep **Output only new posts** on.
4. **Click Start.** The log and status line give the posts and profiles delivered; the pre-filled example took 3 seconds in a test.
5. **View and download the results.** The **Posts** view shows date, account, text, likes, reposts, replies, media and link; the **Profiles** view shows each account. Export as JSON, CSV, Excel or another format.

The `RUN_SUMMARY` record in the run's key-value store lists every search, account, post and feed with its status.

### Example input

**The last 500 posts from three news accounts, with their profiles:**

```json
{
  "handles": ["nytimes.com", "theverge.com", "@jay.bsky.team"],
  "maxPostsPerSource": 500
}
```

**An hourly brand watch in English** (schedule it with this input):

```json
{
  "searchQueries": ["apify", "web scraping"],
  "searchSort": "latest",
  "searchLanguage": "en",
  "monitorId": "brand-watch",
  "onlyNew": true
}
```

**A post's replies two levels deep, and the Discover feed:**

```json
{
  "postUrls": ["https://bsky.app/profile/bsky.app/post/3mz4cvpstb22v"],
  "feedUrls": ["https://bsky.app/profile/bsky.app/feed/whats-hot"],
  "replyDepth": 2,
  "maxPostsPerSource": 300
}
```

### Example output

One row per post or profile. A real post from the `bsky.app` account, shortened:

```json
{
  "type": "post",
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mz4cvpstb22v",
  "url": "https://bsky.app/profile/bsky.app/post/3mz4cvpstb22v",
  "authorHandle": "bsky.app",
  "authorDid": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "authorName": "Bluesky",
  "text": "To try the moderation inbox, enable beta features under Settings…",
  "createdAt": "2026-10-30T17:34:58.470Z",
  "indexedAt": "2026-10-30T17:35:05.618Z",
  "langs": ["en"],
  "likeCount": 279,
  "repostCount": 24,
  "replyCount": 9,
  "quoteCount": 4,
  "bookmarkCount": 20,
  "links": [],
  "mentions": [],
  "hashtags": [],
  "media": [],
  "linkCard": {
    "url": "https://bsky.social/about/blog/10-30-2026-moderation-inbox",
    "title": "Introducing the moderation inbox - Bluesky",
    "description": "Starting today, the moderation inbox is available in beta…",
    "thumbnailUrl": "https://cdn.bsky.app/img/feed_thumbnail/plain/…"
  },
  "quotedPost": null,
  "replyTo": null,
  "repostedBy": null,
  "labels": [],
  "sourceType": "account",
  "source": "bsky.app",
  "fetchedAt": "2026-10-31T04:54:50.000Z"
}
```

A profile row has `type: "profile"`, the account's handle, name and avatar, plus `description`, `bannerUrl`, `followersCount`, `followsCount`, `postsCount`, `accountCreatedAt`, `verified`, `trustedVerifier` and `pinnedPostUri`.

#### About the fields

- `media`: one entry per image or video: `{ type, url, thumbnailUrl, alt, width, height }`. Video `url` is a streaming playlist.
- `quotedPost`: `{ uri, url, authorHandle, authorName, text, createdAt, available }`. `available` is false when the quoted post was deleted or blocked.
- `replyTo`: `{ uri, url, authorHandle }` of the post it replies to; `threadRootUri` is the thread's first post.
- `repostedBy`: set only when you include reposts: the account that reposted it.
- `pinned`: the account's pinned post, shown at the top of its posts.
- `labels`: moderation labels, such as content warnings.
- With a monitor ID: `isNew`.
- **Dates** are converted to UTC. Posted dates come from the post itself; `indexedAt` is when Bluesky saw it.

**Run summary:** the `RUN_SUMMARY` record has each search, account, post and feed with its status (`ok`, `not found`, `blocked`, `failed` or `not read`), the reason, pages read, posts delivered and, for searches, Bluesky's total match count. It also has the totals: posts, profiles, new posts, posts left out by your filters, and requests.

### Integrations

- **Schedules:** run it hourly or daily in Apify Console. With a monitor ID, each run returns only new posts.
- **Apify API:** start a run and get the posts back as JSON in one call:

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

  Replace `YOUR_TOKEN` with your Apify API token.
- **Webhooks:** call your own endpoint when a run finishes, for example to post new mentions into your app.
- **Make, Zapier and n8n:** send new posts to Slack, email or a CRM with Apify's apps for these tools.
- **Monitoring:** Apify's monitoring can alert you when a scheduled run fails or returns fewer results than usual.

### FAQ

#### Is it legal to use?

Bluesky's own rules were checked on 31 October 2026. Its robots.txt allows crawling and points bulk users to the API. Its API docs list the public hosts this Actor uses for requests without an account. Its Terms of Service (last updated 14 August 2025) have no clause on scraping or automated collection. Posts still belong to the people who wrote them and are personal data: use what you collect lawfully, and respect privacy laws. Bluesky's developer guidelines ask apps to remove content people delete; if you keep or show these posts, delete the ones later removed. This isn't legal advice.

#### Do I need a Bluesky account?

No. Everything comes from Bluesky's public API, without logging in.

#### Why only 100 posts per search?

That's Bluesky's limit for searches without an account: it answers one page of results and refuses the next with "forbidden by administrative rules". The Actor respects it. Use more specific words, or monitor the search on a schedule: an hourly or daily run catches each new post. Post search goes to `api.bsky.app`, which Bluesky's docs list as the other direct host for requests without an account; all other reads use the cached public API (`public.api.bsky.app`).

#### How far back can it read an account?

To its first post. In a test, `bsky.app`'s posts went back to 12 April 2023. Reposts are left out by default and don't count toward your limit.

#### How do I get only new posts?

Add a monitor ID, keep **Output only new posts** on, and schedule the Actor. Runs with the same monitor ID remember the posts they delivered, for 60 days. The first run outputs up to your limit from each source; later runs output only posts not delivered before. Turn **Output only new posts** off to get every post, with `isNew` marking the new ones. In a test, a run 4 minutes after the first output only the 4 new posts for "bitcoin". The New York Times account cost a single request, because nothing was new.

#### How fast is it?

Up to 100 posts per request, about two requests a second. In a test, 5 accounts gave 2,336 posts in 49 seconds.

#### What if Bluesky slows down or refuses?

The Actor uses plain requests and says it's a bot. If Bluesky asks it to slow down, it waits and retries. If Bluesky refuses, the run stops, keeps what it delivered, and says so.

#### Does it get followers lists?

Not yet. It gives each account's follower and following counts, not the lists.

#### Can I read a custom feed?

Yes. Paste the feed's link from bsky.app; the posts come in the feed's own order.

#### What happens with an account that doesn't exist?

It's listed in the run summary as "not found", with Bluesky's reason, and costs nothing.

### Related Actors

More Actors from the same developer that work well with this one:

| Actor | Use it to |
|---|---|
| [Telegram Channel Scraper](https://apify.com/subtleasianmerch/telegram-channel-scraper) | follow the same topics in public Telegram channels |

Questions, bugs or ideas? Open an issue on this Actor's **Issues** tab.

# Actor input Schema

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

Words to search for, one search per line, using Bluesky's own post search. Without an account, Bluesky returns one page per search: up to 100 posts (the newest, or the top ones).

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

Accounts whose profile and posts you want: a handle (nytimes.com, name.bsky.social), @handle, a DID or a profile link (https://bsky.app/profile/nytimes.com).

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

Post links (https://bsky.app/profile/<handle>/post/<id>). Each gives the post and its replies.

## `feedUrls` (type: `array`):

Custom feed links (https://bsky.app/profile/<handle>/feed/<id>), such as Discover. Each gives the feed's posts in its own order.

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

Newest first for accounts and searches. Searches are capped at 100 by Bluesky. With a monitor ID, this caps the new posts per source per run.

## `postedAfter` (type: `string`):

Leave out posts older than this date (UTC). Accounts' posts stop paging here.

## `postedBefore` (type: `string`):

Leave out posts newer than this date (UTC). For searches, Bluesky applies both dates itself.

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

Newest posts first, or Bluesky's top posts.

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

Only posts in this language, as a code such as en, ja or pt.

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

Bluesky's own filter for an account's posts.

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

Also output posts the accounts reposted from others (marked with repostedBy).

## `includeProfiles` (type: `boolean`):

One profile row per account: name, bio, followers, following, posts count and verification.

## `replyDepth` (type: `integer`):

For post links: 1 gives direct replies, 2 also replies to those, and so on. 0 gives only the post.

## `monitorId` (type: `string`):

Runs with the same ID remember the posts they delivered, for 60 days. Use letters, digits and dashes. Schedule the Actor with it for a new-post feed.

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

With a monitor ID: output only posts not delivered by earlier runs. Turn off to output every post, with isNew marking the new ones.

## `requestDelaySecs` (type: `number`):

Pause between requests to Bluesky, from 0.2 to 10.

## Actor input object example

```json
{
  "searchQueries": [
    "apify"
  ],
  "handles": [
    "bsky.app"
  ],
  "maxPostsPerSource": 20,
  "searchSort": "latest",
  "authorFeedFilter": "posts_no_replies",
  "includeReposts": false,
  "includeProfiles": true,
  "replyDepth": 1,
  "onlyNew": true,
  "requestDelaySecs": 0.5
}
```

# Actor output Schema

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

The dataset rows this run produced.

## `runSummary` (type: `string`):

Counts, costs and anything skipped, saved as RUN_SUMMARY.

# 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": [
        "apify"
    ],
    "handles": [
        "bsky.app"
    ],
    "maxPostsPerSource": 20
};

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

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

```

## MCP server setup

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