> ## Documentation Index
> Fetch the complete documentation index at: https://docs.madiad.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Safely retry requests without creating duplicate posts.

Network calls fail in ways that leave you unsure whether the request went through. **Idempotency keys** make retries safe: replay the same request with the same key and MADIAD Hub returns the original result instead of publishing again.

## Send an idempotency key

Add an `Idempotency-Key` header with a value you control — a UUID per logical action is ideal. The Hub also accepts `X-Idempotency-Key` or `X-Request-Id` as equivalents.

```bash theme={null}
curl -X POST https://api.madiad.com/v1/posts/photos \
  -H "Authorization: Bearer $MADIAD_API_KEY" \
  -H "Idempotency-Key: 9f1c2e7a-4b6d-4e2a-9c10-7b3f8d5a1e22" \
  -F "profile_id=prof_…" \
  -F "platforms[]=instagram" \
  -F "caption=Launch" \
  -F "photos[]=@./launch.jpg"
```

## How it behaves

* **First request** with a key — processed normally; the result is stored against that key.
* **Replay** with the same key **and the same content** — returns the **stored result**; no second post is created, and the response carries `Idempotent-Replayed: true`.
* **Same key, different content** — rejected with `409 idempotency_conflict`. Nothing is published.
* **Different key** — treated as a brand-new request.

<Tip>
  Generate the key **before** the first attempt and reuse it for every retry of that same action. Generating a new key on retry defeats the purpose.
</Tip>

## Telling a replay apart from a fresh publish

A replayed response is identical to the original — same `request_id`, same status. That is the point, but it means an automation whose key stops changing keeps receiving successful-looking responses while **nothing new is being published**.

Every replayed response therefore carries a header:

```http theme={null}
Idempotent-Replayed: true
```

A fresh publish never sets it. If you run scheduled or queued publishing, treat this header as an alarm: it means the key you sent had already been used, so the content you just submitted did **not** go out.

<Warning>
  **Use a new key for every new post.** A counter that stops advancing, a retried workflow item, or a template that hardcodes the key will all send content under a key that is already spoken for. Since 26 July 2026 that returns `409 idempotency_conflict` when the content differs — before then it silently returned the earlier result.
</Warning>

## When the outcome is unknown

A publish can take longer than MADIAD Hub is allowed to wait. When that happens the request is **not** reported as a failure, because the post may well have gone out — we stopped waiting, the platform did not stop working.

You get `502` with code `upstream_timeout`, nothing is refunded, and the key you sent is **closed against that answer**. Replaying it returns the same `502` and the same body — deliberately, so an automation branching on the status code cannot record an unknown post as a success.

```json theme={null}
{ "status": "unknown", "platforms": ["instagram"], "request_id": null, "results": null }
```

What to do:

1. Check [history](/concepts/posts) or the platform itself before doing anything else.
2. If it did **not** publish, send it again with a **new** `Idempotency-Key` — the old one now permanently answers "unknown".
3. Never retry it automatically on the same key expecting a different result.

## When to use it

Always, for any request that creates something — posts in particular. It's essential when:

* A client or workflow tool (n8n, queues, cron) **auto-retries** on timeout.
* A webhook or job may be **delivered more than once**.
* A user **double-clicks** publish.

Keys are scoped to your account and remembered long enough to cover realistic retry windows.
