Lumeo Docs
Core Concepts

Error handling

How Lumeo reports failures, when to retry, and how idempotency keys interact with errors.

Lumeo uses conventional HTTP status codes. This page is the reference for what each range means, the error codes you'll actually encounter, and how retries and idempotency interact when something fails.

The error response shape below is assembled from what the current NestJS exception filters actually return, not from a single canonical schema — some endpoints (e.g. confirm, payment list filters) use untyped request handling and can surface a generic 500 for cases a typed DTO would normally reject with 400. Treat unfamiliar error codes defensively rather than assuming an exhaustive enum.

Status code ranges

RangeMeaningWhat to do
2xxSuccess
400Bad request — validation failure or invalid state transitionFix the request; do not retry unchanged.
401Missing or invalid Bearer tokenRefresh or reissue the API key.
403Signature or permission failure (e.g. webhook HMAC mismatch)Check signing secret / scope.
404Resource not found, or not owned by the authenticated userVerify the ID and that it belongs to this account.
429Rate limitedBack off and retry after the window resets.
5xxInternal errorSafe to retry with the same Idempotency-Key where supported.

Error response shape

Most 4xx errors from validated endpoints return:

{
  "statusCode": 404,
  "message": "Payment not found",
  "error": "ERR_PAYMENT_NOT_FOUND"
}

Known error codes seen across the API today:

CodeWhereMeaning
ERR_PROFILE_NOT_FOUNDAccountsThe user has no profile record yet.
ERR_PAYMENT_NOT_FOUNDPaymentsPayment ID doesn't exist or isn't owned by this user.
ERR_WALLET_NOT_FOUNDPaymentsThe funding vaultId supplied to confirm doesn't exist.

Endpoints built on untyped inline request bodies (see the confirm, link-wallet, and payment list-filter endpoints in Payments and Accounts) don't run through a class-validator DTO. Malformed input to these can surface as a 500 rather than a 400 — plan your error handling to treat unexpected 5xx from these specific endpoints as a possible input problem, not purely a server fault.

Rate limits

Rate limits are enforced per-endpoint, per-user:

EndpointLimit
POST /payments/initiate10 requests / minute
POST /payments/confirm5 requests / minute

A 429 response does not include a Retry-After header today — use exponential backoff starting at 1 second, matching the pattern in Handle webhook retries.

Idempotency and retries

initiate and confirm both accept an Idempotency-Key header (see Authentication). This changes how you should react to different failure types:

  • On a 5xx or timeout: retry with the same Idempotency-Key. If the original request actually succeeded server-side despite the timeout, you get back the original result instead of creating a duplicate payment.
  • On a 4xx: do not retry with the same key unchanged — the request itself was rejected, and Lumeo will keep rejecting it. Fix the input first.
  • Keys expire after 24 hours, scoped to your API key. A retry after expiry is treated as a brand-new request.

Two payment identifiers exist and are not interchangeable: id (the payment's primary key, returned from initiate/confirm) and txnId (derived from the Idempotency-Key you sent). GET /payments/:id/status expects txnId; POST /payments/:id/retry and GET /payments/:id/form15ca expect id. Passing the wrong one returns a 404, not a helpful error explaining the mismatch — see Payments for exactly which route expects which.

What's safe to retry blindly

CallSafe to retry without checking state first?
GET requests (status, list, balance)Yes — always safe.
initiate / confirm with the same Idempotency-KeyYes — idempotent by design.
initiate / confirm with a new keyNo — will create a duplicate payment.
retry on a paymentOnly if the payment is still FAILED or PENDING — check status first.

On this page