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. |
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
- Generate the
Idempotency-Keyonce, before the first attempt, and keep it for every retry of that operation. - Send exactly the same body again. A different body under the same key returns
409 idempotency_key_reuse. - Honour
Retry-Afterwhen it is present; otherwise wait longer and longer between attempts. - Cap the number of attempts, then surface the error with its
request_id. - Log every attempt: each one has its own
Noryo-Request-Id.
A loop in TypeScript
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));
}
}TypeScriptThe 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.
Published on 2026-10-08 21:04 UTC (version 1).