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

# Retrying safely

> Send the same message twice by accident, and only one arrives. How the optional Idempotency-Key header works, and when you need it.

A send is not free to repeat. If your request times out, or your job runner retries a failed step, or a proxy gives up waiting and your client tries again, **the message goes out twice** — and your customer sees it twice.

That is true of every HTTP API that sends something, and it is why this one accepts an `Idempotency-Key`.

<Note>
  This header is **optional**. If you do not send it, nothing changes: the API behaves exactly as it always has, and a retry sends again. Everything below is opt-in.
</Note>

## The header

Pick a string that is unique to the *message you are trying to send* — an order id, a row id, a UUID — and send it with the request:

```bash Sending with a key theme={null}
curl -X POST "https://api.bulkneo.com/v1/messages/text" \
  -H "apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4471-confirmation" \
  -d '{
    "instance_id": "YOUR_INSTANCE_ID",
    "to": "919000000001",
    "text": "Your order 4471 is on its way."
  }'
```

Send that **exact request** again — same key, same body — any time in the next 24 hours, and you get the **original response back**, unchanged, and nothing is sent.

## What each outcome looks like

| What you did | What you get | Was anything sent? |
| - | - | - |
| First request with a key | `200` and `Idempotency-Replayed: false` | Yes |
| The same request again | `200` and `Idempotency-Replayed: true`, byte-identical body | **No** |
| The same key, still processing | `409 idempotency_key_in_flight` | Not by this request |
| The same key, **different** body | `422 idempotency_key_reused` | No |
| The first attempt failed | The key is released — a retry is a real retry | No |

<Check>
  Read **`Idempotency-Replayed`** to know which happened. `false` means this call did the sending; `true` means it was a replay. If the header is **absent from the response**, idempotency is not enabled on the deployment you are talking to and retries will send again — treat that as "not protected".
</Check>

## Choosing a key

* Make it unique **per message**, not per customer and not per day. `order-4471-confirmation` is good; `daily-reminder` is not, because tomorrow's reminder is a different message that would be refused as a reuse.
* 8 to 255 characters, from letters, digits, and `.` `_` `:` `~` `-`.
* A UUID is always a safe choice if you have nothing natural to use.
* Reuse the **same** key for every retry of the same send. Generating a fresh key on retry defeats the entire purpose.

## Two things it deliberately does not do

**It does not merge different messages.** Reusing a key with a changed body returns `422 idempotency_key_reused` rather than quietly sending the new one or quietly replaying the old one. Both of those would be a lie about what your customer received.

**It cannot rescue a lost reply.** If we genuinely delivered the message and only the response was lost on the way back to you, a retry sends again — the key was released when the request failed, because the far more common case is a request that never got through at all. No key can tell those two apart from our side. If exactly-once matters more to you than delivery, wait and check rather than retry.

## When to bother

<CardGroup cols={2}>
  <Card title="Worth it" icon="check">
    Anything triggered by a queue, a webhook, a cron, or a serverless function — all of which retry by default, often without telling you.
  </Card>

  <Card title="Probably not" icon="minus">
    A human clicking a button in a UI you control, where you can disable the button and show the result.
  </Card>
</CardGroup>

## Errors

Both codes are documented in full on the [Errors](/errors) page.

* `409 idempotency_key_in_flight` — your first request is still running. Retry the identical request shortly; you will get the original result. Carries `Retry-After`.
* `422 idempotency_key_reused` — this key already means a different message. Use a new key.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.