DocsGuides

Idempotency and retries

Send an Idempotency-Key header on every request and retries are safe. A repeat of a successful call returns the stored response instead of parsing again.

One key per document#

Any unique string; a UUID is fine. Generate it once for a document and reuse it for every attempt, including attempts after a timeout where you never saw a response.

What a repeat does#

State of the first callThe repeatCharged
Succeeded, within 24 hoursReturns the stored response with Idempotent-Replayed: true.No
Failed with any statusRuns fresh. Failures are not stored against a key.Only if this attempt succeeds
Succeeded, more than 24 hours ago409 idempotency_key_not_replayable, reason: "expired".No
Succeeded, response too large to store409 idempotency_key_not_replayable, reason: "too_large".No
Still in flightCounts towards the four concurrent documents and may return 429. Retry shortly with the same key.No

Retrying#

While a document keeps failing, every attempt runs fresh and none of them cost anything. Retry 429 and 5xx with backoff.

import { randomUUID } from "node:crypto";

// One key per document, reused by every attempt. A new key on a retry
// is a new document, and is charged as one.
async function parse(pdf, { key = randomUUID(), attempt = 1 } = {}) {
  const res = await fetch("https://statementbear.com/api/v1/documents", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.STATEMENTBEAR_KEY}`,
      "Content-Type": "application/pdf",
      "Idempotency-Key": key,
    },
    body: pdf,
  });

  if (res.ok) return res.json();

  // Retry 429 (concurrency) and 5xx.
  const retryable = res.status === 429 || res.status >= 500;
  if (!retryable || attempt >= 4) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message}`);
  }

  await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
  return parse(pdf, { key, attempt: attempt + 1 });
}

The 409#

Stored responses are kept for 24 hours and then deleted. After that the same key returns 409 rather than parsing again, which would charge a second time for one call. The reason field says whether the response expired or was too large to store. Retry with a new key if you do want the document parsed again.

Response headers#

HeaderMeaning
Idempotent-Replayed: trueThe body came from storage. Nothing was parsed or charged.
X-Billable-Documents1 when the call is on your invoice, 0 when it is not. A replay is always 0.
Something wrong or missing on this page? [email protected]