Skip to content
Sandbox

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.

View as Markdown

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

ResponseRetry?How
429 rate_limitedYesAfter Retry-After seconds. Nothing was computed or billed.
503 busyYesAfter Retry-After seconds, same key. An identical request was in progress, or the service was momentarily contended; nothing was written.
503 engine_errorYesWith an increasing delay, same Idempotency-Key. Nothing was billed or memorised. No Retry-After.
503 pricing_not_activatedLaterAfter Retry-After (in the order of several minutes). Billing is not activated for the operation yet.
500 internal_errorYes, carefullySame 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 timeoutYesSame key, same body. See Timeouts.
409 (any code)NoFix the request or the key.
400, 413, 415NoFix the request.
401, 403NoFix the key, its scopes or the account state.
402 sandbox_quota_exhaustedNoThe allowance does not refill.
402 no_active_subscriptionNoNot until the subscription is active.
404, 410NoThe 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

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));
  }
}
TypeScript

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.

Published on 2026-10-08 21:04 UTC (version 1).

Was this page helpful?