Idempotency · Robopost Docs

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_PROGRESS with retry_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}")

Next

Changes and webhooks