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

# Retry without duplicate sends

> Reuse an idempotency key to retry the same API request safely.

An `Idempotency-Key` header makes a `POST` safe to retry: a repeat with the same key returns the first answer and does not act twice, so your retries never make a second send.

```bash
curl https://api.flow.engineer/v0/conversations/conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1/basic \
  -H "Authorization: Bearer $FLOW_MESSAGING_KEY" \
  -H "Idempotency-Key: evt_01JB8ZC3K5M7P9R1T3V5X7Z9B1" \
  -H "Content-Type: application/json" \
  -d '{"text": "Got it!"}'
```

## Rules

* Any unique string up to 255 characters works; a UUID or ULID is a good choice. Every `POST` accepts one.
* Keys are kept for **24 hours** per app and mode.
* A repeat returns the first answer with the header `Idempotent-Replayed: true`.
* Reusing a key with a different method, path or body fails with `409` [`idempotency_conflict`](https://flow.engineer/docs/unified-messaging-api/errors/idempotency_conflict). So does a repeat that arrives while the first request is still running, and a repeat of a request that made a secret shown only once (creating a webhook endpoint, rotating its secret): Flow does not keep that answer, so it cannot show the secret again.
* Answers worth retrying (`429`, `500`, `502`, `503`, `504`) are **not** kept, so a retry with the same key runs again. Every other answer is kept, a `422` included.
* Typing takes a key too: a repeat within 24 hours answers the first result and does not show the indicator again, so give each typing call its own key (or none).

## What it does not cover

The key protects your requests, not the hop to the channel. When a channel takes a send but never answers, Flow cannot tell whether it arrived, so it sends it once more; on Telegram, which has no way to check, the person can rarely get it twice (on the other channels Flow first waits for the channel's own report). If the outcome is still unknown, the message fails with `channel_timeout_unknown_outcome` ([`channel_error`](https://flow.engineer/docs/unified-messaging-api/errors/channel_error)): check the conversation before sending it again.

## Good keys for agents

* **Replying to an event:** use the event's `id`, plus what each call is when you send several messages (`evt_...:0`, `evt_...:1`, or `evt_123:text`, `evt_123:card:2`). Give every call its own key. A redelivered webhook then cannot make your agent answer twice.
* **A send that replaces a refused one:** a `422` answer is kept under its key like any other, so repeating the key returns the same `422`. The message you send instead (a `basic` after a `buttons` came back `422 unsupported_content`) is a new request with a new key: the refused one plus what it is, such as `evt_123:card:2:basic`.
* **Webhook replies** already use the event's `id` as their key.
* **Stream frames:** the `ref` of a `send` or `start` frame is its idempotency key.
* **The TypeScript SDK** generates a key for every `POST` and reuses it across its own retries; pass `idempotencyKey` to choose it yourself.
