Skip to content
Checklist

Go-live checklist

Before the first real invoice goes out: eight points an integration should cover. Each one links to the page with the details.

The eight points

  1. 1

    Switch to the live key

    Replace sk_test_… with sk_live_…. Host, paths and responses stay the same; the prefix does not change how the API behaves. Both keys count against the same monthly quota.

  2. 2

    Read the key from the environment only

    The key belongs in an environment variable or a secret manager, never in code, the query string or the request body. It is sent in the Authorization header as a bearer token.

  3. 3

    Tell the two kinds of 429 apart

    If the response carries a quota object (QUOTA_EXCEEDED), the monthly quota is used up: do not retry. Without a quota object the rate limit applies: retry with exponential backoff, since no Retry-After header is sent.

  4. 4

    Honor Retry-After on 503

    Responses such as SERVER_BUSY (5 seconds) or EMBED_UNAVAILABLE (60 seconds) state in the Retry-After header how long to wait. Nothing is billed.

  5. 5

    Do not retry 4xx

    A 400 or 422 is fixed in the data, not by a second attempt. On 422, errors[] names the rule that failed.

  6. 6

    Detect duplicate invoices yourself

    The API has no Idempotency-Key header. Every generate response carries payloadHash, a SHA-256 over the canonical JSON of your invoice data: same data, same value. Store it with the invoice and check for it before calling again. hash does not work for this: it changes on every call.

  7. 7

    Log the request id

    Every response carries x-request-id. Log it with every error; support uses it to find your call.

  8. 8

    Check your plan against your volume

    The pricing calculator shows which plan fits your monthly invoice volume and what it costs.

Putting it together: one call with retry

Retries only what a retry can fix, waits as long as Retry-After says, and includes the request id in the error.

javascript
1async function callInvoiceApi(url, body, attempts = 4) {
2 for (let i = 0; i < attempts; i++) {
3 const res = await fetch(url, {
4 method: "POST",
5 headers: {
6 Authorization: `Bearer ${process.env.INVOICE_API_KEY}`,
7 "Content-Type": "application/json",
8 },
9 body: JSON.stringify(body),
10 });
11 if (res.ok) return res.json();
12 
13 const requestId = res.headers.get("x-request-id");
14 const err = await res.json().catch(() => ({}));
15 
16 // 429 with a quota object = monthly quota used up: retrying cannot help.
17 // 429 without it = rate limit. 5xx = try again. 4xx = fix the data.
18 const retryable = (res.status === 429 && !err.quota) || res.status >= 500;
19 if (!retryable || i === attempts - 1) {
20 throw new Error(`${res.status} ${err.error ?? ""} (request ${requestId})`);
21 }
22 
23 // 503 SERVER_BUSY / EMBED_UNAVAILABLE say how long to wait; otherwise back off.
24 const retryAfter = Number(res.headers.get("retry-after"));
25 const waitMs = retryAfter > 0 ? retryAfter * 1000 : 2 ** i * 1000;
26 await new Promise((resolve) => setTimeout(resolve, waitMs));
27 }
28}

Not there yet?

The quickstart gets you to your first invoice in five minutes with a test key.