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

# Idempotency and Retries

> How to retry requests safely and prevent duplicate label creation

# Idempotency and retries

Shipping labels have real-world cost and logistics impact. Duplicate labels cause operational confusion, wasted shipping spend, and inventory tracking errors. The Flex Forward API provides built-in idempotency to prevent this.

## How idempotency works

The `POST /labels` endpoint uses the `idempotencyKey` field to ensure that each label creation request is processed exactly once.

**First request** — the API creates the label and returns HTTP 201:

```bash theme={null}
curl -X POST https://api.flexforward.com/labels \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "N-2026-05-27-TEST-03",
    "courier": "yunexpress",
    ...
  }'
```

```json 201 Created theme={null}
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "created",
  "courier": "yunexpress",
  "courierOrderNumber": "YT2503010001",
  "courierTrackingNumber": "YT2503010001CN",
  "error": null
}
```

**Replayed request** — the same `idempotencyKey` returns the original result with HTTP 200, without creating a new label:

```json 200 OK (idempotent replay) theme={null}
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "created",
  "courier": "yunexpress",
  "courierOrderNumber": "YT2503010001",
  "courierTrackingNumber": "YT2503010001CN",
  "error": null
}
```

The idempotency key is scoped to your API account. Two different accounts can use the same key without collision.

## Choosing an idempotency key

The key should uniquely identify the intended label creation. Good patterns:

| Pattern          | Example                                | Use case                  |
| ---------------- | -------------------------------------- | ------------------------- |
| Order ID         | `N-2026-05-27-TEST-03`                 | One label per order       |
| Order + sequence | `N-2026-05-27-TEST-03-1`               | Multiple labels per order |
| UUID             | `f47ac10b-58cc-4372-a567-0e02b2c3d479` | General purpose           |

<Warning>
  Never generate a new `idempotencyKey` when retrying the same logical request. Reuse the original key so the API can detect the duplicate.
</Warning>

<Note>
  After a 400 validation error, the same `idempotencyKey` can be reused because no label was created. Fix the request and retry with the same key.
</Note>

## Retry strategy

For transient failures (HTTP 500, 502, or network timeouts), retry with exponential backoff:

<Steps>
  <Step title="Set a request timeout">
    Use a reasonable timeout for the HTTP request (e.g., 30 seconds).
  </Step>

  <Step title="On failure, wait before retrying">
    Use exponential backoff: 1 second, 2 seconds, 4 seconds, 8 seconds.
  </Step>

  <Step title="Reuse the same idempotency key">
    Always retry with the same `idempotencyKey` as the original request. This ensures no duplicate label is created even if the original request succeeded but the response was lost.
  </Step>

  <Step title="Cap the retry count">
    Stop after 3–5 retries. If the request still fails, log the error and escalate for investigation.
  </Step>
</Steps>

```
Attempt 1: POST /labels → timeout
  Wait 1s
Attempt 2: POST /labels → 502
  Wait 2s
Attempt 3: POST /labels → 200 (idempotent replay — the label was created on attempt 1)
```

## Idempotency by endpoint

| Endpoint             | Idempotent | Mechanism                                          |
| -------------------- | ---------- | -------------------------------------------------- |
| `POST /labels`       | Yes        | `idempotencyKey` field — required in every request |
| `GET /labels/{id}`   | Yes        | Read-only — naturally idempotent                   |
| `GET /tracking/{id}` | Yes        | Read-only — naturally idempotent                   |

## Failure scenarios

<AccordionGroup>
  <Accordion title="Network timeout before receiving a response">
    The label may or may not have been created. Retry with the same `idempotencyKey`. If the label was created, the API returns HTTP 200 with the original result. If not, it creates the label and returns HTTP 201.
  </Accordion>

  <Accordion title="HTTP 500 Internal Server Error">
    A transient server error. Retry with backoff using the same `idempotencyKey`.
  </Accordion>

  <Accordion title="HTTP 502 Bad Gateway (courier error)">
    The courier service returned an error. The label is persisted with `status: failed`. Check the error message before retrying — some courier errors require changes to the request (e.g., invalid address). For transient courier issues, retry with the same `idempotencyKey`.
  </Accordion>

  <Accordion title="HTTP 400 Validation Error">
    The request is invalid. Do not retry without fixing the request. The same `idempotencyKey` can be reused after correcting the request body because no label was created.
  </Accordion>
</AccordionGroup>
