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

# Idempotency

> Every write accepts an Idempotency-Key. Retries are safe. No double-posting.

Sending the same `POST /v1/posts` request twice should publish exactly one post per target. That's what the `Idempotency-Key` header buys you — and on multi-target batches it covers every target in the batch, not just the first.

## How it works

Pass a unique key — typically a UUID v4 — on every write:

```bash header.txt theme={"system"}
Idempotency-Key: 01HY6X4AWBJM2K9F2PTQMRD9JQ
```

The first request with that key executes normally. Any subsequent request with the same key, the same route, and a matching body hash returns the **first response** verbatim — same status code, same batch envelope, same per-target `results[]`. None of the underlying platform calls run a second time.

If the body hash differs from the first request, you get back [`idempotency_conflict`](/errors/idempotency_conflict) so you don't accidentally publish two different posts under one key.

## Multi-target batches

The key applies to the whole batch. If you send a request with `targets: [A, B, C]` and retry with the same key, you replay the same batch response — A, B, and C each publish at most once, even if the first call partially succeeded. Change any target (add D, remove C, swap an `accountId`) and the body hash changes; you'll get `idempotency_conflict` on the retry.

```bash retry-safe.sh theme={"system"}
# First call — fans out to three targets.
curl -X POST https://api.letmepost.dev/v1/posts \
  -H "Authorization: Bearer $LMP_KEY" \
  -H "Idempotency-Key: 01HY6X4AWBJM2K9F2PTQMRD9JQ" \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{
  "targets": [
    { "accountId": "<twitter_account_id>" },
    { "accountId": "<linkedin_account_id>" },
    { "accountId": "<bluesky_account_id>" }
  ],
  "text": "shipped"
}
EOF

# Network blew up before the response landed? Retry with the same key,
# byte-identical body. You get back the original batch envelope; no
# target publishes twice.
```

## When to use a key

* Every retry path. Network blip, lambda re-invocation, Temporal workflow restart — pin them to a key the caller computes deterministically.
* Every webhook handler that publishes. Webhooks are at-least-once; keys make idempotent processing a one-line addition.
* Every CI / cron job that posts on a schedule.

## What counts as the "same request"

The replay cache keys on:

* API key
* HTTP method + path
* The exact `Idempotency-Key` header
* A SHA-256 hash of the request body

Change the body and the second request returns `idempotency_conflict` — that's a feature, not a bug. It catches the case where you reused a key by mistake but actually meant to publish something different.

## TTL

Replay records are kept for **24 hours** after the first request, then evicted. Beyond 24h the same key is treated as fresh, so don't rely on long-lived dedup; that's what idempotent application logic is for.

## Generating keys

UUID v4 from any standard library is fine. For deterministic retries, hash the upstream event id (e.g., your Temporal workflow run id):

```ts key-strategies.ts theme={"system"}
// Random per-call — fine for ad-hoc scripts.
const key = crypto.randomUUID();

// Deterministic from your own correlation id — best for workflows.
const key = sha256(\`workflow:\${workflowId}:step:\${stepName}\`);
```

## What this is *not*

Idempotency keys do not deduplicate based on content. Two different keys, same body, will publish two posts. That's the contract — the key is the unit of "this is the same request"; we don't second-guess.

## See also

* [`POST /v1/posts` reference](/api-reference/#tag/posts/post/v1/posts) for the parameter on the endpoint itself.
* [`idempotency_conflict`](/errors/idempotency_conflict) for the error you'll see on a key-with-different-body collision.
