# Bluesky Scraper – Posts Search, Profiles & Leads (`dedicated_crest/bluesky-posts-profiles-search`) Actor

Search Bluesky posts by keyword or hashtag with date and language filters, or get author profiles with bio links, emails, website domains, followers and recent posts. Official AT Protocol API, no login, no proxy.

- **URL**: https://apify.com/dedicated_crest/bluesky-posts-profiles-search.md
- **Developed by:** [Ben Barazandeh](https://apify.com/dedicated_crest) (community)
- **Categories:** Social media, Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bluesky Scraper – Posts Search, Profiles & Leads

**Search Bluesky posts by keyword or hashtag and turn the authors into leads.** Get posts with likes, reposts, replies, hashtags, links and language, filtered by date range and language. Or switch to **profiles mode** to get one row per account with the bio, follower counts, and the **links, emails and website domains found in the bio**. You can also add each account's followers and latest posts. It uses only Bluesky's official public AT Protocol API (`api.bsky.app`), so you don't need a login, an API key, proxies or HTML scraping.

**Try it now:** the input is pre-filled with `#opensource` and 50 posts. Click **Start**.

### What can you do with it?

- **Brand and competitor monitoring**: schedule a daily search for your product name, a competitor or a domain (`linkDomain`), and get every new post.
- **Lead generation on Bluesky**: find the founders, agencies, developers and creators posting about a topic, and export their websites, custom-domain handles and public emails to a CSV or your CRM.
- **Hashtag and trend research**: pull every `#buildinpublic` or `#booksky` post in a date range, with engagement counts.
- **Influencer discovery**: run an account search, filter by `minFollowers`, and include each account's recent posts to check what they write about.
- **AI, NLP and sentiment datasets**: clean post text with language tags, hashtags and links in consistent fields.

### Modes

| Mode | One row per | What you get |
|---|---|---|
| `posts` | matching post | text, created date, likes, reposts, replies, quotes, hashtags, links, link-card title, images, reply/quote info, author handle and name, post URL |
| `profiles` | unique account | handle, name, bio, `bioLinks`, `emails`, `websiteDomains`, `socialLinks` (LinkedIn, GitHub, Instagram, X, YouTube, Linktree and others), `customDomainHandle`, followers, following, post count, created date. Optional `followers[]` and `recentPosts[]` |

Profiles mode finds accounts in one of two ways. **Post authors** (the default) collects the authors of posts that match your query, which means people who are actively talking about the topic. **Account search** matches against name, handle and bio. You can also list specific `handles`.

### Input

| Field | Notes |
|---|---|
| `queries` | Keywords, `"exact phrases"` or `#hashtags`. Bluesky search operators such as `from:handle` also work. |
| `maxItems` | Per query. |
| `sort` | `latest` (default) or `top`. |
| `dateFrom` / `dateTo` | `YYYY-MM-DD` or an ISO date-time, in UTC. `dateTo` includes the whole day. |
| `language` | e.g. `en`, `de`, `ja`. This is the language tag the author's app set on the post. |
| `author`, `linkDomain` | Optional post filters. |
| `minFollowers`, `onlyWithContact` | Profile filters. `onlyWithContact` keeps an account only if it has an email, a website or a custom-domain handle. |
| `maxFollowersPerProfile`, `maxFeedPostsPerProfile` | Optional enrichment for each profile. 0 turns it off. |

### Real run results (28 Oct 2026)

- **Posts:** run `NznnFtwDxJ6eRqYml` with `{"queries":["#opensource","bluesky api"],"maxItems":150,"dateFrom":"2026-10-20","language":"en"}` saved **300 posts** (150 for each query, 300 unique, all tagged `en`) in about 20 seconds.
- **Profiles from post authors:** run `cMZa1JEg8EQS8MBkb` with `{"mode":"profiles","queries":["#buildinpublic"],"maxItems":40,"maxFollowersPerProfile":10,"maxFeedPostsPerProfile":3}` saved **40 profiles**. 23 had a website domain in the bio and 12 used a custom-domain handle. The run also returned 335 follower rows.
- **Profiles from account search:** run `oBMfEUuXfMkTKNatC` with `{"mode":"profiles","queries":["marketing agency"],"profileSource":"accountSearch","maxItems":25,"onlyWithContact":true}` saved **25 lead profiles**. 21 had a website domain, 7 used a custom-domain handle and 2 listed a public email.

Sample post:

```json
{ "type": "post", "query": "#opensource", "url": "https://bsky.app/profile/valkeyio.bsky.social/post/3mywyxsrjqk2n",
  "text": "Suffering from information overload across a zillion apps? Planet Valkey is here! … #Valkey #OpenSource",
  "createdAt": "2026-10-28T14:53:57.218Z", "langs": ["en"], "likeCount": 0, "repostCount": 0, "replyCount": 0,
  "hashtags": ["Valkey", "OpenSource"], "links": ["https://valkey.io/blog/announcing-planet-valkey/"],
  "externalTitle": "Valkey: Announcing Planet Valkey", "authorHandle": "valkeyio.bsky.social" }
```

Sample profile (shortened):

```json
{ "type": "profile", "handle": "redfstudios.bsky.social", "displayName": "RED-F Studios",
  "description": "We build Android apps, websites and free browser tools. … at redfstudios.com/tools. Now building NotiPin.",
  "bioLinks": ["redfstudios.com/tools"], "emails": [], "websiteDomains": ["redfstudios.com"], "customDomainHandle": null,
  "followersCount": 35, "followsCount": 130, "postsCount": 92, "followers": [ … ], "recentPosts": [ … ] }
```

### Pricing

**Pay per event, with no start fee:**

- **$0.001 per post** ($1 per 1,000 posts) in posts mode
- **$0.002 per profile** ($2 per 1,000 profiles) in profiles mode

The optional `followers[]` and `recentPosts[]` arrays come with each profile at no extra charge. Set *Maximum cost per run* and the Actor stops cleanly when it hits the limit. The 300-post test run above used about $0.002 of platform compute.

### Limits (please read)

- **Paging:** Bluesky blocks cursor pagination on unauthenticated search (it returns 403), so the Actor pages by moving the date window back in time. With `sort: latest` this gives you the full time range. With `sort: top`, results are "top within each window", not one global ranking.
- **Search coverage:** results come from Bluesky's own search index. It can miss some posts, and deleted or moderated posts don't appear.
- **Language** depends on the tag the posting app set, so some posts are untagged or tagged wrongly.
- **Emails** are only the ones people typed into their public bio, including `name [at] domain` forms. Most accounts don't list one. Bluesky doesn't expose private emails, and this Actor doesn't guess them.
- **Bio links** are detected in plain text. Links cut short by the user can be partial.
- **Rate limits:** requests are spaced at about 4 per second, and the Actor backs off on HTTP 429 or 5xx using Bluesky's `ratelimit-reset` header. Large follower lists add one call per 100 followers.

### FAQ

**Is it allowed?** It reads only public data through Bluesky's official, open AT Protocol API, which is designed for third-party access. Follow Bluesky's community guidelines and anti-spam rules, and applicable privacy law such as GDPR or CAN-SPAM, when you contact anyone you find here.

**Affiliation?** This Actor is independent and not affiliated with Bluesky Social PBC.

# Actor input Schema

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

Posts returns one row per matching post. Profiles returns one row per unique account, with optional followers and recent posts.

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

Keywords, phrases ("in quotes") or hashtags (#buildinpublic). Lucene-style syntax is supported by Bluesky search, e.g. from:handle, domain:example.com.

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

Maximum posts (posts mode) or unique profiles (profiles mode) saved for each query.

## `sort` (type: `string`):

Bluesky search ranking.

## `dateFrom` (type: `string`):

Only posts created on or after this date (YYYY-MM-DD or ISO date-time, UTC).

## `dateTo` (type: `string`):

Only posts created on or before this date (inclusive, UTC).

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

Two-letter language code, e.g. en, de, ja, pt. Uses the language tag the author's app set on the post.

## `author` (type: `string`):

Optional: restrict post search to one account.

## `linkDomain` (type: `string`):

Optional: only posts containing a link to this domain, e.g. github.com.

## `textMaxChars` (type: `integer`):

0 = full text (Bluesky posts are at most 300 characters).

## `profileSource` (type: `string`):

Profiles mode only.

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

Profiles mode: also fetch these handles, DIDs or bsky.app profile URLs.

## `minFollowers` (type: `integer`):

Skip accounts with fewer followers.

## `onlyWithContact` (type: `boolean`):

Keep only profiles where a lead signal was found.

## `maxFollowersPerProfile` (type: `integer`):

Adds a followers array (handle, name, bio) to each profile. 0 = off. Each 100 followers is one extra API call.

## `maxFeedPostsPerProfile` (type: `integer`):

Adds the account's latest original posts (no replies) as recentPosts. 0 = off.

## Actor input object example

```json
{
  "mode": "posts",
  "queries": [
    "#opensource"
  ],
  "maxItems": 50,
  "sort": "latest",
  "textMaxChars": 0,
  "profileSource": "postAuthors",
  "minFollowers": 0,
  "onlyWithContact": false,
  "maxFollowersPerProfile": 0,
  "maxFeedPostsPerProfile": 0
}
```

# Actor output Schema

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

Bluesky posts or profiles

# 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": "posts",
    "queries": [
        "#opensource"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("dedicated_crest/bluesky-posts-profiles-search").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": "posts",
    "queries": ["#opensource"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("dedicated_crest/bluesky-posts-profiles-search").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": "posts",
  "queries": [
    "#opensource"
  ],
  "maxItems": 50
}' |
apify call dedicated_crest/bluesky-posts-profiles-search --silent --output-dataset

```

## MCP server setup

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

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/GxV2kUqe9bPaEXVfL/builds/0YGmwLlye0MKiyX0i/openapi.json
