Docs › API reference › Drafts

Drafts API: autosave, then publish or schedule

Save a post as you write it, autosave changes safely, then publish or schedule it in one call.

Updated

A draft is text you have started but not sent. The API calls it an idea, and the paths say /v1/ideas. The OmniPosts composer saves to it as you type. You can use it the same way, or to hand a draft from one tool to another.

A draft has a status: draft (you can still change it), promoted (it was sent as a post), or discarded.

targetDrafts is a map from channel id to that channel's tweaks. Each tweak can have body, mediaIds and settings, the same as a target in Posts.

{
  "id": "0192a3b4-0000-7000-8000-0000000000d1",
  "body": "Draft of the launch post",
  "mediaIds": [],
  "status": "draft",
  "targetDrafts": {
    "0192a3b4-0000-7000-8000-000000000001": { "body": "Shorter version for this channel" }
  },
  "createdAt": "2026-10-14T09:00:00.120331Z",
  "updatedAt": "2026-10-14T09:00:00.120331Z"
}

POST /v1/ideas

Create a draft

POST /v1/ideas takes body, mediaIds and targetDrafts, all optional. The answer is 201 with the draft.

curl https://omniposts.dev/v1/ideas \
  -H "Authorization: Bearer omniposts_..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Draft of the launch post" }'

GET /v1/ideas

List and get drafts

GET /v1/ideas lists drafts, newest first. Filter with status=draft or status=promoted. Discarded drafts are never listed. It is paginated: see Pagination.

GET /v1/ideas/{id} returns one draft. An id from another organization is 404.

PATCH /v1/ideas/{id}

Autosave a draft

PATCH /v1/ideas/{id} saves changes. Send only the fields you want to change: a field you leave out stays as it was. A field you send replaces the old value completely (mediaIds and targetDrafts are replaced as a whole, not merged).

Send If-Match with the draft's updatedAt so two tabs cannot overwrite each other:

The answer is 200 with the draft, including its new updatedAt. Use that value for your next save.

  • Send updatedAt back exactly as received. It has microsecond precision. Keep it as a string; do not round-trip it through a date type that keeps only milliseconds.
  • If the draft changed since, you get 409 stale_write and details.current holds the draft as it is now. Merge your change into it and save again.
  • If the draft is no longer a draft, you get 409 not_editable, with details.current.
  • If-Match is optional. Without it the save always wins.

Autosave has its own, lower rate limit, 30 saves a minute for your organization. See Rate limits. Save when the person pauses, not on every key press.

curl -X PATCH https://omniposts.dev/v1/ideas/0192a3b4-0000-7000-8000-0000000000d1 \
  -H "Authorization: Bearer omniposts_..." \
  -H 'If-Match: "2026-10-14T09:00:00.120331Z"' \
  -H "Content-Type: application/json" \
  -d '{ "body": "Draft of the launch post, second pass" }'

POST /v1/ideas/{id}/promote

Publish a draft

POST /v1/ideas/{id}/promote turns a draft into a post. It behaves exactly like POST /v1/groups: the same 201, 207, 200 and 422 answers, the same rejections, and the same Idempotency-Key header. The text and media come from the draft.

Field Meaning
targets The channels to post to, as in a normal post. A channel with saved tweaks in the draft's targetDrafts uses them, unless you set that field here
scheduledFor, timezone When to send, as in a normal post
queue true to take the next free posting time. Not with scheduledFor

A draft is published once:

  • Send an Idempotency-Key. If your request times out, send the same call again with the same key and you get the same post back with 200 and Idempotent-Replayed: true. See Idempotency.
  • Without a matching key, promoting a draft that is already published (or discarded) gives 409 already_promoted. Nothing is sent twice.
  • If nothing can be published (422 nothing_to_publish), the draft stays a draft, so you can fix it and try again.
  • After a successful promote the draft's status is promoted, and it can no longer be changed.
curl https://omniposts.dev/v1/ideas/0192a3b4-0000-7000-8000-0000000000d1/promote \
  -H "Authorization: Bearer omniposts_..." \
  -H "Idempotency-Key: promote-launch-draft" \
  -H "Content-Type: application/json" \
  -d '{ "targets": [{ "channelId": "0192a3b4-0000-7000-8000-000000000001" }], "queue": true }'

DELETE /v1/ideas/{id}

Discard a draft

DELETE /v1/ideas/{id} discards a draft. The answer is 204, and the draft no longer appears in the list. Discarding twice is not an error. A draft that was already published cannot be discarded: you get 409 not_editable, because it is part of the record of what was sent.