Idempotency
On this page
A request can time out after Robopost has created the posts. If you simply send it again, you get the
posts twice. An Idempotency-Key header makes the retry safe: Robopost recognizes it and answers with the
result of the first request.
You need: a unique key for each post you intend to create, for example your own record ID.
Send a key with every create
curl -X POST https://public-api.robopost.app/v1/scheduled_posts \
-H "Authorization: Bearer $ROBOPOST_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: campaign-42-post-7" \
-d '{"text": "Hello", "channel_ids": ["<id>"], "schedule_at": "2026-11-20T15:00:00Z"}'- Same key, same body: you get the first response again, with the header
Idempotent-Replayed: true. No new posts are created. - Same key, different body:
422 IDEMPOTENCY_KEY_REUSED. Use a new key for a different post. - Same key while the first request is still running:
409 IDEMPOTENCY_REQUEST_IN_PROGRESSwithretry_after_seconds. Wait and retry. - If the first request failed with a server error, retrying with the same key runs it again. If it had already created some of the per-channel posts, the retry creates only the missing ones.
Keys are 1–255 printable characters without spaces, are scoped to your team, and are remembered for 7 days.
Find what a request created
If you never got the response, ask for the posts created under the key:
curl "https://public-api.robopost.app/v1/scheduled_posts?idempotency_key=campaign-42-post-7" \
-H "Authorization: Bearer $ROBOPOST_API_KEY"A retry loop that can't duplicate
import time, uuid, requests
def create_post(session, body, key=None, attempts=5):
key = key or str(uuid.uuid4())
for attempt in range(attempts):
try:
r = session.post("https://public-api.robopost.app/v1/scheduled_posts",
json=body, headers={"Idempotency-Key": key}, timeout=30)
except requests.RequestException:
time.sleep(2 ** attempt)
continue
if r.status_code == 409 and r.json().get("code") == "IDEMPOTENCY_REQUEST_IN_PROGRESS":
time.sleep(r.json().get("retry_after_seconds", 2))
continue
if r.status_code >= 500:
time.sleep(2 ** attempt)
continue
return r
raise RuntimeError(f"gave up; check GET /v1/scheduled_posts?idempotency_key={key}")