Bluesky.bot

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

Download the OpenAPI 3.1 document

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

NameInTypeDescription
name (required)pathstringThe name part, e.g. `weather`; `@weather.bluesky.bot` is accepted too.

Responses

StatusDescriptionBody
200The availability, with the reason when not available.Availability
404Not found. Also returned on a bot's handle subdomain: the API is only served on the main site.Error
429Rate limited (policy `publicApi`, per IP).Error
500Unexpected error.Error

Example

bash
curl https://bluesky.bot/api/v1/handles/weather/availability

The DID behind a claimed handle

GET/resolve/{handle}

Same answer as `https://<handle>/.well-known/atproto-did`. Only `*.bluesky.bot` handles.

Parameters

NameInTypeDescription
handle (required)pathstringA full handle, e.g. `weather.bluesky.bot` (case-insensitive).

Responses

StatusDescriptionBody
200The handle and its DID.Resolution
400Not a `*.bluesky.bot` handle (code `invalid_handle`).Error
404Not found. Also returned on a bot's handle subdomain: the API is only served on the main site.Error
429Rate limited (policy `publicApi`, per IP).Error
500Unexpected error.Error

Example

bash
curl https://bluesky.bot/api/v1/resolve/weather.bluesky.bot

Bots 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

NameInTypeDescription
limitqueryinteger (1–100), default 20
cursorquerystringOpaque cursor from a previous page.
categoryquerystringExact category.
verifiedquerystring: "true", "false"Only bots whose handle is (true) or is not yet (false) live on Bluesky.
qquerystringCase-insensitive search in the handle and the display name.

Responses

StatusDescriptionBody
200A page of bots.BotList
400Invalid query parameter (code `bad_request`).Error
404Not found. Also returned on a bot's handle subdomain: the API is only served on the main site.Error
429Rate limited (policy `publicApi`, per IP).Error
500Unexpected error.Error

Example

bash
curl https://bluesky.bot/api/v1/bots

One bot listed in the directory

GET/bots/{handle}

Parameters

NameInTypeDescription
handle (required)pathstringA full handle, e.g. `weather.bluesky.bot` (case-insensitive).

Responses

StatusDescriptionBody
200The bot.Bot
404Not found. Also returned on a bot's handle subdomain: the API is only served on the main site.Error
429Rate limited (policy `publicApi`, per IP).Error
500Unexpected error.Error

Example

bash
curl https://bluesky.bot/api/v1/bots/weather.bluesky.bot

Trade 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

StatusDescriptionBody
200A token for the `/me/*` endpoints.CliToken
400Malformed 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
404Not found. Also returned on a bot's handle subdomain: the API is only served on the main site.Error
429Rate limited (policy `publicApi`, per IP).Error
500Unexpected error.Error

Example

bash
curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"code":"…","code_verifier":"…","redirect_uri":"…"}' \
  https://bluesky.bot/api/v1/cli/token

The account behind the token

GET/me

Responses

StatusDescriptionBody
200Your account and the token's expiry.Me
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me

Your 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

StatusDescriptionBody
200Your bots.MyBotList
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me/bots

Claim 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

NameInTypeDescription
did (required)pathstringThe bot's DID, as listed by `GET /me/bots`.

Responses

StatusDescriptionBody
200The claimed handle and the switch outcome.ClaimResult
400Malformed body (`bad_request`), or a name that is not allowed (`invalid_handle`, with `reason`).Error
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
403The bot is suspended (`forbidden`).Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
409Another bot holds the handle, or it is in its grace period (`conflict`, reason `taken`).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
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/claim

Ask the bot's PDS again to use its claimed handle

POST/me/bots/{did}/switch

Parameters

NameInTypeDescription
did (required)pathstringThe bot's DID, as listed by `GET /me/bots`.

Responses

StatusDescriptionBody
200The switch outcome.SwitchResult
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -X POST \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/switch

Check whether the claimed handle is live on Bluesky

POST/me/bots/{did}/verify

Parameters

NameInTypeDescription
did (required)pathstringThe bot's DID, as listed by `GET /me/bots`.

Responses

StatusDescriptionBody
200The check outcome.SwitchResult
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -X POST \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/verify

Release 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

NameInTypeDescription
did (required)pathstringThe bot's DID, as listed by `GET /me/bots`.

Responses

StatusDescriptionBody
200The released handle and the revert outcome.ReleaseResult
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
403The bot is suspended (`forbidden`).Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
409The bot has no handle to release (`conflict`, reason `no_handle`).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -X POST \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me/bots/did:plc:yourbot/release

Revoke the token of this request

DELETE/me/token

What `bsbot logout` calls.

Responses

StatusDescriptionBody
204Revoked. No body.
401Missing, expired or revoked token (code `unauthorized`). Run `bsbot login` again.Error
404No such bot in your account, or a bot's handle subdomain (the API is only served on the main site).Error
429Rate limited: `publicApi` per IP, `creatorApi` per account, and `handleChange` per account for claims, switches, checks and releases (shared with the dashboard).Error
500Unexpected error.Error

Example

bash
curl \
  -X DELETE \
  -H "Authorization: Bearer $BLUESKY_BOT_TOKEN" \
  https://bluesky.bot/api/v1/me/token

Schemas

Error

FieldTypeDescription
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.reasonstringWith `invalid_handle`, `conflict` and `forbidden`: e.g. `reserved`, `taken`, `no_handle`, `suspended`.
error.parameterstringThe invalid query parameter.
error.retryAfterSecondsinteger (1–)With `rate_limited`.

Availability

FieldTypeDescription
name (required)stringThe normalised name (as typed when invalid).
handle (required)string | nullThe 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

FieldTypeDescription
handle (required)string
did (required)string

Bot

FieldTypeDescription
did (required)string
handle (required)string
displayName (required)string | null
avatar (required)string | nullAvatar URL.
description (required)string | null
category (required)string | null
verifiedAt (required)string | null, date-timeWhen the handle was confirmed live on Bluesky; null if not yet.

BotList

FieldTypeDescription
bots (required)Bot[]
cursor (required)string | nullCursor of the next page; null on the last page.

CliTokenRequest

FieldTypeDescription
code (required)string
code_verifier (required)stringThe PKCE verifier (RFC 7636).
redirect_uri (required)stringThe same `http://127.0.0.1:<port>/callback` as on /cli/authorize.

CliToken

FieldTypeDescription
token (required)stringKeep it secret: it acts as you on `/me/*`.
tokenType (required)string = "Bearer"
expiresAt (required)string, date-time
creator (required)Account

Account

FieldTypeDescription
did (required)string
handle (required)stringYour Bluesky handle.
displayNamestring | null

Me

FieldTypeDescription
creator (required)Account
token (required)object
token.name (required)stringThe machine name given at login.
token.createdAt (required)string, date-time
token.expiresAt (required)string, date-time

MyBot

FieldTypeDescription
did (required)string
handle (required)string | nullThe claimed `*.bluesky.bot` handle; null when none.
previousHandle (required)string | nullThe bot's handle before it got one here.
displayName (required)string | null
avatar (required)string | null
verifiedAt (required)string | null, date-timeWhen the claimed handle was confirmed live on Bluesky.
listed (required)booleanShown in the public directory.
status (required)string: "active", "suspended"

MyBotList

FieldTypeDescription
bots (required)MyBot[]

ClaimRequest

FieldTypeDescription
name (required)stringThe name part, e.g. `weather` for `weather.bluesky.bot`.

SwitchOutcome

FieldTypeDescription
status (required)string: "verified", "pending", "manual"
verifiedAtstring, date-time
reasonstring: "reauthorize", "pds_refused", "pds_unreachable"

ClaimResult

FieldTypeDescription
handle (required)string
switch (required)SwitchOutcome

SwitchResult

FieldTypeDescription
switch (required)SwitchOutcome

ReleaseResult

FieldTypeDescription
handle (required)stringThe 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.handlestring
revert.reasonstring: "reauthorize", "pds_refused", "pds_unreachable", "no_previous_handle"
revert.previousHandlestring | null