API reference
The public endpoints are read-only, without authentication, and rate limited per IP. The `/me/*` endpoints are for the `bsbot` command-line tool: they need a token (`Authorization: Bearer`), which `bsbot login` gets through the browser (`POST /cli/token`). They send no CORS headers. Served only on the main site, never on a bot's handle subdomain. Versioning: the major version is in the path (`/api/v1`); v1 only gets backward-compatible changes (new endpoints, new optional fields or parameters). A breaking change ships as `/api/v2`, and v1 then keeps working for at least 6 months, announced with `Deprecation` and `Sunset` headers. See docs/api.md.
- Base URL
- https://bluesky.bot/api/v1
- Version
- 1.0.0
Is a handle free to claim?
GET/handles/{name}/availability
Applies the exact server-side rules of a claim: format, reserved names, offensive words, then whether a bot already holds it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| name (required) | path | string | The name part, e.g. `weather`; `@weather.bluesky.bot` is accepted too. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The availability, with the reason when not available. | Availability |
| 404 | Not found. Also returned on a bot's handle subdomain: the API is only served on the main site. | Error |
| 429 | Rate limited (policy `publicApi`, per IP). | Error |
| 500 | Unexpected error. | Error |
Example
curl https://bluesky.bot/api/v1/handles/weather/availabilityThe DID behind a claimed handle
GET/resolve/{handle}
Same answer as `https://<handle>/.well-known/atproto-did`. Only `*.bluesky.bot` handles.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| handle (required) | path | string | A full handle, e.g. `weather.bluesky.bot` (case-insensitive). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The handle and its DID. | Resolution |
| 400 | Not a `*.bluesky.bot` handle (code `invalid_handle`). | Error |
| 404 | Not found. Also returned on a bot's handle subdomain: the API is only served on the main site. | Error |
| 429 | Rate limited (policy `publicApi`, per IP). | Error |
| 500 | Unexpected error. | Error |
Example
curl https://bluesky.bot/api/v1/resolve/weather.bluesky.botBots listed in the directory
GET/bots
Only bots whose creator listed them in the directory, newest first. Cursor pagination: pass the `cursor` of a page to get the next one; it is null on the last page.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integer (1–100), default 20 | |
| cursor | query | string | Opaque cursor from a previous page. |
| category | query | string | Exact category. |
| verified | query | string: "true", "false" | Only bots whose handle is (true) or is not yet (false) live on Bluesky. |
| q | query | string | Case-insensitive search in the handle and the display name. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | A page of bots. | BotList |
| 400 | Invalid query parameter (code `bad_request`). | Error |
| 404 | Not found. Also returned on a bot's handle subdomain: the API is only served on the main site. | Error |
| 429 | Rate limited (policy `publicApi`, per IP). | Error |
| 500 | Unexpected error. | Error |
Example
curl https://bluesky.bot/api/v1/botsOne bot listed in the directory
GET/bots/{handle}
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| handle (required) | path | string | A full handle, e.g. `weather.bluesky.bot` (case-insensitive). |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The bot. | Bot |
| 404 | Not found. Also returned on a bot's handle subdomain: the API is only served on the main site. | Error |
| 429 | Rate limited (policy `publicApi`, per IP). | Error |
| 500 | Unexpected error. | Error |
Example
curl https://bluesky.bot/api/v1/bots/weather.bluesky.botTrade a CLI sign-in code for a token
POST/cli/token
Step 4 of `bsbot login`: the one-time code that /cli/authorize handed to the CLI's loopback address, with the PKCE verifier and the same redirect URI. A code works once and for two minutes; a failed attempt burns it. The token is valid 30 days. Rate limited per IP (`cliToken`).
Responses
| Status | Description | Body |
|---|---|---|
| 200 | A token for the `/me/*` endpoints. | CliToken |
| 400 | Malformed body (`bad_request`), or a code that is unknown, expired, already used, or does not match the verifier or the redirect URI (`invalid_grant`). | Error |
| 404 | Not found. Also returned on a bot's handle subdomain: the API is only served on the main site. | Error |
| 429 | Rate limited (policy `publicApi`, per IP). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"code":"…","code_verifier":"…","redirect_uri":"…"}' \
https://bluesky.bot/api/v1/cli/tokenThe account behind the token
GET/me
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Your account and the token's expiry. | Me |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/meYour bots
GET/me/bots
Every bot of your account, listed in the directory or not, oldest first. Add a bot from the dashboard: it needs a sign-in on the bot account.
Responses
| Status | Description | Body |
|---|---|---|
| 200 | Your bots. | MyBotList |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/me/botsClaim a handle for one of your bots, and switch to it
POST/me/bots/{did}/claim
The dashboard's own rules: format, reserved names, offensive words, availability (guarded by a unique index), grace period of released handles. Then the bot's handle is switched on Bluesky right away; `switch` says how it went. Changing the handle of a bot that already has one frees the old one.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| did (required) | path | string | The bot's DID, as listed by `GET /me/bots`. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The claimed handle and the switch outcome. | ClaimResult |
| 400 | Malformed body (`bad_request`), or a name that is not allowed (`invalid_handle`, with `reason`). | Error |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 403 | The bot is suspended (`forbidden`). | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 409 | Another bot holds the handle, or it is in its grace period (`conflict`, reason `taken`). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X POST \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"weather"}' \
https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/claimAsk the bot's PDS again to use its claimed handle
POST/me/bots/{did}/switch
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| did (required) | path | string | The bot's DID, as listed by `GET /me/bots`. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The switch outcome. | SwitchResult |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X POST \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/switchCheck whether the claimed handle is live on Bluesky
POST/me/bots/{did}/verify
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| did (required) | path | string | The bot's DID, as listed by `GET /me/bots`. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The check outcome. | SwitchResult |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X POST \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/verifyRelease a bot's handle
POST/me/bots/{did}/release
The bot goes back to its previous Bluesky handle when possible, the handle stops resolving, and it stays blocked for others during its grace period.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| did (required) | path | string | The bot's DID, as listed by `GET /me/bots`. |
Responses
| Status | Description | Body |
|---|---|---|
| 200 | The released handle and the revert outcome. | ReleaseResult |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 403 | The bot is suspended (`forbidden`). | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 409 | The bot has no handle to release (`conflict`, reason `no_handle`). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X POST \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/releaseRevoke the token of this request
DELETE/me/token
What `bsbot logout` calls.
Responses
| Status | Description | Body |
|---|---|---|
| 204 | Revoked. No body. | |
| 401 | Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again. | Error |
| 404 | No such bot in your account, or a bot's handle subdomain (the API is only served on the main site). | Error |
| 429 | Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard). | Error |
| 500 | Unexpected error. | Error |
Example
curl \
-X DELETE \
-H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
https://bluesky.bot/api/v1/me/tokenSchemas
Error
| Field | Type | Description |
|---|---|---|
| error (required) | object | |
| error.code (required) | string: "bad_request", "invalid_handle", "not_found", "rate_limited", "internal_error", "unauthorized", "forbidden", "conflict", "invalid_grant" | |
| error.message (required) | string | |
| error.reason | string | With `invalid_handle`, `conflict` and `forbidden`: e.g. `reserved`, `taken`, `no_handle`, `suspended`. |
| error.parameter | string | The invalid query parameter. |
| error.retryAfterSeconds | integer (1–) | With `rate_limited`. |
Availability
| Field | Type | Description |
|---|---|---|
| name (required) | string | The normalised name (as typed when invalid). |
| handle (required) | string | null | The full handle, e.g. `weather.bluesky.bot`; null when the name is invalid. |
| available (required) | boolean | |
| reason (required) | null | string: "required", "too_short", "too_long", "invalid_characters", "invalid_hyphen", "reserved", "not_allowed", "taken" | Why it is not available; null when it is. Names are 3–30 characters of a-z, 0-9 and single hyphens. |
Resolution
| Field | Type | Description |
|---|---|---|
| handle (required) | string | |
| did (required) | string |
Bot
| Field | Type | Description |
|---|---|---|
| did (required) | string | |
| handle (required) | string | |
| displayName (required) | string | null | |
| avatar (required) | string | null | Avatar URL. |
| description (required) | string | null | |
| category (required) | string | null | |
| verifiedAt (required) | string | null, date-time | When the handle was confirmed live on Bluesky; null if not yet. |
BotList
| Field | Type | Description |
|---|---|---|
| bots (required) | Bot[] | |
| cursor (required) | string | null | Cursor of the next page; null on the last page. |
CliTokenRequest
| Field | Type | Description |
|---|---|---|
| code (required) | string | |
| code_verifier (required) | string | The PKCE verifier (RFC 7636). |
| redirect_uri (required) | string | The same `http://127.0.0.1:<port>/callback` as on /cli/authorize. |
CliToken
| Field | Type | Description |
|---|---|---|
| token (required) | string | Keep it secret: it acts as you on `/me/*`. |
| tokenType (required) | string = "Bearer" | |
| expiresAt (required) | string, date-time | |
| creator (required) | Account |
Account
| Field | Type | Description |
|---|---|---|
| did (required) | string | |
| handle (required) | string | Your Bluesky handle. |
| displayName | string | null |
Me
| Field | Type | Description |
|---|---|---|
| creator (required) | Account | |
| token (required) | object | |
| token.name (required) | string | The machine name given at login. |
| token.createdAt (required) | string, date-time | |
| token.expiresAt (required) | string, date-time |
MyBot
| Field | Type | Description |
|---|---|---|
| did (required) | string | |
| handle (required) | string | null | The claimed `*.bluesky.bot` handle; null when none. |
| previousHandle (required) | string | null | The bot's handle before it got one here. |
| displayName (required) | string | null | |
| avatar (required) | string | null | |
| verifiedAt (required) | string | null, date-time | When the claimed handle was confirmed live on Bluesky. |
| listed (required) | boolean | Shown in the public directory. |
| status (required) | string: "active", "suspended" |
MyBotList
| Field | Type | Description |
|---|---|---|
| bots (required) | MyBot[] |
ClaimRequest
| Field | Type | Description |
|---|---|---|
| name (required) | string | The name part, e.g. `weather` for `weather.bluesky.bot`. |
SwitchOutcome
| Field | Type | Description |
|---|---|---|
| status (required) | string: "verified", "pending", "manual" | |
| verifiedAt | string, date-time | |
| reason | string: "reauthorize", "pds_refused", "pds_unreachable" |
ClaimResult
| Field | Type | Description |
|---|---|---|
| handle (required) | string | |
| switch (required) | SwitchOutcome |
SwitchResult
| Field | Type | Description |
|---|---|---|
| switch (required) | SwitchOutcome |
ReleaseResult
| Field | Type | Description |
|---|---|---|
| handle (required) | string | The released handle. |
| revert (required) | object | `reverted`: the bot is back on `handle`. `manual`: change the bot's handle yourself on Bluesky; `reason` says why. |
| revert.status (required) | string: "reverted", "manual" | |
| revert.handle | string | |
| revert.reason | string: "reauthorize", "pds_refused", "pds_unreachable", "no_previous_handle" | |
| revert.previousHandle | string | null |