Chirio
Get Started
Introduction
Quickstart
How Chirio works
Authentication
Platforms
Overview
Instagram
Threads
X
LinkedIn
Guides
Connect accounts
Publish a post
Media requirements
Carousels
First comments
Idempotency and retries
Account lifecycle
Revocation and deletion
The dashboard
Billing
Plans
Metering
Quotas
Api Reference
Overview
Accounts
Posts
Errors
Changelog
Roadmap
TrademarkTrademark
Ctrl k
Search...
Sign up
Chirio
Get Started
Introduction
Quickstart
How Chirio works
Authentication
Platforms
Overview
Instagram
Threads
X
LinkedIn
Guides
Connect accounts
Publish a post
Media requirements
Carousels
First comments
Idempotency and retries
Account lifecycle
Revocation and deletion
The dashboard
Billing
Plans
Metering
Quotas
Api Reference
Overview
Accounts
Posts
Errors
Changelog
Roadmap
TrademarkTrademark© Dopler. All rights reserved.
Built with Aveiro
Guides

Idempotency and retries

Make a timed-out publish safe to send again, and know what a replayed response actually means.
Updated 2d ago
First comments
Account lifecycle
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.
curl -X POST "$CHIRIO/api/v1/posts" \
  -H "Authorization: Bearer $CHIRIO_API_KEY" \
  -H "Idempotency-Key: release-2-4-0" \
  -H "Content-Type: application/json" \
  -d '{ "content": "...", "platforms": [ ... ] }'
{ "idempotencyKey": "release-2-4-0", "content": "...", "platforms": [] }
let { post, replayed } = await publish();

while (replayed && post.targets.some((t) => t.status !== "published" && t.status !== "failed")) {
  await new Promise((r) => setTimeout(r, 5_000));
  ({ post } = await fetch(`${CHIRIO}/api/v1/posts/${post.id}`, { headers }).then((r) => r.json()));
}