# Bluesky Public Posts Scraper - Feeds & Threads

Extract public Bluesky author posts and supplied threads with links, reply relationships and engagement metrics. Optional saved-observation change tracking. No login or API key.

Canonical product page: https://mayd-it.com/data-tools/actors/bluesky-public-posts-scraper/
Public Actor: https://apify.com/maydit/bluesky-public-posts-scraper

## Example input

```json
{
  "authors": [
    "bsky.app"
  ],
  "maxPosts": 3,
  "includeThread": true,
  "maxContextPosts": 10
}
```

## Full guide

![Public posts, with thread context — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/bluesky-public-posts-scraper-09628735dee8.png?signature=hEhQmGfdh4XS5aCYD3lj)

## Public posts, with thread context

Collect public Bluesky posts for editorial research, account watchlists or structured archives. Each row represents a root post; optional parent and reply context stays nested within that row.

[Open this Actor in Console](https://console.apify.com/actors/KQRzP39gn24WO2IfI/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

## Try this Actor

1. **Choose a small scope.** Use **Bluesky Official Posts and Reply Context** below: it requests three posts from `bsky.app` with up to ten context posts per result. For a post-only run, clear the prefilled author list.
2. **Run and review.** Open the dataset for post observations. Check the run's key-value-store `SUMMARY`, `observationComplete` and thread coverage before treating a result as complete.
3. **Use the results.** Export JSON to retain nested author, reply, media and metric fields. The table view is useful for scanning post text and source links.

**Read this before using the data:** This is a bounded public view, not a complete conversation or firehose. Hidden replies may be absent. Comparison is optional metadata: unchanged saved observations are still emitted and billed.

**Bluesky Official Posts and Reply Context** — copy this input into the JSON editor in Console.

```json
{
  "authors": [
    "bsky.app"
  ],
  "maxPosts": 3,
  "includeThread": true,
  "maxContextPosts": 10
}
```

## Result preview

The dataset view highlights these columns:

- **Post link** · `url`
- **Author handle** · `author.handle`
- **Post text** · `text`
- **Source creation time** · `createdAt`
- **Observed likes** · `metrics.likes`
- **Observed replies** · `metrics.replies`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

## Usage and pricing

Base result rate on the FREE tier: **$2 per 1,000 post observations**. The [Pricing tab](https://apify.com/maydit/bluesky-public-posts-scraper/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

---

<!-- CUSTOMER-WORKFLOW-2026-10-13:START -->
## Customer workflow: An editorial watchlist with engagement checkpoints

Watch explicit public publisher accounts, retain stable post identities and source links, and separate newly observed content from ordinary engagement changes. This is an editorial queue, not network-wide listening.

**Setup:** Connect your own Apify and Google Sheets current-post register and optional observation history through [Make](https://docs.apify.com/integrations/make) or [n8n](https://docs.apify.com/integrations/n8n). Keep credentials in your connector's credential manager.

```json
{
  "authors": [
    {
      "value": "bsky.app",
      "externalId": "publisher-demo"
    }
  ],
  "postUrls": [],
  "maxPosts": 10,
  "maxPostsPerAuthor": 10,
  "maxPagesPerAuthor": 3,
  "includeReplies": false,
  "includeReposts": false,
  "includeThread": false,
  "comparePrevious": true,
  "baselineKey": "customer-editorial-v1",
  "maxRunSeconds": 240
}
```

Start with a **300-second Run timeout** and **$0.50 maximum run charge**, alongside the input's 240-second budget. Check current pricing and the selected scope before increasing either limit.

1. Create a Posts sheet keyed by `uri`, then replace the demo account with your explicit public publisher watchlist. Keep your customer reference in `externalId`. Seed the sheet from the first run before creating editorial alerts.
2. Require a healthy `SUMMARY`, no failed sources, and `observationComplete: true` before treating an observation as complete. Inspect source stop reasons: this input watches a bounded recent feed, not every historical post.
3. Upsert by `uri` using Sheets "Append or Update Row" in n8n or a lookup/update branch in Make. Flatten metrics into columns; preserve `requestedBy` as JSON because several requested sources can point to one post.
4. Reuse `baselineKey` sequentially. Route `first_observation` or `contentChanged: true` to editorial review, and `metricsChanged` to a separate engagement checkpoint. Null deltas stay unknown; unchanged posts are still saved and billed.

| Destination information | Actor fields |
| --- | --- |
| Stable key and link | `uri`, `url` |
| Author and attribution | `author.handle`, `requestedBy` |
| Content | `text`, `contentHash` |
| Engagement | `metrics.likes`, `metrics.reposts`, `metrics.replies`, `metrics.quotes` |
| Comparison | `comparisonStatus`, `contentChanged`, `metricDeltas`, `observedAt` |

**Keep the meaning:** There is no `onlyChanges` input. A recent window can miss posts between runs, and absence does not prove deletion. Thread context is off here; when enabled, it remains a bounded public view, never a guaranteed complete conversation.

**Empty or interrupted runs:** No public posts may match the selected feed scope. A healthy unchanged repeat normally still contains rows. An unavailable supplied post is an operational issue, not an automatic deletion signal.

**Reliable delivery:** Read the completed run's own SUMMARY and paginated dataset. Retain its run ID; if destination delivery fails, replay that dataset and deduplicate by run ID plus row key. Keep destination writes sequential, and write source text literally.

**Validation:** Inputs and mappings were checked locally against this Actor. The exact customer connection has not been run end to end. Choose recurrence after a manual bootstrap; this guide creates no schedule. See the [run API](https://docs.apify.com/api/v2/actors-runs-post), [pagination guide](https://docs.apify.com/api/v2/dataset-items-get), and the full Actor guide below for detailed setup and limits.
<!-- CUSTOMER-WORKFLOW-2026-10-13:END -->

## Full guide

Extract recent public posts from supplied Bluesky accounts, or inspect known public post URLs. Each saved row is one root post with its text, source URL, stable URI/CID, links, hashtags, facet byte offsets, media references, author attribution, reply relationships and engagement counts.

The optional thread view puts bounded parent/reply context inside the same row. Optional change tracking compares a root post's content and metrics with its previously saved observation.

### Quick start

Run with `{}` for three recent original posts from **bsky.app**, with up to 10 visible context posts each. No Bluesky login, app password, API key or proxy is required. Only the official public API is used. Source availability and public visibility can change.

### Inputs and scope

- `authors`: up to 20 public handles, DIDs or profile URLs. Entries can be strings or `{"value":"bsky.app","externalId":"crm-123"}`.
- `postUrls`: up to 50 supplied public bsky.app post URLs or post AT-URIs; the same object form preserves an external ID.
- `maxPosts` defaults to 3, maximum 200. All sources share this limit; authors are processed before supplied posts. A post selected more than once is saved once with `requestedBy` attribution for sources reached before the global cap.
- `maxPostsPerAuthor` defaults to 25; `maxPagesPerAuthor` defaults to 3. The Actor stops at the first applicable bound and records why.
- Original non-reply posts are the default. `includeReplies` and `includeReposts` apply to author feeds. Supplied post URLs are always eligible. Repost provenance remains separate from the original author.
- `includeThread` defaults to true. Reply depth defaults to 1, parent height to 1 and retained context to 10 posts. Maximum reply depth 3, parent height 5, context 50. The context limit is shared by parents and replies; parents are visited first, then replies breadth-first.
- `maxRunSeconds` defaults to 240 and is capped at900. The Actor also stops 25 seconds before the platform deadline.

An empty authors list and empty post list use the default account. A list supplied only in postUrls does not add the default account. Invalid URLs and input types fail with a diagnostic.

### Output and billing

**One paid result is one saved public root-post observation.** Nested thread context is included in that result. Error messages and inaccessible posts are never paid placeholder rows. Unchanged observations are still saved and billed; comparison is metadata, not an output filter.

Key fields: `url`, `uri`, `cid`, `postId`, `contentHash`, `author`, `text`, `createdAt`, `links`, `hashtags`, `facets`, `media`, `replyTo`, `threadRoot`, `metrics`, `requestedBy`, `thread`, `threadCoverage`, `observationComplete` and `observedAt`. Missing optional source fields are null. Facet positions are UTF-8 byte offsets, not JavaScript character indexes. Images/video are linked, not downloaded.

Source `createdAt` is retained as supplied and may differ from indexing time. Metrics are observations, can decrease, and are not historical lifetime guarantees. A quoted record is a reference; quoted-post bodies are not separately expanded.

### Coverage and failures

This Actor reads a **bounded public view**. It does not provide keyword search, follower graphs, private content, a firehose, exhaustive history or a complete conversation. The API can omit moderated, deleted, blocked or otherwise hidden material. A reported reply count can exceed visible replies. `completeConversation` is always false; `contextTruncated` reports the Actor's own output cap separately.

If thread expansion fails but a valid feed post was obtained, that real post can still be saved with `observationComplete:false`. It does not update comparison history. Supplied unavailable posts and wholly failed sources produce diagnostics in `SUMMARY`. A run with no usable rows and upstream failures fails honestly. A valid empty feed or exhausted spending limit can finish with a clear empty/budget status. Intentional maxPosts/page/context caps are scope controls; they are distinct from source failures or timeouts.

The `SUMMARY` record contains saved totals, selected posts, per-source stop reasons, failed and skipped sources, unsaved counts, deadline/spend flags and baseline commits. Check it when a run is partial. The overview dataset table shows the main observation fields.

### Change tracking

Set `comparePrevious:true` and choose a `baselineKey`. Snapshots are stored in the named key-value store **maydit-bluesky-post-observations-v1**, keyed by namespace and stable post URI. They retain only the URI, hash, metrics, version and observation time—not post text or context bodies.

First observations use `comparisonStatus:first_observation` and no numeric deltas. Later observations include `contentChanged`, `metricsChanged` and signed `metricDeltas`. The comparison covers root content and metrics only; thread context changes, handle/display-name changes and deletion are not compared. Missing posts or missing replies never produce deletion claims.

Only a confirmed saved, complete row updates its baseline. A final accepted row at the charge limit counts as saved; an unsaved row does not advance history. A storage-write failure is visible in SUMMARY. Use one writer at a time per namespace, because key-value storage provides no transaction across concurrent runs. Use a new namespace to start a fresh comparison; remove the relevant records in the named store to clear retained observations.

### Three exact examples

#### Bluesky Official Posts and Reply Context

Three recent original posts from the public Bluesky account, with up to 10 visible thread context posts per result.

Use the [copyable first-run input](#try-this-actor) above.

#### AT Protocol Public Updates

Three recent original posts from the public AT Protocol account, with links and engagement metrics. No thread expansion.

```json
{
  "authors": [
    "atproto.com"
  ],
  "maxPosts": 3,
  "includeThread": false
}
```

#### Bluesky Public Thread with Reply Relationships

Inspect one supplied public Bluesky post and a bounded view of its reply relationships. No search or login is used.

```json
{
  "authors": [],
  "postUrls": [
    "https://bsky.app/profile/bsky.app/post/3mx5e63uvns2d"
  ],
  "maxPosts": 1,
  "includeThread": true,
  "threadDepth": 2,
  "parentHeight": 1,
  "maxContextPosts": 20
}
```

### Source, content requests and support

Source: [Bluesky public API](https://bsky.network/docs/api-directory/), [author-feed definition](https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getAuthorFeed.json), [thread definition](https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getPostThread.json), [rate limits](https://bsky.network/docs/rate-limits/) and [developer guidelines](https://bsky.network/docs/developer-guidelines/).

For support or a content-removal request, open this Actor's **Issues** tab or email **bmay@mayd-it.com**. Include the public post URL and relevant run/dataset record reference; do not send credentials. We can address retained data under our control. You control your run datasets and downstream exports, and should provide a deletion/reporting method for any service you build using them. Public access does not transfer content ownership. Respect source policies and applicable rights; this Actor does not automate posting, liking, following or messaging.

## Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

## Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.
