# Connect Bluesky

Connect or reconnect a Bluesky account with its handle and an app password.


```http
POST https://social-publish-api.onrender.com/v1/accounts/bluesky
```

Signs in to Bluesky with the handle and app password, and saves the account to your project. Bluesky is the only platform you can connect with a single API call; OAuth platforms connect from **[Integrations](https://social-publish-kappa.vercel.app/dashboard/integrations)** in the dashboard, or through a [connect link](/docs/api/create-connect-link) your user opens.

Create an app password in the Bluesky app under **Settings → Privacy and security → App passwords**. Don't use your main Bluesky password.

Calling this again for the same Bluesky account (same DID) updates its credentials in place and sets it back to `active`; the account `id` stays the same. Use this to fix an account in `reconnect_required`.

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `x-access-key` | Yes* | Your API key. *Or send `Authorization: Bearer <key>`. |
| `content-type` | Yes | `application/json` |

## Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | string | Yes | Bluesky handle (for example `acme.bsky.social`) or account email. At least 3 characters. |
| `appPassword` | string | Yes | Bluesky app password. At least 8 characters. |

## Request

```bash tab="cURL"
curl -X POST https://social-publish-api.onrender.com/v1/accounts/bluesky \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "identifier": "acme.bsky.social", "appPassword": "abcd-efgh-ijkl-mnop" }'
```

```ts tab="Node.js"
const res = await fetch("https://social-publish-api.onrender.com/v1/accounts/bluesky", {
  method: "POST",
  headers: {
    "x-access-key": process.env.RELAYCAST_API_KEY!,
    "content-type": "application/json",
  },
  body: JSON.stringify({
    identifier: "acme.bsky.social",
    appPassword: process.env.BLUESKY_APP_PASSWORD!,
  }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { data: account } = await res.json();
```

```python tab="Python"
import os, requests

res = requests.post(
    "https://social-publish-api.onrender.com/v1/accounts/bluesky",
    headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
    json={"identifier": "acme.bsky.social", "appPassword": os.environ["BLUESKY_APP_PASSWORD"]},
)
res.raise_for_status()
account = res.json()["data"]
```

## Response

Returns the connected account, with the same fields as [List accounts](/docs/api/list-accounts).

```json title="Response 201"
{
  "data": {
    "id": "7c6b5a49-3827-4e1d-8c0b-9a8f7e6d5c4b",
    "platform": "bluesky",
    "handle": "acme.bsky.social",
    "displayName": null,
    "avatarUrl": null,
    "status": "active",
    "groupId": null,
    "oauthAppId": null,
    "createdAt": "2026-10-05T09:14:03.218Z"
  }
}
```

The status is `201` for reconnects too.

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `invalid_json` | The body isn't valid JSON. |
| 400 | `validation_error` | `identifier` or `appPassword` is missing or too short. `details` lists the issues. |
| 400 | `invalid_credentials` | Bluesky rejected the handle or app password. |
| 401 | `unauthorized` | Missing or invalid API key. |
| 429 | `rate_limited` | Rate limit exceeded for this key. |

```json title="Response 400"
{
  "error": {
    "code": "invalid_credentials",
    "message": "Bluesky rejected these credentials"
  }
}
```

> [!NOTE]
> Accounts are signed in through `bsky.social`. See the [Bluesky guide](/docs/platforms/bluesky) for what you can post.
