# Bluesky Posts Scraper: Accounts, Threads, Feeds & Alerts (`jtpalms/bluesky-posts`) Actor

Public Bluesky posts without a login: the latest posts of any accounts, whole reply threads, and third-party custom feeds. Text, likes, reposts, replies, quotes, links, images, hashtags and mentions. Schedule it with "Only new posts" for alerts. USD 0.50 per 1,000 posts.

- **URL**: https://apify.com/jtpalms/bluesky-posts.md
- **Developed by:** [JT Palms](https://apify.com/jtpalms) (community)
- **Categories:** Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$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 Posts Scraper: Accounts, Threads, Feeds & Alerts

Get public Bluesky posts as clean JSON, CSV or Excel, without a login and without a Bluesky account. Give it accounts to get their latest posts, post links to get the whole reply thread with each reply's depth, or custom feed links to get a topic feed's top posts. Every post comes with its text, author, like, repost, reply and quote counts, links, link card, images with alt text, video, hashtags and mentions. Turn on **Only new posts** and schedule it to get an alert whenever something new appears.

It reads Bluesky's public AppView API, the same logged-out API that serves public Bluesky pages. No login, no credentials, no browser.

**USD 0.50 per 1,000 posts.** Sources that fail are reported free, and monitor runs with nothing new cost nothing.

### What people use it for

- **Brand, competitor and news monitoring.** Watch the accounts of competitors, journalists, agencies or your own brand. Turn on **Only new posts**, schedule it every hour, and connect the task to Slack, email, Google Sheets or a webhook so each new post lands there.
- **Topic alerts from custom feeds.** Bluesky has thousands of third-party topic feeds (science, tech, climate, a sport, a language). Add a feed, narrow it with **Must contain (any of)** such as `CRISPR` or `startup*`, and get only the new matching posts.
- **Reply and feedback analysis.** Give the link of a launch announcement, a support post or a viral post and get every reply as a row with its depth, likes and parent, ready for sentiment analysis, FAQ mining or a report.
- **Research datasets.** An account's posts for a date range with engagement counts, languages, hashtags and shared links, for trend or media analysis.
- **AI agents and LLM pipelines.** Plain text plus image alt text and link card titles make posts easy to summarise, classify or load into RAG. Agents can call it through the Apify API or MCP server.
- **Link tracking.** See which articles and domains a set of accounts shares, with the link card title and description.

### Sample output

Real rows, trimmed. A post from an account, with an image and its alt text:

```json
{
  "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mmwmla3xph26",
  "url": "https://bsky.app/profile/bsky.app/post/3mmwmla3xph26",
  "text": "v1.122 is rolling out today, featuring a better way to discover and share articles, blog posts and newsletters across the Atmosphere via new, dynamic Standard.site link cards.",
  "createdAt": "2026-05-28T18:09:50.000Z",
  "langs": ["en"],
  "authorHandle": "bsky.app",
  "authorDisplayName": "Bluesky",
  "likeCount": 3154,
  "repostCount": 535,
  "replyCount": 524,
  "quoteCount": 298,
  "isReply": false,
  "isRepost": false,
  "links": [],
  "linkCard": null,
  "images": [
    {
      "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreicnrpnkalwhwp2td7sxgpbxbt6ii3tjth2n5hdcdok5mxfro6np3u",
      "alt": "The Bluesky and Standard.site logos sharing an emoji handshake above examples of the new, dynamic link cards."
    }
  ],
  "video": null,
  "hashtags": [],
  "sourceType": "account",
  "source": "bsky.app"
}
```

A post with a link card:

```json
{
  "url": "https://bsky.app/profile/theverge.com/post/3mwelcstrxs2u",
  "text": "Read the review from the only reviewer brave enough to see this:",
  "createdAt": "2026-09-25T20:11:15.460Z",
  "authorHandle": "theverge.com",
  "authorDisplayName": "The Verge",
  "likeCount": 73,
  "repostCount": 5,
  "replyCount": 2,
  "quoteCount": 4,
  "links": ["https://www.theverge.com/entertainment/996499/ai-odyssey-movie-review"],
  "linkCard": {
    "url": "https://www.theverge.com/entertainment/996499/ai-odyssey-movie-review",
    "title": "The 2.5-hour AI-generated Odyssey movie is 2.5 hours too long",
    "description": "Going up against Christopher Nolan with AI slop was ill-advised."
  },
  "sourceType": "account",
  "source": "theverge.com"
}
```

A reply from a thread, with its depth and mentions:

```json
{
  "url": "https://bsky.app/profile/bsky.app/post/3mv3zcjbomc22",
  "text": "Thank you to our Apple experts: @adapples.bsky.social, @kwiaht.org, @orchardnotes.com, @willrea.bsky.social, and @chuckwendig.bsky.social ...",
  "createdAt": "2026-09-09T17:02:28.503Z",
  "langs": ["en"],
  "authorHandle": "bsky.app",
  "likeCount": 228,
  "replyCount": 5,
  "isReply": true,
  "replyToUri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mv3zcjaijk22",
  "mentions": ["adapples.bsky.social", "kwiaht.org", "orchardnotes.com", "willrea.bsky.social", "chuckwendig.bsky.social"],
  "threadDepth": 1,
  "sourceType": "thread",
  "source": "https://bsky.app/profile/bsky.app/post/3mv3zcjaijk22"
}
```

Every post row has the same fields, so CSV and Excel exports line up:

| Field | What it is |
|---|---|
| `uri`, `url` | The post's AT Protocol URI (what **Only new posts** remembers) and its bsky.app link. |
| `text` | The post text as written. |
| `createdAt`, `indexedAt` | When the author posted it and when Bluesky indexed it, ISO 8601 in UTC. |
| `langs` | Language codes the author's app tagged, such as `en` or `pt-BR`. Can be empty. |
| `authorHandle`, `authorDisplayName`, `authorDid` | Who posted it: handle, display name and permanent account ID. |
| `likeCount`, `repostCount`, `replyCount`, `quoteCount` | Engagement at the time of the run. |
| `isReply`, `replyToUri`, `threadRootUri` | Whether it is a reply, the post it answers, and the first post of its thread. |
| `isRepost`, `repostedBy`, `repostedAt` | For accounts with **Include reposts**: the account that reposted it and when. |
| `quotedPostUri`, `quotedPostUrl` | The post it quotes, if any. |
| `links` | Every link in the text and the link card, in full. |
| `linkCard` | `url`, `title` and `description` of the link preview. |
| `images` | Each image's `url` and `alt` text. |
| `video` | `playlistUrl`, `thumbnailUrl` and `alt` of an attached video. |
| `hashtags`, `mentions` | Hashtags without `#`, and mentioned handles without `@` (or the account ID when the text does not show a handle). |
| `labels` | Moderation labels on the post, such as `nudity` or `graphic-media`. |
| `threadDepth` | For threads: 0 is the post you gave, 1 a direct reply, and so on. |
| `sourceType`, `source` | `account`, `thread` or `feed`, and which input produced the row. |

Sources that fail and inputs that cannot be used get a free row with `source`, `error` and `scrapedAt`. Each run also writes a `SUMMARY` record with counts per source to the default key-value store.

### How to use it

#### Accounts

1. Add accounts under **Accounts**, one per line: a handle (`nytimes.com`, `@jay.bsky.social`, or just `jay` for `jay.bsky.social`), a DID, or a profile link like `https://bsky.app/profile/bsky.app`.
2. Choose **Which account posts**: posts without replies (default), posts and replies, posts and the author's own threads, only posts with images or video, or only video.
3. Optional: **Include reposts** to also get the posts they reposted.
4. Set **Max posts per account, thread or feed** (default 100, newest first) and click **Start**. Export as JSON, CSV or Excel, or read the dataset through the Apify API.

#### Threads

Add post links under **Posts (whole threads)**, like `https://bsky.app/profile/bsky.app/post/3mv3zcjaijk22`. You get the post and the replies under it, in the order Bluesky shows them, each with `threadDepth`. **Thread depth** 1 gives only direct replies. When the maximum is lower than the thread, the post and top-level replies come first. Replies that the thread's author hid, deleted replies and blocked replies are left out.

#### Custom feeds

Add feed links under **Custom feeds**, like `https://bsky.app/profile/did:plc:jfhpnnst6flqway4eaeqzj2a/feed/for-science`. You get the feed's top posts in the feed's own order, up to the maximum. Find feeds in the Bluesky app under Feeds; topic and keyword feeds made with tools like SkyFeed work well for monitoring.

Any line is routed by its shape, so a post link pasted under Accounts still works as a thread.

#### Filters

These apply to every source: **Posted after** and **Posted before** (a date like `2026-09-01` or a relative value like `7 days`), **Languages** (`en` also matches `en-US`), **Must contain (any of)** and **Minimum likes**. Keywords match whole words, not case-sensitive, in the text, link card, links, hashtags and image alt text; end a word with `*` for any ending, or write `/pattern/` for a regular expression. You pay only for posts that pass the filters.

#### Set it up as an alert

1. Turn on **Only new posts**.
2. Choose what the first run does: **All current posts** returns what is there now (up to the maximum); **None, just set the starting point** returns nothing and only records what exists, so you hear only about posts made after today.
3. Save it as a task and add a **schedule**, for example every hour.
4. In the task's **Integrations** tab, connect Slack, email, Google Sheets, Zapier, Make or a webhook.

Examples:

- Competitor watch: three competitor accounts, posts without replies, Only new posts, hourly.
- Topic alert: a science feed with Must contain `CRISPR`, Only new posts, every 30 minutes.
- New replies to your announcement: its post link, Thread depth 1, Only new posts, hourly.

If two schedules watch the same account, thread or feed with different filters, give each a **Monitor name** (under Monitoring) so they keep separate memories.

### Pricing

| What | Price |
|---|---|
| Post in the results (from an account, thread or feed) | USD 0.0005 (USD 0.50 per 1,000) |
| A source that fails, an input that cannot be used, a skipped post | Free |
| Monitor run where nothing is new | Free |

Examples: 10 accounts that post 20 times a day each, monitored hourly, is about 200 posts a day, USD 0.10 a day or about USD 3 a month. A thread with 500 replies costs USD 0.25. 10,000 posts for a research dataset cost USD 5.

Set a maximum cost per run in the run options. The actor stops cleanly when it gets there, and with **Only new posts** anything it did not save is kept for the next run.

### Limits

- **The actor identifies itself.** Every request carries the user agent `Mozilla/5.0 (compatible; bluesky-posts/1.0; +https://apify.com/JTPalms/bluesky-posts)`, with plain request headers. It never pretends to be a browser. Some sites block bots; a blocked request is reported as an error, not hidden.
- **No keyword search of all of Bluesky.** Bluesky's post search (`app.bsky.feed.searchPosts`, also used for hashtag search) needs a login: the public API answers HTTP 403 Forbidden to logged-out requests (checked 2026-09-28). This actor never logs in, so keyword search is not offered. To follow a topic, use a custom feed for it plus **Must contain (any of)**.
- **Bluesky's own feeds** (Discover, Popular With Friends and others) also need a login. Links to them get a free note instead. Third-party custom feeds work.
- **People who opted out are skipped.** Bluesky lets people ask apps not to show their account to logged-out viewers. Their posts are never returned: not from their account, not as replies in a thread, not in feeds, not as reposts, and posts that quote them do not link to them. Giving such an account returns a free note.
- No follower or following lists, no profiles or bios, no likes lists, no user lists (list links are refused), no private data. Each post carries only the author's handle, display name and account ID.
- Up to 5,000 posts per account, thread or feed in each run. For accounts, reading stops once posts are older than **Posted after**.
- Custom feeds: the feed's server decides what is in it and how far back it goes. With **Only new posts**, a feed monitor looks at the top posts up to the maximum each run, so set the maximum above what the feed gets between runs.
- **Only new posts** remembers up to 5,000 post IDs per account, thread or feed in your `bluesky-posts-monitor` key-value store. An account monitor re-reads 48 hours before the newest post it has seen, to catch posts Bluesky indexed late.
- Counts are those at the time of the run. A post deleted since an earlier run is simply not returned again.
- The actor does not log in, like, repost, follow or post.

### Related actors

- [Hacker News Scraper: Stories, Comments, Search & Alerts](https://apify.com/JTPalms/hacker-news): Hacker News front page, new, best, Ask HN, Show HN and job lists, or keyword search with date, points and comment filters.

### FAQ

**Do I need a Bluesky account or an app password?** No. The actor uses only the public, logged-out API and never asks for credentials.

**Is this allowed?** It reads Bluesky's public AppView API, which Bluesky provides for logged-out access to public posts, and it honours the logged-out opt-out that people set. The content belongs to its authors: quote and link back. If you keep the data, respect deletions and the privacy laws that apply to you (for example GDPR), because posts contain handles and names.

**Why can I not search all of Bluesky by keyword?** Bluesky requires a login for search. See Limits for the custom feed approach that works without one.

**Why did I get fewer posts than the maximum?** Filters, reposts (off by default), and posts from people who opted out of logged-out viewing reduce the count. The run log and the `SUMMARY` record show how many were skipped and why.

**Why did the first alert run return posts?** With **All current posts** the first run returns what is there now. Choose **None, just set the starting point** to hear only about new posts.

**Can I start over?** Delete the matching record in the `bluesky-posts-monitor` key-value store (Storage, Key-value stores), or use a new **Monitor name**.

**I get HTTP 429.** Bluesky rate-limits by IP address, and cloud IPs are shared. Try again later, lower **Sources in parallel**, or turn on a proxy under Advanced.

**Something missing or wrong?** Open an issue with your input.

# Actor input Schema

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

Public Bluesky accounts to read the latest posts from, one per line: a handle (nytimes.com, @jay.bsky.social, or just jay for jay.bsky.social), a DID, or a profile link like https://bsky.app/profile/bsky.app.

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

Post links, one per line, like https://bsky.app/profile/bsky.app/post/3mv3zcjaijk22. You get the post and the replies under it, each with its depth in the thread.

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

Custom feed links, one per line, like https://bsky.app/profile/did:plc:jfhpnnst6flqway4eaeqzj2a/feed/for-science. You get the feed's top posts in the feed's own order. Topic and keyword feeds made with tools like SkyFeed work well for monitoring. Bluesky's own feeds (Discover and others) need a login and are not supported.

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

For accounts: which of their posts to include.

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

For accounts: also include posts by others that the account reposted. Each such row has isRepost, repostedBy and repostedAt.

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

At most this many posts per source in each run. For accounts, the newest first. For threads, the post and top-level replies come first. For custom feeds, this is how many of the feed's top posts are read. With "Only new posts", new posts beyond this number are kept for the next run (accounts and threads).

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

For post links: how deep into the replies to go. 1 is only direct replies to the post.

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

Optional. Only posts created on or after this date. A date like 2026-09-01, or a relative value like "7 days" or "24 hours" (counted back from the start of each run). For accounts, reading stops once posts are older than this.

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

Optional. Only posts created on or before this date (the whole day is included), or a relative value like "1 day".

## `languages` (type: `array`):

Optional. Only posts tagged with one of these language codes, like en, de or pt (pt also matches pt-BR). Posts without a language tag are left out when this is set.

## `keywords` (type: `array`):

Optional. Keep only posts whose text, link card, links, hashtags or image descriptions contain at least one of these. Whole words, not case-sensitive: "AI" does not match "said". End a word with \* for any ending ("startup\*"), or write /pattern/ for a regular expression. This filters the posts of your sources; it is not a search of all of Bluesky.

## `minLikes` (type: `integer`):

Optional. Only posts with at least this many likes.

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

Remember what earlier runs returned and output only posts not seen before: new posts from accounts, new replies in threads, new posts in custom feeds. Schedule the actor (every hour, every morning) to use it as an alert: runs with nothing new output nothing and cost nothing. The memory is kept in your "bluesky-posts-monitor" key-value store.

## `firstRunOutput` (type: `string`):

What the first monitor run outputs. All: the current posts (up to the maximum). None: nothing; it only records what is there now, so you get only posts that appear after today.

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

Optional. Keeps a separate memory of seen posts. Use a different name for each schedule that watches the same accounts or feeds with different filters, so they do not share what counts as new.

## `maxConcurrency` (type: `integer`):

How many accounts, threads or feeds to read at the same time. The default is fast and polite.

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

Optional. Only needed if Bluesky answers HTTP 429 (too many requests from a shared IP address).

## Actor input object example

```json
{
  "accounts": [
    "bsky.app"
  ],
  "authorFeedFilter": "posts_no_replies",
  "includeReposts": false,
  "maxPostsPerSource": 100,
  "threadDepth": 6,
  "onlyNew": false,
  "firstRunOutput": "all",
  "maxConcurrency": 5
}
```

# Actor output Schema

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

All output rows in the default dataset (JSON, CSV, Excel via the format parameter).

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

Counts and per-input status for this run.

# 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 = {
    "accounts": [
        "bsky.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jtpalms/bluesky-posts").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 = { "accounts": ["bsky.app"] }

# Run the Actor and wait for it to finish
run = client.actor("jtpalms/bluesky-posts").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 '{
  "accounts": [
    "bsky.app"
  ]
}' |
apify call jtpalms/bluesky-posts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jtpalms/bluesky-posts"
        }
    }
}
```

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/9LmMxBhaQJ6it81ye/builds/bBUIoZHqZ7MourOv4/openapi.json
