Retries & Idempotency

Corvo integrations work best when your own queue treats each shipment as an independent job with dedupe keys, retry rules, and bounded concurrency.

Recommended guardrails

  • Create an internal job ID before you call Corvo.
  • Store the Corvo shipment ID and selected quote ID once a draft exists.
  • Retry transient failures with exponential backoff.
  • Honor `429 RATE_LIMITED` responses and the `Retry-After` header.
  • Recreate drafts when quote expiry makes a stored quote unusable.

Quote expiry

Draft quotes expire. If a worker holds a draft too long, Corvo returns RATE_EXPIRED with HTTP 410 on buy. Recreate the draft, select a fresh quote, and retry the purchase path.

Pseudo-code pattern

TypeScript
for (const job of queue) {
  await withRetry(async () => {
    if (await alreadyProcessed(job.id)) {
      return;
    }

    const draft = await createDraft(job);
    const quote = chooseQuote(draft.rates);
    const purchased = await buyShipment(draft.id, quote.quote_id);

    await markProcessed(job.id, purchased.id);
  });
}

async function withRetry(fn: () => Promise<void>, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
    try {
      await fn();
      return;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await sleep(2 ** attempt * 1000);
    }
  }
}