# 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:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
