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

Scrape Bluesky without a login or API key. Search posts, get an account's posts, profiles, followers, follows, threads and replies, likes and reposts. Text, images, links, counts and dates. $0.40 per 1,000 results.

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

## Pricing

from $0.30 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does Bluesky Scraper do?

Bluesky Scraper reads Bluesky through its public API, the one anyone can use without logging in, and turns it into a table. Search for posts by keyword, hashtag, language, author, mentioned account, website or period. Read an account's posts, its profile, its followers and the accounts it follows. Read a single post with its replies, the conversation above it, and the people who liked or reposted it. Export to Excel, CSV or JSON, or send it on to another app.

There is nothing to set up. No Bluesky account, no app password, no token and no API key: the tool only sends plain read requests to Bluesky's public API (`public.api.bsky.app` and `api.bsky.app`), which answers without a login, and it never writes anything (no posting, liking, following or messages).

- **Search posts** by words, `"exact phrases"` and `#hashtags`, newest or top first, with a period, a language, an author, a mentioned account, a website or a link as filters
- **An account's posts**, replies and reposts, newest first, as far back as you ask, with a date range, with or without reposts, or only the posts with pictures or video
- **Profiles**: handle, DID, name, description, followers, follows and posts counts, created date, picture, banner, labels, check mark
- **Followers and follows** of any account
- **A post's thread**: the post, the replies below it (up to 10 levels), the posts above it, and the accounts that liked or reposted it
- Every post with its text, date, like, repost, reply and quote counts, language, hashtags, mentions, links, pictures with their descriptions, video, link card, quoted post, and the post it replies to
- Handles with or without `@`, `bsky.app` links, `at://` addresses and DIDs, all in one list
- Public posts and profiles only: e-mail addresses and phone numbers in profile descriptions and names are replaced with `[removed]`, so you do not get contact details

### What data do you get?

Each post is one row (`type: "post"`):

| Field | Example |
|---|---|
| Post | `https://bsky.app/profile/bsky.app/post/3myi2ihquwk26` and its `at://` address |
| Author | handle, DID and display name |
| Text | the post as written, with line breaks |
| Created | `2026-10-22T16:11:13.754Z` (ISO 8601, UTC) |
| Counts | likes, reposts, replies, quotes, bookmarks |
| Languages | `["en"]`, as the author marked them |
| Hashtags, mentions, links | the tags, the mentioned accounts (handle and DID) and the real web addresses behind links |
| Pictures | address, thumbnail, the description (alt text) the author wrote, and size |
| Video | address of its playlist, thumbnail, size |
| Link card | address, title, description, thumbnail |
| Quoted post | the post it quotes: link, author, text, date and counts |
| Reply to | the post it answers and the first post of the thread |
| Labels | for example `spam`, `bot` or `graphic-media` |
| Reposted by | in an account's posts: who reposted it and when |
| Thread depth | in a thread: 0 is the post you asked for, 1 its replies, -1 the post it answers |

A profile is one row too (`type: "profile"`): handle, DID, display name, description, avatar, banner, followers, follows and posts counts, created date, labels, check mark, pronouns and website where the account lists them, and the pinned post. People in lists have the same fields without the counts: `follower` and `following` rows (with `of`, the account whose list it is), `liker` rows (with `likedAt`) and `reposter` rows.

### How to scrape Bluesky

1. Under **Search terms**, add words or hashtags, one search per line. Or under **Accounts**, add handles such as `bsky.app` or profile links. Or under **Posts**, add post links.
2. Choose what to get for each account (profile, posts, followers, following) and each post (the post, replies, posts above, who liked it, who reposted it), and set the maximums.
3. Press **Start**.
4. Export the results as Excel or CSV, or connect them to Google Sheets, Slack, Make or Zapier.

### How much does it cost to scrape Bluesky?

You pay for results, not for time: **$0.40 per 1,000 results**. Apify subscribers on the Silver plan pay $0.35 and on the Gold plan $0.30 per 1,000 results. A result is one row: a post, a profile, a follower, a follow, a liker, a reposter or an error. Each run also has a start fee of $0.00005.

| Results | Cost |
|---|---|
| 100 | $0.04 |
| 1,000 | $0.40 |
| 10,000 | $4.00 |
| 100,000 | $40.00 |

Apify's free plan includes $5 of credit each month, which covers about 12,000 results. A post found by two searches, or listed twice, is returned and charged once. Posts left out by your filters, by **Skip adult content**, or because their author asked not to be shown to logged-out visitors are not returned and not charged. A row that reports a problem (an account that does not exist, an entry that is not a Bluesky link) counts like any other result. Set a maximum cost for the run in Apify Console and the tool stops there and keeps what it has read.

### Input

All three lists are optional, but give at least one of them.

- **Search terms** and **Maximum posts per search**. Each line is one search. Use `*` to search by the filters alone, for example every post that links to one website (without a period: Bluesky refuses `*` together with one). Bluesky shows a search without a login only its first page, up to 100 posts (see the FAQ).
- **Sort search results by**, **Posted from** and **Posted until**, **Language**, **Only posts by**, **Only posts mentioning**, **Only posts linking to this domain** and **to this address**: the search filters. A date, or a period such as `7 days` or `24 hours`, works in **Posted from**; a plain end date includes the whole day. **Posted from** and **Posted until** also limit the posts read from accounts.
- **Accounts**: handles, `@handles`, profile links or DIDs. **What to get for each account** chooses profile, posts, followers and following. **Maximum posts per account**, **Which posts of an account**, **Include reposts**, and **Maximum followers and follows per account**.
- **Posts**: links to posts, or `at://` addresses. **What to get for each post** chooses the post, replies, the posts above it, who liked it and who reposted it. **How deep to read replies** (1 to 10), **Maximum replies per post** and **Maximum likes and reposts per post**.
- **Skip adult content** (on by default) and, under More options, **Time limit per request** and **User-Agent**.

### Output example

```json
{
    "type": "post",
    "source": "search",
    "input": "bluesky",
    "uri": "at://did:plc:osgbq7gwpy2jhi6urpjcnlw7/app.bsky.feed.post/3myk2ht5l6y2h",
    "url": "https://bsky.app/profile/example.bsky.social/post/3myk2ht5l6y2h",
    "cid": "bafyreie7ziwaqt42camdsqeasqqotwbnx56raasyoqetrtjxyrizloh5bm",
    "authorHandle": "example.bsky.social",
    "authorDid": "did:plc:osgbq7gwpy2jhi6urpjcnlw7",
    "authorDisplayName": "Example",
    "text": "New photos from the trip! https://example.com/trip #travel",
    "createdAt": "2026-10-23T11:16:11.415Z",
    "indexedAt": "2026-10-23T11:16:11.838Z",
    "likeCount": 12,
    "repostCount": 2,
    "replyCount": 1,
    "quoteCount": 0,
    "bookmarkCount": 1,
    "languages": ["en"],
    "hashtags": ["travel"],
    "mentions": [],
    "links": ["https://example.com/trip"],
    "images": [{ "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:osgbq7gwpy2jhi6urpjcnlw7/bafkreiexample", "thumbnail": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:osgbq7gwpy2jhi6urpjcnlw7/bafkreiexample", "alt": "A beach at sunset", "width": 1200, "height": 900 }],
    "video": null,
    "externalLink": null,
    "quotedPost": null,
    "replyTo": null,
    "labels": [],
    "repostedBy": null,
    "pinned": false,
    "threadDepth": null
}
```

An input that cannot be read gives one row with an `error`: `ACCOUNT_NOT_FOUND`, `POST_NOT_FOUND`, `INVALID_INPUT` (for example a handle without a dot, or a post link under Accounts), `ACCOUNT_OPTED_OUT`, `ACCOUNT_SKIPPED_ADULT`, `SEARCH_LIMITED` (see the FAQ), `RATE_LIMITED`, `ACCESS_DENIED`, `ROBOTS_DISALLOWED` or a network error. If a page fails in the middle of a list, the rows read so far are kept and the error row says how many. The **Run summary** (key-value store) lists every search, account and post with the rows saved, the pages read, and how many rows were left out.

### What can you use it for?

- Monitor a brand, a product, a topic or a hashtag on Bluesky and send new posts to Slack, e-mail or your own app on a schedule
- Collect posts for sentiment analysis, research and AI tools
- Track what an account posts, how its followers and posts counts grow, and which posts get the most reactions
- Find who follows, likes or reposts an account's posts, for community research
- Check what people share from a website: search by domain
- Archive a thread with all its replies

Schedule the run in Apify Console, for example every hour with **Posted from** set to `1 hour`, to get only the new posts each time.

### Use it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/cyber1nj3ct0r~bluesky-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms": ["climate"], "maxPostsPerSearch": 100, "postedFrom": "24 hours", "accounts": ["bsky.app"], "accountData": ["profile", "posts"], "maxPostsPerAccount": 50}'
```

The same works from Python, JavaScript, Make, Zapier and n8n through Apify's integrations.

### FAQ

#### Do I need a Bluesky account or an API key?

No. The tool reads what Bluesky shows to visitors who are not logged in. It uses no login, app password, token, cookie or proxy, identifies itself in its User-Agent, follows the robots.txt of the API (which allows this), asks for at most about four pages a second, and waits when Bluesky asks it to slow down.

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

Bluesky answers a search without a login with its first page, up to 100 posts. In our tests it refused every later page (HTTP 403). The tool still asks for the next page once, in case Bluesky allows it, but when Bluesky refuses it stops, saves a row with the code `SEARCH_LIMITED` that says how many posts matched, and does not try to get round the refusal. To read more of a busy search, run it for shorter periods with **Posted from** and **Posted until**, one period after the other: each period gives its newest 100 posts. A search for `*` or for a single hashtag cannot be combined with a period (Bluesky answers "Invalid request"); add a word to the search or leave the period out.

#### Why are some posts or accounts missing?

Two kinds are left out on purpose. Accounts that asked apps not to show them to people who are not logged in (10 to 25 percent of the posts in the searches we tried) are never returned, whatever the settings. Posts and accounts that Bluesky or their authors label as adult (porn, sexual, nudity) are left out while **Skip adult content** is on; untick it to get them. You are not charged for rows that are left out.

#### How many replies does it get?

The replies Bluesky gives for a post: it ranks them and returns the top part. In a test, a post with 8,678 replies returned 165 direct replies and 84 answers to them. Replies that are deleted, or hidden by a block, are not included. The list of who reposted a post can be shorter than the repost count, because Bluesky lists only some of them.

#### Does it return e-mail addresses or phone numbers?

No. The tool returns public posts and profiles, not contact details. E-mail addresses and phone numbers in profile descriptions and display names are replaced with `[removed]`. Text that people write in a post is returned as written. There is no tool here to look up a person by e-mail or phone number, and nothing that sends messages, follows or likes.

#### What is the handle `handle.invalid`?

An account whose handle (its domain name) no longer checks out. The profile link then uses the DID, which never changes. Use the DID to identify accounts over time: people can change their handle.

#### Which dates and numbers does it give?

Dates are ISO 8601 in UTC. Counts are the ones Bluesky shows when the tool reads, and change over time. Hashtags are the tags that authors marked in their post, as Bluesky's own apps show them. Mentions are listed with the DID, and with the handle where the text shows it.

#### Do the picture and video links last?

They are Bluesky's own CDN addresses (`cdn.bsky.app` and `video.bsky.app`) and stay valid as long as the post exists. Videos come as an HLS playlist (`.m3u8`) that video players and tools like ffmpeg can open.

#### Is it allowed to scrape Bluesky?

The tool reads only what Bluesky's public API shows without a login, and it follows the API's robots.txt. Posts and profiles can still contain personal data and are copyrighted by their authors: you are responsible for how you store, use and share them, under the laws that apply to you and Bluesky's terms.

#### Something does not work

Open an issue on the **Issues** tab with the search, account or post you tried. Bluesky changes its API from time to time, and every report is read and answered.

# Actor input Schema

## `searchTerms` (type: `array`):

Words, phrases or hashtags to search for, one search per line, such as <code>climate</code>, <code>"good morning"</code> (the exact phrase) or <code>#cats</code>. Use <code>\*</code> to search by the filters below alone, for example every post that links to one website (not together with a period). Bluesky shows a search without a login only its first page, up to 100 posts: for more, search shorter periods one after the other.

## `maxPostsPerSearch` (type: `integer`):

Keep at most this many posts of each search term. You pay per post returned, so this also caps the cost. At most 100 come from one search without a login (see the search terms).

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

<b>Latest</b> lists the newest posts first. <b>Top</b> lists the posts Bluesky ranks highest, which are mostly the most liked.

## `postedFrom` (type: `string`):

Keep only posts created on or after this date. Pick a date or a period such as <code>7 days</code> or <code>24 hours</code>. It applies to searches and to the posts of accounts.

## `postedUntil` (type: `string`):

Keep only posts created on or before this date (a plain date includes the whole day). It applies to searches and to the posts of accounts. To read more than a search shows at once, set a short period here and in <b>Posted from</b>, and repeat with the next one.

## `language` (type: `string`):

Search only posts that their authors marked as written in this language, as a language code such as <code>en</code>, <code>fr</code> or <code>pt</code>. Searches only.

## `searchAuthor` (type: `string`):

Search only the posts of this account: a handle such as <code>alice.bsky.social</code>, with or without @, or a DID. Searches only.

## `searchMentions` (type: `string`):

Search only posts that mention this account (a handle or DID). Searches only.

## `searchDomain` (type: `string`):

Search only posts that link to this website, for example <code>nytimes.com</code>. Searches only.

## `searchLink` (type: `string`):

Search only posts that link to exactly this web address, for example <code>https://example.com/article</code>. Searches only.

## `accounts` (type: `array`):

Bluesky accounts, one per line: a handle such as <code>bsky.app</code> or <code>@jay.bsky.team</code>, a profile link such as <code>https://bsky.app/profile/bsky.app</code>, or a DID such as <code>did:plc:z72i7hdynmk6r22z27h6tvur</code>.

## `accountData` (type: `array`):

<b>Profile</b>: name, description, follower, follow and post counts, picture, labels. <b>Posts</b>: the account's posts, replies and reposts, newest first. <b>Followers</b>: the accounts that follow it. <b>Following</b>: the accounts it follows.

## `maxPostsPerAccount` (type: `integer`):

Keep at most this many posts of each account, newest first. You pay per post returned, so this also caps the cost. Bluesky gives 100 posts per request.

## `feedFilter` (type: `string`):

Which of an account's posts to read. Reposts of other people's posts come with every choice except the last two; switch them off below if you do not want them.

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

Include the posts an account reposted. These rows carry the original author's post and, in <code>repostedBy</code>, the account that reposted it. Switch off to get only what the account wrote itself.

## `maxPeoplePerAccount` (type: `integer`):

Keep at most this many followers, and this many accounts followed, of each account. You pay per person returned, so this also caps the cost.

## `posts` (type: `array`):

Single posts, one per line: the link from the Bluesky app or website (<code>https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l</code>) or the <code>at://</code> address of the post. To find the link, open the post, then Share, Copy link to post.

## `postData` (type: `array`):

<b>The post</b>: text and counts as they are now. <b>Replies</b>: the answers below it. <b>Posts above</b>: the conversation it replies to, back to the first post. <b>Likes</b> and <b>reposts</b>: the accounts that liked or reposted it.

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

1 reads the answers to the post, 2 also the answers to those, and so on. Bluesky gives the replies it ranks highest, so a post with thousands of replies shows only part of them.

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

Keep at most this many replies of each post, over all levels.

## `maxPeoplePerPost` (type: `integer`):

Keep at most this many accounts that liked, and this many that reposted, each post.

## `skipAdultContent` (type: `boolean`):

Leave out posts and accounts that Bluesky or their authors label as adult (porn, sexual, nudity). Left-out rows are not charged. Accounts that asked not to be shown to people who are not logged in are always left out.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one answer from Bluesky before giving up.

## `userAgent` (type: `string`):

The name the tool sends with each request. Leave empty to use the tool's own name, which tells Bluesky what is visiting. Set your own only if you have permission to use it.

## Actor input object example

```json
{
  "searchTerms": [
    "bluesky"
  ],
  "maxPostsPerSearch": 10,
  "searchSort": "latest",
  "accounts": [
    "bsky.app"
  ],
  "accountData": [
    "profile",
    "posts"
  ],
  "maxPostsPerAccount": 10,
  "feedFilter": "posts_with_replies",
  "includeReposts": true,
  "maxPeoplePerAccount": 100,
  "postData": [
    "post",
    "replies"
  ],
  "replyDepth": 3,
  "maxRepliesPerPost": 100,
  "maxPeoplePerPost": 100,
  "skipAdultContent": true,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

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

One row per post (type post) or person (profile, follower, following, liker, reposter), and one row for each input that could not be read (error).

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

Each search, account and post you gave: rows saved, pages read, rows left out, and the problem if it could not be read.

# 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 = {
    "searchTerms": [
        "bluesky"
    ],
    "maxPostsPerSearch": 10,
    "accounts": [
        "bsky.app"
    ],
    "accountData": [
        "profile",
        "posts"
    ],
    "maxPostsPerAccount": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("cyber1nj3ct0r/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 = {
    "searchTerms": ["bluesky"],
    "maxPostsPerSearch": 10,
    "accounts": ["bsky.app"],
    "accountData": [
        "profile",
        "posts",
    ],
    "maxPostsPerAccount": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("cyber1nj3ct0r/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 '{
  "searchTerms": [
    "bluesky"
  ],
  "maxPostsPerSearch": 10,
  "accounts": [
    "bsky.app"
  ],
  "accountData": [
    "profile",
    "posts"
  ],
  "maxPostsPerAccount": 10
}' |
apify call cyber1nj3ct0r/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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