Bluesky setup
Bluesky is the first-platform path in these docs: one account, one app password, and text-only posting through the official @atproto/api SDK. This page covers the credentials, the Worker secrets they become, one single-platform request, and how to read the result.
bluesky-native
Status and evidence
Syndroo 0.2.0-rc.1 is an unpublished release candidate: prepared in the repository, not published to npm, not tagged, and not deployed. Bluesky is recorded as mock-tested for this candidate.
Evidence: unit tests plus the local Mock SNS end-to-end gate in the core repository. That gate replaces only the network boundary with a loopback mock, so it proves wiring, persistence, idempotency, ambiguity handling and scheduling. It cannot prove provider permissions, current API compatibility or rate limits. No live-account acceptance record exists yet, and live acceptance is an open release gate. See Platform readiness and the capability record.
What you need
- A Bluesky account you can post from. Use an account you are willing to publish real text with, because the commands on this page publish for real once the credentials are configured.
- A Bluesky app password. App passwords are created separately from the account password and can be revoked on their own. Syndroo never asks for the account password.
- A running Worker and its API key. Either a deployment in your own Cloudflare account or a local instance. The Worker holds the platform credentials; clients hold only
SYNDROO_API_KEY. If you have not set one up yet, the first-post walkthrough covers the whole path with Bluesky. - Your handle, such as
alice.bsky.social, and optionally the PDS hostname, which defaults tobsky.social.
The adapter reads three Worker secrets:
| Secret | Required | Value |
|---|---|---|
BLUESKY_IDENTIFIER | Yes | Your Bluesky handle. |
BLUESKY_PASSWORD | Yes | An app password, not the account password. |
BLUESKY_HOST | No | PDS hostname. Defaults to bsky.social; set it only when your account lives on another PDS. |
Create the app password by hand
App passwords are issued inside the account settings of the Bluesky client. The exact menu labels belong to Bluesky and can change, so treat the official documentation linked under Official sources as the authority.
-
Sign in to Bluesky
Use the account you intend to publish from. Nothing on this page needs the account password.
-
Create an app password
Open the app-password section of account settings and create one entry for this Worker, for example named
syndroo-worker. A named entry is easier to revoke later without disturbing anything else. -
Copy the value once
Copy the generated password as soon as it is shown and store it in the secret place your Worker reads. Treat it as a secret from that moment on; do not paste it into a page, a chat message or an issue.
An app password can be revoked on its own, and using one keeps your account password out of a deployment's secret store.
Add the secrets to the Worker
Put the values where your runtime reads secrets: Worker secrets for a deployment, .dev.vars for a local instance. Cloudflare deployment and Local development cover the exact commands for each path. The names are the same in both places.
BLUESKY_IDENTIFIER="your-handle.bsky.social"
BLUESKY_PASSWORD="your-bluesky-app-password"
BLUESKY_HOST="bsky.social"
Before accepting a post, Syndroo checks that the required values exist and are non-empty. It does not call Bluesky to test them. A revoked or mistyped app password passes that check and surfaces later as a publication failure with errorCode AUTH.
Only the platforms you configure are selectable. Sending platforms: ["bluesky"] without these secrets returns HTTP 422 with PLATFORM_NOT_CONFIGURED before anything is stored or queued.
Publish and check one post
The commands below publish for real once the secrets above are configured. Set the two client variables in the terminal that will call Syndroo.
export SYNDROO_URL="https://your-worker.your-subdomain.workers.dev"
export SYNDROO_API_KEY="the-same-secret-your-worker-has"
curl -X POST "$SYNDROO_URL/v1/posts" \
-H "Authorization: Bearer $SYNDROO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bluesky-setup-001" \
--data '{
"content": "First post from my own Syndroo Worker.",
"platforms": ["bluesky"]
}'
The accepted response is HTTP 202 with status queued:
{
"id": "post_...",
"status": "queued"
}
HTTP 202 means the request was accepted for processing. It does not confirm that Bluesky accepted anything, and it does not prove that the Queue has not already sent the platform request, because delivery can start immediately after acceptance. Read the stored state to learn the outcome:
curl \
-H "Authorization: Bearer $SYNDROO_API_KEY" \
"$SYNDROO_URL/v1/posts/post_..."
{
"id": "post_...",
"content": "First post from my own Syndroo Worker.",
"platforms": ["bluesky"],
"status": "published",
"publications": [
{
"platform": "bluesky",
"provider": "bluesky-native",
"status": "published",
"attempts": 1,
"externalId": "at://did:plc:.../app.bsky.feed.post/...",
"externalUrl": "https://bsky.app/profile/.../post/...",
"errorAmbiguous": false
}
]
}
queued and publishing are not terminal; poll the same URL. published and failed are terminal. The aggregate post status is published only when every selected platform succeeded, so with one platform selected it mirrors that publication.
Repeating the identical request with the same Idempotency-Key returns the stored result with HTTP 200 instead of creating a second post. See Idempotency-Key.
Limits, tokens and external dependencies
| Item | Recorded for 0.2.0-rc.1 |
|---|---|
| Text limit | The candidate enforces 300 Unicode code points and 3,000 UTF-8 bytes before the network call, counting the text by code point. The official lexicon allows 300 graphemes and 3,000 bytes, so this guard can be stricter than the provider for text built from combining marks or multi-code-point emoji; they are two different rules, not one shared limit. HTTP(S) URLs in the text receive link facets. |
| Content type | Text only. Media, threads and rich embeds are outside this version. |
| Credential maintenance | Yours. Revoke or replace the app password in Bluesky, then update the Worker secret. Syndroo has no OAuth flow and does not renew credentials. |
| External dependency | Bluesky's own service, terms and limits apply. Syndroo controls none of them, includes no pricing of its own, and makes no promise about a provider's current plan, quota or limits. |
Check the provider's current documentation before relying on a specific limit, scope or plan. Those facts change on the provider's schedule, not on this page; the links under Official sources are the references to re-check.
When something fails
A failed publication stores errorCode, errorMessage and errorAmbiguous in the post response. Read them before you act.
| What you see | What it means | What to do |
|---|---|---|
HTTP 422 PLATFORM_NOT_CONFIGURED | The required Bluesky secrets are missing or empty. | Add the secrets named above, then send the request again. Nothing was stored or queued. |
AUTH | Bluesky rejected the credentials: revoked, mistyped, or the app password no longer exists. | Create a new app password and update the Worker secret. Check the account before resending, because an earlier attempt may have succeeded before the credential broke. |
INVALID_CONTENT | The text exceeded the adapter limit and nothing was sent. | Shorten the text and publish again with a new key. |
RATE_LIMIT | Bluesky asked the caller to slow down. | Syndroo retries this automatically with backoff, up to three attempts. Let it finish, then poll the same post. |
PROVIDER_UNAVAILABLE, NETWORK or UNKNOWN, with errorAmbiguous: true | The write may have reached Bluesky; the outcome is unknown. | Check the account by hand. Syndroo never resends an ambiguous publication automatically. |
Post status partial | Some selected platforms succeeded and some failed. | Never resubmit the platforms that succeeded. The failed ones need attention only after you have excluded an ambiguous outcome. |
If a publication is failed with errorAmbiguous: true, check the platform account by hand first, because the post may already exist. Only when you have confirmed that nothing was published and you still want the post should you send a new request with a new Idempotency-Key. Reusing the old key replays the stored result instead of publishing. Read Ambiguous outcomes for the reasoning.
Official sources
- Bluesky API documentation
- Official
@atproto/apiSDK - Official lexicon
app.bsky.feed.post, which setsmaxGraphemes: 300andmaxLength: 3000 - Syndroo adapter source:
@syndroo/bluesky - Capability record, taken from the product repository at commit
87ba42band reviewed on 2026-09-18
This page follows the candidate's capability record and the product revision above. It was not verified against a live Bluesky account, so re-check the official documentation when a step, limit or permission differs from what you see.