# Retries

> Which responses to retry, after what delay, and why a retry is safe as long as it keeps the same Idempotency-Key. With a ready-to-use retry loop.

A retry is safe on Noryo Merchant API **on one condition: reusing the same `Idempotency-Key`**. If the previous attempt was settled, you receive its memorised response; if it was not, the request is processed. In no case is the enrichment duplicated or billed twice.

## What to retry

| Response | Retry? | How |
|---|---|---|
| `429 rate_limited` | Yes | After `Retry-After` seconds. Nothing was computed or billed. |
| `503 busy` | Yes | After `Retry-After` seconds, same key. An identical request was in progress, or the service was momentarily contended; nothing was written. |
| `503 engine_error` | Yes | With an increasing delay, **same `Idempotency-Key`**. Nothing was billed or memorised. No `Retry-After`. |
| `503 pricing_not_activated` | Later | After `Retry-After` (in the order of several minutes). Billing is not activated for the operation yet. |
| `500 internal_error` | Yes, carefully | Same key: if the attempt had been settled, you receive the memorised response. If the error persists, contact support with the `request_id`. |
| Network cut, client-side timeout | Yes | Same key, same body. See [Timeouts](https://developer.noryo.app/en-GB/merchant/operations/timeouts). |
| `409` (any code) | No | Fix the request or the key. |
| `400`, `413`, `415` | No | Fix the request. |
| `401`, `403` | No | Fix the key, its scopes or the account state. |
| `402 sandbox_quota_exhausted` | No | The allowance does not refill. |
| `402 no_active_subscription` | No | Not until the subscription is active. |
| `404`, `410` | No | The identifier does not exist for you, or its content has expired. |

`Retry-After` only appears on responses that a retry can clear. Its presence is a good indicator; its absence on a `4xx` means: do not retry unchanged.

## The rules of a good loop

1. **Generate the `Idempotency-Key` once**, before the first attempt, and keep it for every retry of that operation.
2. **Send exactly the same body again.** A different body under the same key returns `409 idempotency_key_reuse`.
3. **Honour `Retry-After`** when it is present; otherwise wait longer and longer between attempts.
4. **Cap the number of attempts**, then surface the error with its `request_id`.
5. **Log every attempt**: each one has its own `Noryo-Request-Id`.

## A loop in TypeScript

```ts
import { randomUUID } from "node:crypto";

const BASE_URL = "https://api.noryo.app"; // keep it in configuration
const RETRYABLE = new Set([429, 500, 503]);

export async function enrichWithRetries(body: Record<string, unknown>, maxAttempts = 4) {
  const idempotencyKey = randomUUID(); // once per operation
  for (let attempt = 1; ; attempt++) {
    let res: Response | null = null;
    try {
      res = await fetch(`${BASE_URL}/merchant/v1/enrichments`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.NORYO_API_KEY}`,
          "Content-Type": "application/json",
          "Idempotency-Key": idempotencyKey, // the same on every attempt
        },
        body: JSON.stringify(body),         // the same body on every attempt
        signal: AbortSignal.timeout(10_000),
      });
    } catch {
      // network cut or timeout: the attempt may have been settled, the key will tell
    }

    if (res && !RETRYABLE.has(res.status)) return res;
    if (attempt >= maxAttempts) return res;

    const retryAfter = Number(res?.headers.get("retry-after"));
    const delayMs = retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
}
```

The 10-second timeout in this example is a client-side choice, not a value published by Noryo. A response returned after a successful retry may carry `Noryo-Idempotency-Replayed: true`: a sign that the previous attempt had succeeded.

## Refreshes

The same rules apply to `POST …/refresh`, where the `Idempotency-Key` is mandatory anyway. Refresh has no economic deduplication: the key is its only protection against double billing. Never regenerate it between two attempts of the same refresh.

---

Canonical page: https://developer.noryo.app/en-GB/merchant/operations/retries
