Publishing is synchronous and can run for minutes on video. A network failure or client
timeout leaves you unable to tell whether the post went out. An idempotency key removes the
question.Or in the body — the body field wins if both are present:Keys are 1–255 characters and scoped per project, so two tenants can use the same string
without colliding.
What a retry does
Attempt
Status
Body
First
`201`
`replayed: false`
Retry with the same key
`200`
`replayed: true`, the original post
Retrying never publishes twice. The post row carrying the key is inserted before any
platform call, and a unique index on (project, key) means a concurrent duplicate loses the
insert race and is answered with the original post instead.
A replayed response is a snapshot, not a promise
Replayed targets may not be final
A replay returns the stored per-target state at that moment. If the original request is still running — video processing, a slow carousel — some targets will still be pending. Poll GET /api/v1/posts/:id until they settle.
Choosing a key
Derive it from the logical post, not from the attempt:
Good: release-2-4-0, campaign-42-slot-3, a UUID stored with your own draft record.
Bad: a fresh crypto.randomUUID() per attempt — that is no key at all.
Use a new key when retrying failed targets
Reusing the key returns the original post untouched. To republish targets that failed, send a fresh request with only those accountIds and a NEW idempotency key — usage is recorded per target that actually published, so retrying the third of three targets bills one write.
What is not idempotent
Only POST /api/v1/posts takes a key. POST /api/v1/accounts/connect mints a new single-use
OAuth state each call by design — an authorize URL that could be replayed would be a security
problem, not a convenience.