# BlueSky Auto Follow (`advantageous_subcontra/bluesky-auto-follow`) Actor

- **URL**: https://apify.com/advantageous_subcontra/bluesky-auto-follow.md
- **Developed by:** [Fabian Maume](https://apify.com/advantageous_subcontra) (community)
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 follows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

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

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

## How to integrate an Actor?

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

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

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

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

# README

### What does Bluesky Auto-Follow do?

Bluesky Auto-Follow grows your [Bluesky](https://bsky.app) followers automatically. You just need to schedule the Actor for daily runs. It signs in with your own **App Password**, **follows** a few accounts each day, detects **follow-backs**, and **unfollows** accounts that didn't reciprocate after a delay you set. It talks to Bluesky through the official **AT Protocol API** (`@atproto/api` SDK), and is compliant with Bluesky's terms and conditions.

### Why use Bluesky Auto-Follow?

- Grow a focused following around a topic by targeting handles from a Google Sheet, or the follower bases of accounts you choose.
- Keep your follow/following ratio healthy automatically: reciprocators stay, non-reciprocators are unfollowed after N days.
- Runs on the official Bluesky API, so it's **fast, cheap, and stable**.
- Uses an **App Password** (not your main password); the App Password cannot change your account settings or delete your account.
- Resumes from the last run's state via the key-value store. So you can easily schedule the Actor for daily runs.

[How to schedule Actor](https://www.youtube.com/watch?v=1jI7WcVQmwM\&pp=ygUeZ2V0dGluZyBzdGFydGVkIHdpdGggYXBpZnkgMy80)

If you want to automate multiple Bluesky accounts, set up an **Account label** under the Multi-account section. The Actor will append the label to the key-value store used to store the automation state between each run.

### How to automate Bluesky follows

1. **Generate a Bluesky App Password:** [bsky.app → Settings → Privacy and security → App passwords](https://bsky.app/settings/app-passwords) → Add App Password → copy the `xxxx-xxxx-xxxx-xxxx` string. Keep "Allow access to your direct messages" OFF; this Actor doesn't need it.
2. **Prepare a target list**: either make a public Google Sheet with a column named `blueskyHandle` (set sharing to "Anyone with the link"), or list a few Bluesky handles whose followers you want to pull into your backlog.
3. **Configure the Actor**: paste your handle (e.g. `alice.bsky.social`) and the App Password, set `maxFollowsPerDay` (start with 10), fill in your Google Sheet URL and/or follower-source handles.
4. **Run once manually** to confirm login works, then **create a daily schedule** in the Schedules tab. One run per day is the sweet spot.

### What activities does Bluesky Auto-Follow log?

Each run writes a short dataset so you can audit everything it did:

| Field | Type | Description |
| --- | --- | --- |
| `action` | enum | `summary`, `target_added`, `follow`, `follow_back`, or `unfollow` |
| `username` | string | Bluesky handle (e.g. `alice.bsky.social`) involved in the row |
| `did` | string | Stable AT Protocol DID of the account |
| `timestamp` | ISO date | When the event was recorded |
| `sourceProfile` | string | For `target_added`: `google-sheet` or the handle whose followers were scraped |
| `followedAt` | ISO date | For `follow_back` / `unfollow`: when the original follow happened |
| `followsPerformed` | integer | Summary row — follows this run |
| `unfollowsPerformed` | integer | Summary row — unfollows this run |
| `targetsAdded` | integer | Summary row — handles added to backlog |
| `backlogSize` | integer | Summary row — remaining backlog at end of run |
| `followBacksDetected` | integer | Summary row — new follow-backs this run |
| `rateLimited` | boolean | Summary row — `true` if the AT Protocol server returned HTTP 429 during the follow phase, in which case the unfollow phase was skipped and state was still saved for the next run |
| `error` | string | For `follow` / `unfollow` / `follow_back` rows — a short reason when the action was attempted but failed (e.g. `not_found`, `rate_limited`, `already_following (preexisting; excluded from unfollow)`). `null` on successful rows |

Cross-run state (backlog, everyone followed with their follow-record AT-URI, everyone who followed back, everyone unfollowed, and which profiles have already been scraped for followers) lives under the `STATE` key in a **named key-value store called `bluesky-auto-follow-state`**. The cached AT Protocol session lives under `BSKY_SESSION` in the same named store, so the App Password is only used when the refresh token also expires. The named store survives across scheduled runs; the Actor's per-run *default* key-value store does not, which is why state is kept in a named one.

### How much will it cost?

Pricing is pay-per-event. The Actor charges three events:

- `target-list-expanded`: once per new handle added to the backlog (cost $0.01 per 1k)
- `follow` : once per successful follow
- `unfollow` : once per successful unfollow

Follow and unfollow have the same price:

- $0.9 per 1k actions on the free plan
- $0.8 per 1k actions on the starter plan
- $0.7 per 1k actions on the Scale plan
- $0.6 per 1k actions on the business plan

Expanding the target list

### Input

See the **Input** tab for the full form. The important ones:

- **`handle`**: your Bluesky handle, e.g. `alice.bsky.social` (no leading `@`).
- **`appPassword`**: the App Password you generated, stored encrypted.
- **`maxFollowsPerDay`**: upper bound of follows per run (Max 200). Run the Actor once per day on a schedule.
- **`followVariation`**: on by default; each run picks a random count between 50% and 100% of `maxFollowsPerDay`.
- **`unfollowDelayDays`**: minimum age of a follow before it becomes a candidate for unfollow.
- **`targetListGoogleSheet`** vs. **`targetAccountsForFollowerExtraction`**: provide one or both. The sheet is tried first; follower-scraping is used as a fallback.
- **`service`**: PDS base URL, defaults to `https://bsky.social`. Only change this if your account lives on a self-hosted PDS.

### Output

You can download the dataset in JSON, HTML, CSV, or Excel. Example rows:

```json
[
  {
    "action": "target_added",
    "username": "exampleuser.bsky.social",
    "sourceProfile": "google-sheet",
    "timestamp": "2026-01-15T09:00:00.000Z"
  },
  {
    "action": "follow",
    "username": "exampleuser.bsky.social",
    "did": "did:plc:z72i7hdynmk6r22z27h6tvur",
    "timestamp": "2026-01-15T09:04:12.350Z"
  },
  {
    "action": "follow_back",
    "username": "someotheruser.bsky.social",
    "did": "did:plc:zz2zz2zz2zz2zz2zz2zz2zz2",
    "followedAt": "2026-01-08T09:12:45.100Z",
    "timestamp": "2026-01-15T09:05:30.900Z"
  },
  {
    "action": "summary",
    "username": "alice.bsky.social",
    "did": "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa",
    "timestamp": "2026-01-15T09:06:00.000Z",
    "followsPerformed": 7,
    "unfollowsPerformed": 2,
    "targetsAdded": 50,
    "backlogSize": 43,
    "followBacksDetected": 1,
    "rateLimited": false
  }
]
```

### Tips

- **Start small**: 10 follows/day on a fresh account for the first week. The AT Protocol server caps writes at roughly [1,666 points/hour (each follow = 3 points)](docs.bsky.app/docs/advanced-guides/rate-limits), and bulk patterns can draw moderation attention regardless of the server cap.
- **Keep scale reasonable**: The Actor will prevent you from following more than 200 people per day. I recommend staying below 50.
- **If a run reports `rateLimited: true`**, let the Actor rest a day and consider lowering `maxFollowsPerDay`.
- **To reset backlog state**, delete the `STATE` record from the named key-value store `bluesky-auto-follow-state` (Apify Console → Storage → Key-value stores → Named). The cached session (`BSKY_SESSION`) can also be deleted from the same store to force a fresh login.

### FAQ, disclaimers, and support

**Can my account be suspended?** Bluesky's Community Guidelines don't ban automation but do prohibit spam-adjacent behavior. A conservative daily cap with a reciprocation-based unfollow is well within normal use. Running this Actor at hundreds of follows per day on a new account is the kind of pattern moderation may act on. Use it on your own account at a reasonable pace.

**Is this against Bluesky's TOS?** No — the AT Protocol is explicitly designed for third-party clients, and App Passwords exist precisely so you can log a programmatic client in. The `app.bsky.graph.follow` record is a first-class public lexicon.

**What if my App Password stops working?** The Actor's startup login check will fail fast with a clear error. Generate a new App Password in Bluesky Settings and re-run.

**What are follow records?** Every follow on Bluesky is a public, portable record under `at://<yourDid>/app.bsky.graph.follow/<rkey>`. This Actor stores each follow's AT-URI in the key-value store so it can later unfollow precisely. Anyone can inspect your public follow list.

**Can I run two accounts in parallel?** Yes. Give each schedule a distinct accountLabel. The two runs will use separate state stores (bluesky-auto-follow-state-<label>) and never see each other’s follow lists.

**How does it detect follow-backs?** The Actor check you recent followers on each run and map it to the list of people you followed.

Feedback and bugs go to the **Issues** tab on this Actor's Apify Store page. See the **API** tab for programmatic access to runs and outputs.

# Actor input Schema

## `handle` (type: `string`):

The handle of the account the Actor acts on, e.g. `alice.bsky.social` (no leading `@`).

## `appPassword` (type: `string`):

Generate at https://bsky.app/settings/app-passwords. Format `xxxx-xxxx-xxxx-xxxx`. Stored encrypted and never logged.

## `service` (type: `string`):

The Personal Data Server base URL. Override only if your account is on a self-hosted PDS.

## `maxFollowsPerDay` (type: `integer`):

Upper bound on how many accounts the Actor may follow in one run. Designed to be scheduled once per day. Start low (1–5) when testing a new account. The AT Protocol server also caps follows at roughly 555/hour.

## `followVariation` (type: `boolean`):

When on, each run's follow count is a random integer between 50% and 100% of `maxFollowsPerDay`. Makes activity look less mechanical and keeps you further from any burst detector.

## `unfollowDelayDays` (type: `integer`):

Minimum age of a follow before the Actor will unfollow an account that hasn't followed back.

## `targetListGoogleSheet` (type: `string`):

Public Google Sheet URL (shared as 'Anyone with the link'). The Actor reads a column named `blueskyHandle`, `handle`, or `username` (first matching column wins). Tried first when the backlog is empty.

## `targetAccountsForFollowerExtraction` (type: `array`):

Fallback target source. When the Google Sheet is absent or exhausted and the backlog is empty, the Actor pages through the followers of the next unused handle in this list via `app.bsky.graph.getFollowers`. Accepts bare handles (`apify.com`), @-prefixed handles (`@apify.com`), DIDs (`did:plc:...`), or `https://bsky.app/profile/<handle>` URLs — the Actor normalizes them.

## `accountLabel` (type: `string`):

Short identifier for the Bluesky account this run acts on. Leave blank to use the shared default state store `bluesky-auto-follow-state` (backward-compatible with existing schedules). Set to a label like `main` or `work` to pick a per-account state store `bluesky-auto-follow-state-<accountLabel>`, so two schedules on two different Bluesky accounts can safely run side-by-side without stepping on each other's backlog, followed list, or cached session. Lowercase letters, digits, and hyphens only. Keep this stable across runs of the same account — if you change it, the next run starts with an empty state.

## Actor input object example

```json
{
  "handle": "alice.bsky.social",
  "service": "https://bsky.social",
  "maxFollowsPerDay": 10,
  "followVariation": true,
  "unfollowDelayDays": 7,
  "targetListGoogleSheet": "https://docs.google.com/spreadsheets/d/1AbCDeF.../edit?usp=sharing",
  "targetAccountsForFollowerExtraction": [],
  "accountLabel": ""
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one row per follow, unfollow, follow-back and backlog add, plus one summary row per run.

## `state` (type: `string`):

Key-value store with the persistent STATE record and the cached BSKY_SESSION.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("advantageous_subcontra/bluesky-auto-follow").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("advantageous_subcontra/bluesky-auto-follow").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 '{}' |
apify call advantageous_subcontra/bluesky-auto-follow --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,advantageous_subcontra/bluesky-auto-follow"
        }
    }
}
```

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/Vi16ggNfOOO6jLRbW/builds/82PDget1S8ZI9ULlU/openapi.json
