Api Drafts And Scheduling

Drafts and scheduling

These routes change a real Bluesky account, so they all need a read and write key. Scheduling additionally needs the scheduling feature on your plan — check features in GET /me/.

One JSON shape covers both a single post and a thread:

{"posts": [{"content": "first"}, {"content": "second"}], "tags": ["launch"]}

One entry is a single post. Several make a thread, wired in order, with the first as the root.

Image upload is not in this version.

Drafts

Route Method Scope
/drafts/ GET read
/drafts/ POST write
/drafts/{id}/ GET read
/drafts/{id}/ PATCH, DELETE write
/drafts/{id}/schedule/ POST write
/drafts/{id}/publish/ POST write

POST /drafts/ returns 201 with {"root_id": 88, "ids": [88, 89], "posts": [...]}.

PATCH accepts content and tags. Tags are given as plain names and created if they do not exist.

Another account's draft returns 404, not 403 — the same behaviour as the rest of the app.

Publishing

POST /drafts/{id}/publish/ posts to Bluesky immediately and deletes the draft. It is the only irreversible route here, so it requires:

{"confirm": true}

That is a deliberate speed bump. It makes accidental publication a two-step decision for an agent rather than a one-step one, and it gives you an obvious place to put a human in the loop.

If the account has no Bluesky credentials stored, you get 409 bluesky_not_connected rather than a server error — sign in on the web app first.

Scheduled posts

Route Method Scope
/scheduled/ GET read
/scheduled/ POST write
/scheduled/{id}/ GET read
/scheduled/{id}/ PATCH, DELETE write
{"posts": [{"content": "..."}], "scheduled_at": "2026-09-01T14:30:00Z"}

GET /scheduled/ accepts ?status=pending|posted|failed.

Getting the time right

This is the easiest thing to get quietly wrong.

  • With an offset or a trailing Z, the time means exactly what it says.
  • Without one, it is read in the account's own timezone — the one GET /me/ reports. A naive 14:00 for a Lisbon account is 14:00 in Lisbon, not UTC.
  • The time must be in the future, or you get 409 scheduled_in_past.

Send an explicit offset whenever you can. It removes the ambiguity entirely.

Threads

Every post in a thread shares one scheduled time. Changing scheduled_at on any member moves the whole thread, and deleting the root deletes all of it.