# Idempotency

How to safely retry a write with an Idempotency-Key header, and when one is required.

Source: https://getolet.com/resources/developers/idempotency/
Updated: 2026-10-15

Send an `Idempotency-Key` header on a write and a retry with the same key and the same body replays the original response instead of doing the work twice. Use this whenever a request might time out or your network might drop before you see the response.

## When do I need an idempotency key

It is required on `POST /inventory/adjustments` and `POST /inventory/batch`, because replaying a relative change would move stock twice. A request without it answers `400 VALIDATION_FAILED` naming the `Idempotency-Key` header. It is optional on the other writes that accept one: `PUT /products/{id}/inventory/{outlet_id}`, `POST /products`, `POST /products/{id}/variants`, `POST /customers`, and the create calls for brands, categories, tags and suppliers. `PATCH` and `DELETE` do not take one. Pick a key that is unique per logical operation, such as a UUID you generate once per attempt, and reuse it only when retrying that same attempt. A key is 1 to 255 printable characters with no spaces.

```bash
curl https://api.oletapp.com/your-store/v1/inventory/adjustments \
  -H "Authorization: Bearer olet_live_YOUR_API_KEY" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "PRODUCT_ID", "outlet_id": "OUTLET_ID", "reason_id": "REASON_ID", "quantity": 1}'
```

```js
await fetch('https://api.oletapp.com/your-store/v1/inventory/adjustments', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer olet_live_YOUR_API_KEY',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    product_id: 'PRODUCT_ID',
    outlet_id: 'OUTLET_ID',
    reason_id: 'REASON_ID',
    quantity: 1,
  }),
})
```

## What happens on a retry

A retry with the same key and the exact same request body returns the same status code and the same resource data as the first attempt, without repeating the underlying change. The response carries an `Idempotent-Replayed: true` header so you can tell a replay from an original.

A request that failed with a client error replays that same failure, so fix the request and send it again under a new key. A `500` is not remembered, so you can retry it with the same key. Keys are remembered per credential.

If a request with that key is still being processed, a retry answers `409 IDEMPOTENCY_IN_PROGRESS`. Wait and try again.

## What happens if the body changes

Reusing a key with a different request body answers `422 IDEMPOTENCY_KEY_REUSED`. This is a signal that you generated a key once and are now sending two different operations under it; use a new key per logical operation.

A key stops being remembered after 24 hours, after which point reusing it starts a fresh operation rather than replaying the old one.

## Where to go next

See [errors](/resources/developers/errors/) for the full list of error codes, or the full [API reference](/resources/developers/reference/) for which operations accept a key.
