Skip to content
Reading with an AI assistant?View as MarkdownOpen in ClaudeOpen in ChatGPT

Guides

Publish to Bluesky with the API

The bluesky overrides cover an external-link preview card and the platform's four self-label content flags (sexual, nudity, porn, graphic media). There is no visibility field, because Bluesky posts are public.

Browse docs

6 min read

What do you need before you publish?#

You need one connected account for the platform. Connect it in the SkedCast console. If a client owns the account, create a connect link with POST /v1/clients/{id}/connect-invites and send it to them. They authorize from that page without a SkedCast login.

GET /v1/accounts lists the connected accounts with their ids. Pass that id in targets.accountIds below.

How do you schedule a Bluesky post with the API?#

bash
curl -X POST https://api.skedcast.com/v1/posts \
  -H "Authorization: Bearer sked_live_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "<clientId>",
    "content": "Launch day is here 🚀",
    "mediaIds": ["<mediaAssetId>"],
    "targets": { "mode": "selection", "accountIds": ["<blueskyAccountId>"] },
    "schedule": { "type": "at", "scheduledAt": "2030-01-15T15:00:00Z" }
  }'
A minimal Bluesky post, scheduled for a specific time.

Which Bluesky fields can you set?#

Put everything platform-specific in one variants entry with "platform": "bluesky" and an overrides object. Send only bluesky keys on a bluesky variant. SkedCast reads the keys in the table below and ignores the rest.

FieldTypeValues / defaultNotes
linkstring (url)—External link for an embed preview card.
labelSexualbooleanfalseSelf-labels the post as sexually suggestive.
labelNuditybooleanfalseSelf-labels the post as containing non-sexual nudity.
labelPornbooleanfalseSelf-labels the post as adult or pornographic content.
labelGraphicMediabooleanfalseSelf-labels the post as violent or otherwise graphic media.
firstCommentstring—Posted as a reply in the same thread, with links resolved like any other post. Best-effort: a failed comment never fails the post. The outcome is reported as firstCommentIssue on GET /v1/post-targets/{id}.
quotePostIdstring—Quote another post: its bsky.app URL or at:// URI. It takes the place of a link card; images can still be attached.
replyGateenumeveryone (default) \| followers \| following \| mentioned \| nobodyWho can reply.
allowQuotesbooleantruefalse turns quote posts off.

How do you add Bluesky-specific settings?#

json
{
  "clientId": "<clientId>",
  "content": "New post on the blog — link below.",
  "mediaIds": [],
  "targets": { "mode": "selection", "accountIds": ["<blueskyAccountId>"] },
  "variants": [
    { "platform": "bluesky", "overrides": { "link": "https://example.com/blog/launch" } }
  ],
  "schedule": { "type": "at", "scheduledAt": "2030-01-15T15:00:00Z" }
}
The same POST /posts body, with a variants entry added.

How do you check that the post published?#

Publishing creates one post target per account. GET /v1/post-targets/{id} (or GET /v1/posts/{id}) reports status, plus externalId (Bluesky's own post id) and permalink (its public URL) once published. To avoid polling, subscribe to the target.published and target.failed webhooks (see the webhooks guide).

blueskyatprotoapipublish

FAQ

Can I post a thread on Bluesky?
Yes. Use the post-level thread field ({ segments: [...] }), the same as on X and Threads: segment 0 is the head post and each later segment replies to the one before it.

Ready to broadcast everywhere?

Sign up free — no credit card. You land on the Free plan, and you can start a one-time 7-day Studio trial from your workspace whenever you are ready. Connect your first accounts, import a batch, and watch one post fan out across every platform.