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

Scrape Bluesky without login: search posts, account posts, profiles with follower counts, full reply threads and custom feeds. Text, likes, reposts, hashtags, mentions, links and image alt text in clean JSON.

- **URL**: https://apify.com/cliqtomedia/bluesky-scraper.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Categories:** Social media, AI
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.55 / 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.
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

Scrape public Bluesky posts, profiles, custom feeds, lists and reply threads with no login, and get clean JSON rows with text, engagement counts, links, mentions, hashtags, image alt text, quoted posts and author details.

Use it for social listening, research datasets, brand monitoring or AI pipelines: the smallest run is one search query, such as `apify`, which returns up to 30 recent posts in a few seconds for about $0.03 (100 posts cost about $0.09 on the Free plan, less on paid plans).

![Bluesky Scraper: search queries, handles, post links and feed links in; full post data and clean rows out. No login.](https://api.apify.com/v2/key-value-stores/DfWdbKJRCIh0Pi6Aj/records/README-BANNER-20261013-2.png)

### Table of contents

- [What it does](#what-it-does)
- [Quick start](#quick-start)
- [Use cases](#use-cases)
- [Input](#input)
- [Output](#output)
- [Pricing and limits](#pricing-and-limits)
- [Switching from another Bluesky scraper](#switching-from-another-bluesky-scraper)
- [API and integrations](#api-and-integrations)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Learn more](#learn-more)
- [Support](#support)

### What it does

Bluesky Scraper reads the public Bluesky AppView API (AT Protocol) without an account, a password or cookies. It has five modes:

| Mode | You give | You get |
| --- | --- | --- |
| `searchPosts` | Keywords, `#hashtags`, phrases, operators like `from:bsky.app`, `lang:en`, `domain:example.com` | Up to 100 matching posts per query, latest or top |
| `authorFeed` | Handles, DIDs or profile links | Posts from each account, newest first, with optional replies and reposts |
| `profiles` | Handles, DIDs or profile links | One profile row per account: bio, follower/following/post counts, avatar, pinned post |
| `thread` | Post links or `at://` URIs | The post, its parent posts and the replies below it, with `threadRole` and `threadDepth` |
| `feed` | Custom feed links or list links | Posts from the feed or list |

An account, post or feed that does not exist returns a `not_found` row instead of silently disappearing, so the number of answers always matches your input.

#### Good fit

- Track what people post about a brand, product, hashtag or domain on Bluesky.
- Collect the posts and profiles of a list of accounts for research or lead lists.
- Pull a full reply thread for sentiment or discussion analysis.
- Feed clean, typed Bluesky rows into an AI agent, a spreadsheet or a database.

#### Not a fit / limits

- No private data: no DMs, no muted or blocked lists, nothing that needs login.
- No followers or following lists in this version.
- Search returns at most 100 posts per query, because Bluesky blocks further search pages without login. The Actor does not try to get around this.
- Counts (likes, reposts, followers) are a snapshot at fetch time.

### Quick start

1. Open the **Input** tab.
2. Keep **What to scrape** on **Search posts** and add a word to **Search queries**, e.g. `apify`.
3. Keep **Max posts per query** at the prefilled `30` (the runtime default is `100`).
4. Click **Start**.
5. Open the **Posts** view in the **Output** tab and check `text`, `author.handle` and `likeCount`.

Expected result: up to 30 of the newest posts that match the query, one row per post.

### Use cases

| Buyer job | Input path | Output entity | Key fields | Next step |
| --- | --- | --- | --- | --- |
| Brand or keyword listening | `mode: searchPosts`, `queries` | post | `text`, `createdAt`, `likeCount`, `author.handle`, `links` | Schedule the Actor, export to Sheets or a database |
| Account research | `mode: authorFeed`, `handles`, `since` | post | `text`, `isRepost`, `replyCount`, `externalLink` | Compare accounts, build datasets |
| Influencer or lead list | `mode: profiles`, `handles` | profile | `displayName`, `description`, `followersCount`, `postsCount` | CRM import |
| Discussion analysis | `mode: thread`, `postUrls` | post | `threadRole`, `threadDepth`, `text`, `replyTo` | Sentiment or topic analysis |
| Curated topic monitoring | `mode: feed`, `feedUrls` | post | `text`, `hashtags`, `images[].alt` | AI summarisation |

### Input

#### Input fields

| Field | Plain name | Type | Required | Default / prefill | Allowed values | Effect | Example |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `mode` | What to scrape | string | yes | `searchPosts` | `searchPosts`, `authorFeed`, `profiles`, `thread`, `feed` | Picks the list that is read | `"authorFeed"` |
| `queries` | Search queries | string\[] | for `searchPosts` | prefill `["apify"]` | up to 100 | One search per line | `["#bluesky", "from:bsky.app lang:en"]` |
| `handles` | Handles | string\[] | for `authorFeed`, `profiles` | none | handle, `@handle`, DID, profile link; up to 1,000 (100 for `authorFeed`) | Accounts to read; a bare name becomes `name.bsky.social` | `["bsky.app", "did:plc:z72i7hdynmk6r22z27h6tvur"]` |
| `postUrls` | Post links | string\[] | for `thread` | none | bsky.app post links or `at://` URIs; up to 100 | Threads to read | `["https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l"]` |
| `feedUrls` | Feed or list links | string\[] | for `feed` | none | `…/feed/…` or `…/lists/…` links; up to 50 | Feeds or lists to read | `["https://bsky.app/profile/bsky.app/feed/whats-hot"]` |
| `maxItems` | Max posts per input | integer | no | `100`, prefill `30` | 1–5,000 | Cap for each query, account, thread or feed; ignored in `profiles` | `500` |
| `since` | From date | string | no | none | `YYYY-MM-DD` or ISO date-time, UTC | Only posts created on or after it | `"2026-10-01"` |
| `until` | To date | string | no | none | same; must be after `since` | Only posts created before it | `"2026-10-08"` |
| `searchSort` | Search order | string | no | `latest` | `latest`, `top` | Order of search results | `"top"` |
| `language` | Post language | string | no | all | language code, e.g. `en`, `pt-BR` | Search only | `"en"` |
| `includeReplies` | Include replies | boolean | no | `true` | | Drop replies in search and account posts when off | `false` |
| `includeReposts` | Include reposts | boolean | no | `false` | | Account posts and feeds: also return reposts, marked `isRepost` | `true` |
| `threadDepth` | Reply depth | integer | no | `10` | 0–100 | Reply levels below the post; `0` = post and parents only | `3` |

Lines pasted with several values are split, and duplicates are removed. A value that cannot be read (e.g. `not a handle!`) returns an `invalid_input` row, and the other values are still processed. A wrong field combination, such as `profiles` with no handles or `since` after `until`, stops the run at once with a clear message and no rows.

#### Interacting settings

| When | Input A | Input B | Effective behavior |
| --- | --- | --- | --- |
| Search with a lone hashtag and dates | `queries: ["#ai"]` | `since` / `until` | Sent to Bluesky as a tag search, which is the only form it accepts with dates |
| Account posts with dates | `handles` | `since` | Reading stops once the feed reaches posts older than `since` |
| Account posts with strict filters | `includeReplies: false`, `since` / `until` | — | If fewer than 1 in 100 feed items match after 1,000 items, the account is stopped as `partial` with a warning. Use a search with `from:handle` instead |
| Any mode | `maxItems` | search | Search never returns more than 100 posts per query, whatever `maxItems` says |

#### Input examples

##### Search posts about a topic

```json
{
  "mode": "searchPosts",
  "queries": ["apify", "#webscraping"],
  "maxItems": 50,
  "searchSort": "latest",
  "language": "en"
}
```

Expected output: up to 50 post rows per query with `text`, `author`, `likeCount`, `links`, `hashtags`.

##### Recent posts from accounts

```json
{
  "mode": "authorFeed",
  "handles": ["bsky.app", "@jay.bsky.team"],
  "since": "2026-10-01",
  "includeReposts": true,
  "maxItems": 200
}
```

##### Profiles

```json
{
  "mode": "profiles",
  "handles": ["bsky.app", "jay.bsky.team", "https://bsky.app/profile/pfrazee.com"]
}
```

##### A reply thread

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

### Output

Every row goes to the default Dataset and has `recordType`: `post`, `profile` or `status`. The **Output** tab has **Posts**, **Profiles** and **Not found & errors** views. Views choose columns, not rows: in a posts run the Profiles view shows the same rows with empty profile columns, and status rows appear in the Posts view with empty post columns. To get only one type, filter by `recordType` in your export or code. The run summary is in the key-value store as `RUN_SUMMARY`.

#### Post fields

| Field | Meaning | Type | Can be empty? |
| --- | --- | --- | --- |
| `uri`, `cid` | AT Protocol identifiers of the post | string | `cid` can be null |
| `url` | bsky.app link to the post | string | rarely null |
| `author` | `did`, `handle`, `displayName`, `avatar`, `url` | object | name and avatar can be null |
| `text` | Post text | string | empty for image-only posts |
| `createdAt`, `indexedAt` | Created (by the author's record) and indexed by Bluesky, ISO 8601 | string | can be null |
| `langs` | Declared languages | string\[] | often empty |
| `replyCount`, `repostCount`, `likeCount`, `quoteCount`, `bookmarkCount` | Counts at fetch time | number | null when Bluesky does not report it (never a fake 0) |
| `isReply`, `replyTo`, `rootPost` | Reply status, parent and root post | boolean, object | objects are null for top-level posts |
| `isRepost`, `repostedBy`, `repostedAt` | Repost marker in account posts and feeds | boolean, object, string | null when not a repost |
| `isPinned` | Pinned on the author's profile | boolean | |
| `isQuote`, `quotedPost` | Quoted post: `uri`, `url`, `authorHandle`, `text` | boolean, object | null when not a quote |
| `embedType` | `images`, `video`, `external`, `record`, `recordWithMedia` | string | null without an embed |
| `images` | `url`, `thumbnailUrl`, `alt`, `width`, `height` | object\[] | empty without images |
| `video` | `playlistUrl`, `thumbnailUrl`, `alt` | object | null without a video |
| `externalLink` | Link card: `url`, `title`, `description` | object | null without a card |
| `links` | All links in the text and the link card | string\[] | |
| `hashtags`, `mentions` | Tags and mentioned accounts (`did`, `handle`) | array | |
| `labels` | Moderation labels | string\[] | |
| `threadRole`, `threadDepth` | `anchor`, `parent` or `reply`, and level (thread mode) | string, number | null outside thread mode |
| `mode`, `input`, `fetchedAt`, `source` | Which input produced the row and when | string | |

#### Profile fields

`did`, `handle`, `displayName`, `description`, `url`, `avatar`, `banner`, `followersCount`, `followsCount`, `postsCount`, `listsCount`, `feedsCount`, `starterPacksCount`, `isLabeler`, `verifiedStatus`, `pinnedPostUrl`, `labels`, `createdAt`, `indexedAt`, plus `mode`, `input`, `fetchedAt`, `source`.

#### Status rows

| `status` | When | `errorCode` examples |
| --- | --- | --- |
| `not_found` | The account, post, feed or list does not exist | `NOT_FOUND` |
| `invalid_input` | The value cannot be read as a handle, link or query | `INVALID_INPUT` |
| `error` | Bluesky failed or refused the request after retries | `RATE_LIMITED`, `SOURCE_FORBIDDEN`, `SOURCE_UNAVAILABLE` |

A source failure is never shown as "no results": it is an `error` row and the run status says so.

#### Run summary (`RUN_SUMMARY`)

| Field | Meaning |
| --- | --- |
| `status` | `COMPLETE`, `PARTIAL`, `NO_RESULTS`, `CAPPED`, `ERROR`, `INVALID_INPUT` or `CANCELLED` |
| `errorCode` | Set when the whole run failed for one reason, e.g. `INVALID_INPUT` or `RUN_TIMEOUT_TOO_SHORT`; otherwise `null` |
| `message` | One plain-English sentence about the result |
| `counts` | `posts`, `profiles`, `notFound`, `errors`, `invalidInputs`, `duplicatesSkipped`, `chargedPosts`, `chargedProfiles`, `chargedEmptyResults`, `unprocessedInputs` |
| `inputs` | One entry per input value (`input`, `status`, `items`, `message`): `complete`, `partial`, `no_results`, `not_found`, `error`, `invalid_input` or `skipped`, with the row count |
| `warnings` | Plain-English notes, e.g. the 100-per-query search limit |

#### Output example

A fictional post, trimmed to the main fields:

```json
{
  "recordType": "post",
  "status": "complete",
  "mode": "searchPosts",
  "input": "opendata",
  "uri": "at://did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/app.bsky.feed.post/3mexample2abc",
  "url": "https://bsky.app/profile/author.example/post/3mexample2abc",
  "author": {
    "did": "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa",
    "handle": "author.example",
    "displayName": "Sample Author",
    "url": "https://bsky.app/profile/author.example"
  },
  "text": "Our open dataset is live! Thanks @team.example #opendata https://data.example/downloads",
  "createdAt": "2026-10-12T09:15:00.000Z",
  "likeCount": 2431,
  "repostCount": 318,
  "replyCount": 154,
  "isReply": false,
  "isRepost": false,
  "embedType": "images",
  "images": [
    { "url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:aaaaaaaaaaaaaaaaaaaaaaaa/bafkexample@jpeg", "alt": "Chart of monthly downloads" }
  ],
  "links": ["https://data.example/downloads"],
  "hashtags": ["opendata"],
  "mentions": [{ "did": "did:plc:bbbbbbbbbbbbbbbbbbbbbbbb", "handle": "team.example" }],
  "quotedPost": null,
  "threadRole": null,
  "fetchedAt": "2026-10-13T01:52:14.278Z",
  "source": "Bluesky AppView API (AT Protocol)"
}
```

`null` means Bluesky did not provide the value; it never stands for zero.

### Pricing and limits

#### Pricing unit

You pay per event. Platform usage (compute, proxy, storage) is included in the price.

| Event | Free plan | Starter (Bronze) | Scale (Silver) | Business (Gold) and higher |
| --- | ---: | ---: | ---: | ---: |
| Post row | $0.0009 | $0.00085 | $0.0007 | $0.00055 |
| Profile row | $0.0027 | $0.0026 | $0.0021 | $0.0016 |
| Empty result | $0.0005 | $0.0005 | $0.0005 | $0.0005 |
| Actor start | $0.001 | $0.001 | $0.001 | $0.001 |

- **Post row / profile row:** one per data row written.
- **Empty result:** one per input value that produced no data: a search query with no matches, an account with no matching posts, a profile, post or feed that does not exist, or a value that cannot be read (for example a typo in a link). If every value is unreadable, the run ends as `NO_RESULTS` with a message saying so, and each value is one empty result. It covers the requests made for that value.
- **Actor start:** charged by Apify once per run (per GB of memory; the default 256 MB counts as one).
- **Free:** `error` rows (Bluesky failed), skipped inputs, duplicate posts and duplicate input values (the same account, post or feed written another way, e.g. `@Name.bsky.social` and its profile link).

A run whose input is rejected before any request (e.g. `profiles` without handles) costs only the start fee.

#### Cost examples (Free plan prices)

| Scenario | Input | Charged events | Cost |
| --- | --- | --- | ---: |
| Prefill search | 1 query, 30 posts | start + 30 posts | about $0.028 |
| 1,000 posts from accounts | 2 accounts × 500 | start + 1,000 posts | about $0.90 |
| 100 profiles | 100 handles | start + 100 profiles | about $0.27 |
| Search with no matches | 1 query | start + 1 empty result | $0.0015 |

#### Spending limit

Set **Maximum cost per run** in the run options. The Actor checks the limit before every row; when the next row would exceed it, the run stops with status `CAPPED` and keeps everything written so far.

#### Limits

- Search: up to 100 posts per query (Bluesky's limit without login). Split the topic into narrower queries or date ranges to see more.
- Account posts: up to 10,000 feed items scanned per account. A scan also stops early when fewer than 1 in 100 items match the filters after the first 1,000 items, so a sparse filter does not read the whole history.
- Feeds: the same sparse stop applies.
- Speed at 256 MB: one account's 1,030 posts in about 5 s; 10,000 account posts took about 2–3 minutes in tests. Large jobs need a longer run timeout than the default 300 s; an input cut by the timeout is marked `partial` with its row count.
- `since` / `until` compare the post's `createdAt` (for reposts, `repostedAt`). Account feeds are ordered by when Bluesky indexed the post, so posts imported later with an older date can be outside the scanned range.

### Switching from another Bluesky scraper

Field names differ from other Bluesky Actors. Typical mapping:

| Other Actor | This Actor |
| --- | --- |
| `mode: search-posts` | `mode: searchPosts` |
| `mode: posts` | `mode: authorFeed` |
| `mode: profiles` | `mode: profiles` |
| `mode: threads` / `post-urls` | `mode: thread` |
| `sort` | `searchSort` |
| `maxResults` | `maxItems` |
| `authorHandle`, `authorName`, `authorDid`, `authorAvatar` | `author.handle`, `author.displayName`, `author.did`, `author.avatar` |
| `lang` | `langs` |
| `mediaUrls`, `hasMedia` | `images[].url`, `video.playlistUrl`, `embedType` |
| `embedUrl`, `embedTitle` | `externalLink.url`, `externalLink.title` (also in `links`) |
| `scrapedAt` | `fetchedAt` |

Extra here: image alt text, video, quoted post, repost details, thread roles, custom feeds and lists, and `not_found` rows.

### API and integrations

- **API:** start a run with `POST https://api.apify.com/v2/acts/cliqtomedia~bluesky-scraper/runs` and the JSON input above; read rows from the run's default Dataset.
- **Export:** the Dataset downloads as JSON, CSV, Excel, XML or RSS from the Output tab or the API.
- **Scheduling:** use Apify Schedules, e.g. a daily search with `since` set to yesterday.
- **Integrations:** Make, Zapier, n8n, Google Sheets and webhooks connect through the Apify integrations tab.

#### Use with AI agents (MCP)

- MCP server with only this Actor: `https://mcp.apify.com?tools=cliqtomedia/bluesky-scraper`.
- Smallest useful input: `{"mode": "searchPosts", "queries": ["apify"], "maxItems": 30}`.
- Key fields: `text` (post text), `author.handle` (who posted), `createdAt` (when), `likeCount` (engagement), `url` (link to cite).
- A small search finishes in a few seconds and costs about $0.03. At most 100 posts per search query; `not_found` and `error` rows explain missing data.

### Troubleshooting

#### Search stops at 100 posts

Symptom: you set `maxItems` above 100, a query returns 100 posts, its entry in `inputs` is `partial` and there is a warning about 100 posts per query. With `maxItems` of 100 or less the query is `complete`.
Reason: Bluesky serves only the first search page without login.
Action: add operators (`lang:en`, `from:`, `domain:`) or a narrower `since` / `until`.

#### An account returns no posts

Check the account's entry in `inputs` in `RUN_SUMMARY`. `not_found` means the handle does not exist. `no_results` means the filters matched nothing; `partial` with the sparse-scan warning means the filters matched fewer than 1 in 100 items. Loosen `since` / `until`, turn on replies or reposts, or use search with `from:handle`.

#### The run ends as PARTIAL with skipped inputs

The run hit its timeout. Raise the timeout in the run options or split the input into smaller runs.

#### The run fails with `RUN_TIMEOUT_TOO_SHORT`

The run timeout leaves no time to fetch data and save the summary. Set the timeout to at least 60 seconds; the default 300 seconds is enough for small runs. Nothing is charged except the start.

#### Rows with `error` status

Bluesky failed or refused the request after retries (`RATE_LIMITED`, `SOURCE_FORBIDDEN`, `SOURCE_UNAVAILABLE`). These rows are free; run the failed values again later.

### FAQ

#### Do I need a Bluesky account or app password?

No. The Actor reads only public data from the public Bluesky API and never asks for login.

#### Why is search limited to 100 posts per query?

Bluesky serves further search pages only to logged-in clients. The Actor respects that and tells you in the run summary when a query hit the limit.

#### Are the counts live?

They are a snapshot at `fetchedAt`. Run again to refresh them.

#### Is this data complete?

It is what the Bluesky API returns at fetch time. Deleted posts, blocked content and posts hidden by moderation are not included, and counts can be null when Bluesky does not report them.

#### Can I scrape followers or DMs?

Not in this version. DMs are private and will never be supported.

#### Is scraping Bluesky allowed?

The Actor uses only public endpoints at a modest request rate and does not bypass any limit. You are responsible for how you store and reuse the content, including personal data and authors' rights.

### Learn more

- [Bluesky API directory](https://docs.bsky.app/docs/advanced-guides/api-directory): the public AppView endpoints this Actor reads.
- [Search API reference](https://docs.bsky.app/docs/api/app-bsky-feed-search-posts): query operators such as `from:`, `lang:` and `domain:`.
- [AT Protocol](https://atproto.com/): how DIDs, handles and `at://` URIs work.
- [Bluesky Terms of Service](https://bsky.social/about/support/tos).

### Support

Use the **Issues** tab of this Actor for bugs, questions and feature requests. Include the run ID, the mode and the error text. Never post tokens, cookies or private data.

# Changelog

This Actor's version history is a separate document: https://apify.com/cliqtomedia/bluesky-scraper/changelog.md

# Actor input Schema

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

searchPosts: posts matching keywords, hashtags or from:/lang: operators (up to 100 per query). authorFeed: posts from accounts. profiles: profile details and follower counts. thread: a post with its parents and replies. feed: posts from a custom feed or list.

## `queries` (type: `array`):

Keywords, phrases in quotes, #hashtags, or Bluesky search operators such as from:bsky.app, lang:en, domain:example.com. One query per line, up to 100. Bluesky returns at most 100 posts per query without login.

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

Bluesky handles (bsky.app, @jay.bsky.team), DIDs or profile links (https://bsky.app/profile/…). A bare name becomes name.bsky.social. Up to 100 accounts for posts, 1,000 for profiles.

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

Post links like https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l or at:// URIs. Up to 100.

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

Custom feed links (https://bsky.app/profile/…/feed/…) or list links (https://bsky.app/profile/…/lists/…). Up to 50.

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

Upper limit of posts for each input value (profiles mode: ignored, one row per handle). Search stops at 100 per query. You pay per row written; a value with no data at all is one empty result.

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

Only posts created on or after this date, e.g. 2026-10-01 or 2026-10-01T12:00:00Z (UTC). Applies to search, account posts and feeds.

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

Only posts created before this date (UTC).

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

latest: newest first. top: Bluesky's ranking of popular posts.

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

Language code for search, e.g. en, de, ja, pt-BR. Empty: all languages.

## `includeReplies` (type: `boolean`):

Keep replies in search and account posts. Turn off to get only top-level posts.

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

Also return posts the account reposted, marked isRepost with repostedBy and repostedAt.

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

How many reply levels below the post to load. 0 returns only the post and its parents.

## Actor input object example

```json
{
  "mode": "searchPosts",
  "queries": [
    "apify"
  ],
  "maxItems": 30,
  "searchSort": "latest",
  "includeReplies": true,
  "includeReposts": false,
  "threadDepth": 10
}
```

# Actor output Schema

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

Post columns for every row. Status rows show here with empty post columns.

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

Profile columns. Filled only when the mode is profiles.

## `statuses` (type: `string`):

Status columns: input, status, errorCode and message for values that were not found, invalid or failed.

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

Status, counts, charged rows, warnings and request statistics.

# 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": "searchPosts",
    "queries": [
        "apify"
    ],
    "maxItems": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/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": "searchPosts",
    "queries": ["apify"],
    "maxItems": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/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": "searchPosts",
  "queries": [
    "apify"
  ],
  "maxItems": 30
}' |
apify call cliqtomedia/bluesky-scraper --silent --output-dataset

```

## MCP server setup

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