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.
No public idempotency key header today
The public API does not currently implement an `Idempotency-Key` request header. If you need exactly-once semantics, store your own job identifier before creating or buying shipments and dedupe on your side.
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
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);
}
}
}Related concepts
Start with High-Volume Workflows for the queueing model, then read Certified Mail Workflows if your jobs use USPS-specific options.