Skip to main content
The API returns structured errors with stable codes. Branch on the code field, not the human-readable message.

Error envelope

Any non-2xx response has the following body:
The HTTP status always matches the code. The message is a human-readable string scoped to the partner contract — internal LMS vocabulary (Client, globalisation codes like error.msg.*) is rewritten or stripped before it reaches the wire, and it may change, so don’t parse it. code is an error-only field: successful responses return the resource object (no code), and 204 No Content has no body. Some errors also carry an optional sub_code — a stable, finer-grained business reason within a code. It lets you map an error to a specific customer-facing message without parsing message (see Sub-codes below). sub_code is additive and optional: an error may omit it, and new sub-codes may be introduced over time — so always branch on code first, and treat an unknown or absent sub_code by falling back to the code. For validation failures (invalid_argument), the message is a JSON-encoded array of {field, error} pairs:

Error codes

These are the codes the API currently returns. Branch on code.

Reserved codes (not currently emitted)

The error vocabulary also defines the canonical codes below, which the API does not currently return. They’re documented so your handler recognises them if a future version starts using one — you don’t need to handle them today. Your code fallback (treat any unrecognised code as a non-retryable error and surface it) covers them safely.

Sub-codes

When present, sub_code pins down the exact business scenario behind the code. Each maps cleanly to a fixed customer-facing message — only validation_error requires reading message (for the per-field detail). Branch on code first; use sub_code to refine the message you show.

Idempotency (repayments)

Repayments accept an optional idempotencyKey (your wallet transaction reference). Re-sending the same key on the same loan returns the original transaction with 200 — a safe replay, not an error. Re-using a key already recorded elsewhere (a different loan, or a race) returns already_exists (409).

Retrying

The reserved codes aren’t returned today; if one is introduced later, too_many_requests / resource_exhausted and deadline_exceeded would be retryable with backoff.

Example: branching on errors