Skip to main content

Four production concerns

Four cross-cutting concerns decide whether a Recurso integration survives production: reading errors, retrying transient failures safely, using idempotency keys so retries don’t double-charge, and paging through lists without missing rows. Each SDK expresses these in its own idiom — this guide shows all three side by side.
The biggest difference between SDKs is how a failed request surfaces. Node throws, Go returns a typed error, and the Python client does not raise for documented 4xx/5xx statuses — it returns an Error model in the return union. Read the next section before writing any error handling.

How errors surface

The error envelope is identical on the wire in every language — {"error": {"code", "message"}} — where code is a stable machine-readable string (NOT_FOUND, VALIDATION_ERROR, …) and message is human-readable.
The Python client only returns an Error for statuses the OpenAPI spec documents. For an undocumented status it raises errors.UnexpectedStatus (when raise_on_unexpected_status=True, the default), and a timeout raises httpx.TimeoutException. To read the raw status code, call the .sync_detailed(...) variant — it returns a Response with .status_code, .parsed, .content, and .headers instead of just the parsed body.

Retrying transient failures

Retry only transient failures — HTTP 429 (rate limited) and 5xx (server-side), plus connection/timeout errors. Never retry a 4xx like 400/404/409; those are deterministic and a retry just repeats the failure. Back off exponentially between attempts.
Add jitter and a cap in production, and honor a Retry-After header on 429 when present. The examples keep the schedule fixed for clarity.

Idempotency

Retrying a money-moving or ingestion call is only safe if the server can tell a retry from a genuinely new request. Recurso’s usage-ingestion path takes a caller-supplied transaction_id: a retried event with the same (subscription, transaction_id) collapses to the original instead of double-counting (the response reports the original event and a duplicate status). Generate the key once per logical event, before the first attempt, and reuse the same key on every retry.
A fresh transaction_id per retry defeats the point — every attempt looks new and you double-count. The key must be stable for the life of the event you are recording.

Pagination

List endpoints default to a small page. Always pass an explicit limit and loop until a short page comes back.
Paging style is not uniform. Resource lists like plans page with limit + page (1-based). Delivery, event, and usage-event feeds page with limit + offset (0-based). Check the params type for the endpoint you are calling.
Page-based loop over a resource list (plans):
Offset-based loop over a feed (webhook deliveries) — advance offset by the page size instead of incrementing a page number: