# Bluesky Scraper - Profiles, Posts, Followers, Search (`actorzlab/bluesky-scraper`) Actor

Scrape Bluesky via the official AT Protocol: profiles, posts, post search, followers, following, threads & custom feeds. No proxy required, no anti-bot — official open API. App password optional.

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

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Bluesky Scraper — Profiles, Posts, Followers & Search via the Open AT Protocol

Scrape any public Bluesky data through the **official AT Protocol API** — no proxies, no anti-bot fights, no terms-of-service grey areas. One actor covers seven use cases: profile scraping, post feeds, keyword search, follower/following graphs, thread expansion, and custom feed scraping.

---

### Features

| Feature | Detail |
|---|---|
| **7 scraping modes** | profile · posts · search · followers · following · thread · feed |
| **No proxy needed** | Official public API; Bluesky does not block scrapers |
| **Auth optional** | Most modes work without a Bluesky account; search & custom feeds work best with an app password |
| **Structured output** | Typed JSON records with consistent field names; covers text, media, embeds, counts |
| **Pagination** | Automatically pages through all results up to your `maxItems` cap |
| **Resilient** | Per-item error isolation; exponential backoff on rate limits (HTTP 429) |
| **Pay-per-event** | Only pay for what you scrape — profiles, posts, or connections |
| **MIT-licensed API** | Uses the MarshalX `atproto` Python SDK |

---

### Why Bluesky scraping beats Twitter / X scraping

| Aspect | Bluesky (this actor) | Twitter / X |
|---|---|---|
| API type | Official, documented AT Protocol | Reverse-engineered / unofficial |
| Anti-bot measures | None — open protocol | Heavy: CAPTCHAs, rate-limit bans, IP blocks |
| Proxy cost | **Zero** — direct connection | High — residential proxies often required |
| Legal standing | Public data, open protocol, no ToS conflict | Grey area; ToS explicitly prohibits scraping |
| Authentication | App password optional (free) | Paid API tiers ($100–$5,000/mo) |
| Data freshness | Real-time | Delayed or restricted on free tiers |

---

### Use Cases

- **AI training datasets** — collect large-scale post corpora with text, language tags, and engagement signals.
- **Social media analytics** — track follower growth, post volume, engagement rates across accounts.
- **Journalism & OSINT** — search posts by keyword and date range, expand threads for context.
- **Brand monitoring** — monitor mentions of a brand, product, or topic across the network.
- **AT Protocol research** — study the social graph, feed algorithms, or labeling systems.

---

### Input

#### Mode

Select exactly **one mode** per run. Combine modes by running the actor multiple times (trivially parallelizable on the Apify platform).

| Mode | What it returns | Required input fields |
|---|---|---|
| `profile` | Full profile records for one or more handles/DIDs | `handles` |
| `posts` | Recent posts by one or more accounts | `handles`, `maxItems` |
| `search` | Posts matching a keyword query | `searchQuery`, `maxItems` (+ auth recommended) |
| `followers` | Accounts that follow a handle | `handles`, `maxItems` |
| `following` | Accounts a handle follows | `handles`, `maxItems` |
| `thread` | Full reply tree for a post URL | `postUrls` |
| `feed` | Posts from a custom feed generator | `feedUrls`, `maxItems` (+ auth recommended) |

#### All input fields

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | enum | `profile` | Scraping mode (required) |
| `handles` | string[] | — | Bluesky handles or DIDs; used by profile, posts, followers, following |
| `searchQuery` | string | — | Keyword/phrase to search (search mode) |
| `postUrls` | string[] | — | Post URLs or `at://` URIs (thread mode) |
| `feedUrls` | string[] | — | Feed generator `at://` URIs (feed mode) |
| `maxItems` | integer | 100 | Max records per handle/query/feed |
| `searchSince` | string | — | ISO date lower bound for search (e.g. `2024-01-01`) |
| `searchUntil` | string | — | ISO date upper bound for search |
| `searchLanguage` | string | — | BCP-47 language filter for search (e.g. `en`) |
| `searchSort` | enum | `latest` | `latest` or `top` |
| `threadDepth` | integer | 6 | How many reply levels to expand (max 1000) |
| `threadParentHeight` | integer | 80 | How many parent levels to walk up (max 1000) |
| `blueskyHandle` | string | — | Your Bluesky handle (optional auth) |
| `blueskyAppPassword` | string | — | **App password** — see Authentication section |
| `proxyConfiguration` | object | — | Optional Apify Proxy (rarely needed) |

#### Example inputs

**Profile scrape (no auth needed):**
```json
{
  "mode": "profile",
  "handles": ["bsky.app", "jay.bsky.team", "pfrazee.com"]
}
````

**Keyword search:**

```json
{
  "mode": "search",
  "searchQuery": "open source AI",
  "maxItems": 200,
  "searchSort": "latest",
  "searchLanguage": "en",
  "blueskyHandle": "alice.bsky.social",
  "blueskyAppPassword": "xxxx-xxxx-xxxx-xxxx"
}
```

**Follower list:**

```json
{
  "mode": "followers",
  "handles": ["bsky.app"],
  "maxItems": 500
}
```

**Thread expansion:**

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

**Recent posts by a user:**

```json
{
  "mode": "posts",
  "handles": ["pfrazee.com"],
  "maxItems": 100
}
```

***

### Output

#### Record families

The actor produces three types of records, all tagged with a `mode` field.

##### Profile record (`mode: "profile"`)

| Field | Type | Example |
|---|---|---|
| `mode` | string | `"profile"` |
| `scrapedAt` | ISO datetime | `"2024-11-15T12:34:56.789Z"` |
| `did` | string | `"did:plc:z72i7hdynmk6r22z27h6tvur"` |
| `handle` | string | `"bsky.app"` |
| `displayName` | string|null | `"Bluesky"` |
| `description` | string|null | `"What's up?"` |
| `followersCount` | integer | `1234567` |
| `followsCount` | integer | `42` |
| `postsCount` | integer | `891` |
| `avatar` | url|null | `"https://cdn.bsky.app/img/avatar/..."` |
| `banner` | url|null | `"https://cdn.bsky.app/img/banner/..."` |
| `createdAt` | ISO datetime|null | `"2022-11-17T00:00:00.000Z"` |
| `indexedAt` | ISO datetime|null | `"2024-01-01T09:00:00.000Z"` |
| `labels` | string\[] | `[]` |
| `pinnedPostUri` | string|null | `"at://did:plc:.../app.bsky.feed.post/..."` |
| `profileUrl` | url | `"https://bsky.app/profile/bsky.app"` |

**Example:**

```json
{
  "mode": "profile",
  "scrapedAt": "2024-11-15T12:34:56.789000+00:00",
  "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
  "handle": "bsky.app",
  "displayName": "Bluesky",
  "description": "What's up?",
  "followersCount": 1423891,
  "followsCount": 48,
  "postsCount": 912,
  "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreiabcd@jpeg",
  "banner": null,
  "createdAt": "2022-11-17T00:00:00.000Z",
  "indexedAt": "2024-01-01T09:00:00.000Z",
  "labels": [],
  "pinnedPostUri": null,
  "profileUrl": "https://bsky.app/profile/bsky.app"
}
```

##### Post record (`mode` ∈ `posts` / `search` / `thread` / `feed`)

| Field | Type | Example |
|---|---|---|
| `mode` | string | `"posts"` |
| `scrapedAt` | ISO datetime | `"2024-11-15T12:34:56Z"` |
| `uri` | string | `"at://did:plc:.../app.bsky.feed.post/3la..."` |
| `cid` | string | `"bafyreia..."` |
| `authorDid` | string | `"did:plc:..."` |
| `authorHandle` | string | `"alice.bsky.social"` |
| `authorDisplayName` | string|null | `"Alice"` |
| `text` | string | `"Hello Bluesky!"` |
| `createdAt` | ISO datetime|null | `"2024-11-15T10:00:00.000Z"` |
| `indexedAt` | ISO datetime|null | `"2024-11-15T10:00:01.000Z"` |
| `langs` | string\[] | `["en"]` |
| `replyCount` | integer | `12` |
| `repostCount` | integer | `34` |
| `likeCount` | integer | `156` |
| `quoteCount` | integer | `5` |
| `bookmarkCount` | integer|null | `null` |
| `isRepost` | boolean | `false` |
| `isReply` | boolean | `false` |
| `replyParentUri` | string|null | `null` |
| `replyRootUri` | string|null | `null` |
| `images` | array | `[{"fullsize": "...", "thumb": "...", "alt": "..."}]` |
| `externalLink` | object|null | `{"uri": "...", "title": "...", "description": "..."}` |
| `quotedPostUri` | string|null | `null` |
| `quotedPostText` | string|null | `null` |
| `video` | object|null | `{"playlist": "...", "thumbnail": "..."}` |
| `postUrl` | url|null | `"https://bsky.app/profile/alice.bsky.social/post/3la..."` |

##### Connection record (`mode` ∈ `followers` / `following`)

| Field | Type | Example |
|---|---|---|
| `mode` | string | `"followers"` |
| `scrapedAt` | ISO datetime | `"2024-11-15T12:34:56Z"` |
| `subjectDid` | string | `"did:plc:..."` (the queried account) |
| `subjectHandle` | string | `"bsky.app"` (the queried account) |
| `did` | string | `"did:plc:..."` (the follower/followed) |
| `handle` | string | `"bob.bsky.social"` |
| `displayName` | string|null | `"Bob"` |
| `avatar` | url|null | `"https://cdn.bsky.app/img/avatar/..."` |
| `description` | string|null | `"Builder."` |

***

### Pricing

This actor uses pay-per-event pricing — you only pay for records actually pushed to the dataset.

| Event | Price | When charged |
|---|---|---|
| Profile scraped | **$0.002** | One full profile record (`profile` mode) |
| Post scraped | **$0.0004** | One post record (`posts`, `search`, `thread`, `feed`) |
| Connection scraped | **$0.0002** | One follower/following edge (`followers`, `following`) |

#### Example costs

| Task | Records | Cost |
|---|---|---|
| 1,000 profiles | 1,000 × $0.002 | **$2.00** |
| 10,000 posts (keyword search) | 10,000 × $0.0004 | **$4.00** |
| 50,000 follower records | 50,000 × $0.0002 | **$10.00** |
| 500 profiles + 5,000 posts | 500 × $0.002 + 5,000 × $0.0004 | **$3.00** |

> **Note:** Pay-per-event pricing takes **14 days** to take effect after monetization is configured.

***

### Authentication

Most modes (`profile`, `posts`, `followers`, `following`, `thread`) work without a Bluesky account.

For **search** and **feed** modes, the public AppView may require authentication. Provide:

- `blueskyHandle` — your Bluesky handle, e.g. `alice.bsky.social`
- `blueskyAppPassword` — an **app password** (NOT your main account password)

#### Creating an app password

1. Log in to [bsky.app](https://bsky.app)
2. Go to **Settings → App Passwords**: https://bsky.app/settings/app-passwords
3. Click **Add App Password**, give it a name (e.g. "Apify Scraper"), and copy the generated code.
4. The format is `xxxx-xxxx-xxxx-xxxx`. Paste it into the `blueskyAppPassword` input field.

App passwords have limited scope (no DM access, no account deletion) and can be revoked individually at any time without affecting your account. **Never enter your main Bluesky password.**

***

### Rate Limits & Throughput

Bluesky's public AppView offers generous rate limits for read-only access. The actor:

- Fetches up to **100 records per API request** (the maximum page size).
- Automatically retries on HTTP 429 (rate limit) and 5xx errors with **exponential backoff**, honouring `Retry-After` headers when present.
- Isolates failures per item — one failed profile/post/edge does not stop the rest of the run.

**Practical throughput:** expect tens of thousands of records per run without hitting limits, depending on your account's tier and the specific endpoints used. For very large runs (100k+ records), add authentication to benefit from higher per-account limits.

***

### FAQ

**1. Will the actor slow down or get blocked at high volumes?**
No blocking — this is an official API. If you hit a rate limit, the actor backs off automatically and retries. For sustained high-volume runs, provide an app password to use per-account limits (higher than per-IP limits).

**2. Do I need a Bluesky account?**
No, for most modes. Profile, posts, followers, following, and thread modes all work unauthenticated. Search and custom-feed modes may return a 401 if used without credentials — just add `blueskyHandle` + `blueskyAppPassword` in that case.

**3. What data is public on Bluesky?**
All posts, profiles, follower graphs, and custom feeds are public by design (AT Protocol is an open, federated network). DMs and muted/blocked relationships are not accessible via the public API.

**4. Why might a field be `null`?**
A few reasons: the post was deleted before scraping, the account is deactivated, the field is viewer-scoped and requires auth (e.g. `bookmarkCount`), or the field is simply optional in the AT Protocol schema (e.g. `banner`, `displayName`).

**5. How is this better than scraping Twitter/X?**
No reverse-engineering, no CAPTCHA, no residential proxy costs, no ToS violation. Bluesky's AT Protocol is documented and open; this actor uses the same API Bluesky's own apps use. See the comparison table at the top of this README.

***

### Legal & Compliance

Bluesky is built on the open AT Protocol. All data scraped by this actor is publicly accessible on the network by design.

- **Respect creators:** only use scraped content in ways consistent with the rights of the people who created it.
- **GDPR / CCPA:** if you process personal data from EU or California residents, you are responsible for complying with applicable data-protection law (legal basis, retention limits, subject rights, etc.).
- **Redistribution:** do not redistribute or commercially exploit scraped content beyond what is permitted by applicable law and the relevant terms.
- **Rate limits:** do not intentionally bypass rate limits or attempt to extract data at a rate that degrades service for other users.

# Actor input Schema

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

What to scrape. Pick exactly one mode per run.

• **profile** — full profile data for one or more handles/DIDs (use `handles`).
• **posts** — recent posts by one or more accounts (use `handles` + `maxItems`).
• **search** — posts matching a keyword query (use `searchQuery`; requires auth).
• **followers** — accounts that follow a handle (use `handles` + `maxItems`).
• **following** — accounts a handle follows (use `handles` + `maxItems`).
• **thread** — full thread tree for a post URL or at:// URI (use `postUrls`).
• **feed** — posts from a custom Bluesky feed generator (use `feedUrls`; requires auth).

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

Bluesky handles (e.g. `bsky.app`, `alice.bsky.social`) or DIDs (e.g. `did:plc:…`). Used by modes: **profile**, **posts**, **followers**, **following**. For profile mode all handles are fetched in a single batched request (up to 25 per call). For posts/followers/following each handle is processed individually.

## `searchQuery` (type: `string`):

Keyword or phrase to search for (mode: **search** only). Lucene-style syntax is supported by Bluesky (e.g. `"open source" lang:en`). Requires authentication — set `blueskyHandle` + `blueskyAppPassword`.

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

URLs or at:// URIs of posts to expand into full threads (mode: **thread** only). Accepts both `https://bsky.app/profile/<handle>/post/<rkey>` links and `at://<did>/app.bsky.feed.post/<rkey>` URIs.

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

AT Protocol URIs of custom feed generators to scrape (mode: **feed** only). Format: `at://<did>/app.bsky.feed.generator/<name>`. Requires authentication for most feeds — set `blueskyHandle` + `blueskyAppPassword`.

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

Maximum number of records to fetch per handle (modes: posts, followers, following), per query (search), or per feed URI (feed). Not used in profile or thread modes (profile fetches all listed handles; thread expands the full thread up to `threadDepth`).

## `searchSince` (type: `string`):

Return posts at or after this date/time (mode: **search** only). ISO 8601 format, e.g. `2024-01-01` or `2024-01-01T00:00:00Z`.

## `searchUntil` (type: `string`):

Return posts before this date/time (mode: **search** only). ISO 8601 format, e.g. `2024-12-31`.

## `searchLanguage` (type: `string`):

Filter search results to posts written in this language (mode: **search** only). BCP-47 code, e.g. `en`, `es`, `fr`, `ja`.

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

Sort order for search results (mode: **search** only). `latest` = newest first (default); `top` = highest-engagement first.

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

How many levels of replies to expand below the target post (mode: **thread** only). Default 6; max 1000.

## `threadParentHeight` (type: `integer`):

How many levels of parent posts to walk above the target post (mode: **thread** only). Default 80; max 1000.

## `blueskyHandle` (type: `string`):

Your Bluesky handle, e.g. `alice.bsky.social`. Optional for most modes. **Required for search and feed modes**, which the public API may block for unauthenticated callers. Pair with `blueskyAppPassword`.

## `blueskyAppPassword` (type: `string`):

**Use an APP PASSWORD — NOT your main Bluesky account password.** Create one at https://bsky.app/settings/app-passwords (format: `xxxx-xxxx-xxxx-xxxx`). Your main password is never needed and should never be entered here. App passwords have limited scope and can be revoked individually without affecting your account.

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

Optional proxy for outbound requests. Bluesky's public API does not require proxies — leave empty for a direct connection. Only set this if your environment restricts outbound traffic.

## Actor input object example

```json
{
  "mode": "profile",
  "handles": [
    "alice.bsky.social",
    "did:plc:ragtjsm2j2vknwkz3zp4oxrd"
  ],
  "searchQuery": "open source AT Protocol",
  "postUrls": [
    "https://bsky.app/profile/bsky.app/post/3laahvvjbek2j"
  ],
  "feedUrls": [
    "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot"
  ],
  "maxItems": 100,
  "searchSince": "2024-01-01",
  "searchUntil": "2024-12-31",
  "searchLanguage": "en",
  "searchSort": "latest",
  "threadDepth": 6,
  "threadParentHeight": 80,
  "blueskyHandle": "alice.bsky.social",
  "blueskyAppPassword": "xxxx-xxxx-xxxx-xxxx"
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# 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": "profile",
    "handles": [
        "bsky.app"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("actorzlab/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": "profile",
    "handles": ["bsky.app"],
}

# Run the Actor and wait for it to finish
run = client.actor("actorzlab/bluesky-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": "profile",
  "handles": [
    "bsky.app"
  ]
}' |
apify call actorzlab/bluesky-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=actorzlab/bluesky-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Bluesky Scraper - Profiles, Posts, Followers, Search",
        "description": "Scrape Bluesky via the official AT Protocol: profiles, posts, post search, followers, following, threads & custom feeds. No proxy required, no anti-bot — official open API. App password optional.",
        "version": "0.0",
        "x-build-id": "dcK3fie8bIr59TAlM"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/actorzlab~bluesky-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-actorzlab-bluesky-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/actorzlab~bluesky-scraper/runs": {
            "post": {
                "operationId": "runs-sync-actorzlab-bluesky-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/actorzlab~bluesky-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-actorzlab-bluesky-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "mode"
                ],
                "properties": {
                    "mode": {
                        "title": "Mode",
                        "enum": [
                            "profile",
                            "posts",
                            "search",
                            "followers",
                            "following",
                            "thread",
                            "feed"
                        ],
                        "type": "string",
                        "description": "What to scrape. Pick exactly one mode per run.\n\n• **profile** — full profile data for one or more handles/DIDs (use `handles`).\n• **posts** — recent posts by one or more accounts (use `handles` + `maxItems`).\n• **search** — posts matching a keyword query (use `searchQuery`; requires auth).\n• **followers** — accounts that follow a handle (use `handles` + `maxItems`).\n• **following** — accounts a handle follows (use `handles` + `maxItems`).\n• **thread** — full thread tree for a post URL or at:// URI (use `postUrls`).\n• **feed** — posts from a custom Bluesky feed generator (use `feedUrls`; requires auth).",
                        "default": "profile"
                    },
                    "handles": {
                        "title": "Handles / DIDs",
                        "type": "array",
                        "description": "Bluesky handles (e.g. `bsky.app`, `alice.bsky.social`) or DIDs (e.g. `did:plc:…`). Used by modes: **profile**, **posts**, **followers**, **following**. For profile mode all handles are fetched in a single batched request (up to 25 per call). For posts/followers/following each handle is processed individually.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "searchQuery": {
                        "title": "Search query",
                        "type": "string",
                        "description": "Keyword or phrase to search for (mode: **search** only). Lucene-style syntax is supported by Bluesky (e.g. `\"open source\" lang:en`). Requires authentication — set `blueskyHandle` + `blueskyAppPassword`."
                    },
                    "postUrls": {
                        "title": "Post URLs / URIs",
                        "type": "array",
                        "description": "URLs or at:// URIs of posts to expand into full threads (mode: **thread** only). Accepts both `https://bsky.app/profile/<handle>/post/<rkey>` links and `at://<did>/app.bsky.feed.post/<rkey>` URIs.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "feedUrls": {
                        "title": "Feed generator URIs",
                        "type": "array",
                        "description": "AT Protocol URIs of custom feed generators to scrape (mode: **feed** only). Format: `at://<did>/app.bsky.feed.generator/<name>`. Requires authentication for most feeds — set `blueskyHandle` + `blueskyAppPassword`.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxItems": {
                        "title": "Max items per handle / query / feed",
                        "minimum": 1,
                        "maximum": 5000,
                        "type": "integer",
                        "description": "Maximum number of records to fetch per handle (modes: posts, followers, following), per query (search), or per feed URI (feed). Not used in profile or thread modes (profile fetches all listed handles; thread expands the full thread up to `threadDepth`).",
                        "default": 100
                    },
                    "searchSince": {
                        "title": "Search: since date",
                        "type": "string",
                        "description": "Return posts at or after this date/time (mode: **search** only). ISO 8601 format, e.g. `2024-01-01` or `2024-01-01T00:00:00Z`."
                    },
                    "searchUntil": {
                        "title": "Search: until date",
                        "type": "string",
                        "description": "Return posts before this date/time (mode: **search** only). ISO 8601 format, e.g. `2024-12-31`."
                    },
                    "searchLanguage": {
                        "title": "Search: language filter",
                        "type": "string",
                        "description": "Filter search results to posts written in this language (mode: **search** only). BCP-47 code, e.g. `en`, `es`, `fr`, `ja`."
                    },
                    "searchSort": {
                        "title": "Search: sort order",
                        "enum": [
                            "latest",
                            "top"
                        ],
                        "type": "string",
                        "description": "Sort order for search results (mode: **search** only). `latest` = newest first (default); `top` = highest-engagement first.",
                        "default": "latest"
                    },
                    "threadDepth": {
                        "title": "Thread: reply depth",
                        "minimum": 1,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "How many levels of replies to expand below the target post (mode: **thread** only). Default 6; max 1000.",
                        "default": 6
                    },
                    "threadParentHeight": {
                        "title": "Thread: parent height",
                        "minimum": 0,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "How many levels of parent posts to walk above the target post (mode: **thread** only). Default 80; max 1000.",
                        "default": 80
                    },
                    "blueskyHandle": {
                        "title": "Bluesky handle (optional auth)",
                        "type": "string",
                        "description": "Your Bluesky handle, e.g. `alice.bsky.social`. Optional for most modes. **Required for search and feed modes**, which the public API may block for unauthenticated callers. Pair with `blueskyAppPassword`."
                    },
                    "blueskyAppPassword": {
                        "title": "Bluesky app password (optional auth)",
                        "type": "string",
                        "description": "**Use an APP PASSWORD — NOT your main Bluesky account password.** Create one at https://bsky.app/settings/app-passwords (format: `xxxx-xxxx-xxxx-xxxx`). Your main password is never needed and should never be entered here. App passwords have limited scope and can be revoked individually without affecting your account."
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Optional proxy for outbound requests. Bluesky's public API does not require proxies — leave empty for a direct connection. Only set this if your environment restricts outbound traffic."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
