Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.api.bsa.ai/llms.txt

Use this file to discover all available pages before exploring further.

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:
{
  "code": "not_found",
  "message": "lms: status=404 code=\"error.msg.client.id.invalid\": Client with id 9999 does not exist"
}
For validation failures (invalid_argument), the body wraps a list of field errors:
{
  "code": "invalid_argument",
  "message": "[{\"field\":\"officeId\",\"error\":\"required\"},{\"field\":\"activationDate\",\"error\":\"required when active=true\"}]"
}
The message is a JSON-encoded array of {field, error} pairs.

Status code mapping

HTTPCodeWhen
400invalid_argumentMalformed body, missing required field, invalid path/query param
401unauthenticatedMissing/invalid/expired token, or non-admin subject
403permission_deniedToken recognized but caller lacks permission for this resource
404not_foundResource does not exist
409abortedState conflict — e.g. attempting to disburse an unapproved loan
500internalUnexpected upstream failure (LMS or credit service unavailable)
503unavailableService is temporarily unavailable

Upstream errors

When LMS or the credit service returns its own structured error (e.g. validation against LMS’s business rules), the wrappers API preserves the upstream message in the message field. You’ll see substrings like error.msg.loan.must.be.approved in there — these come straight from LMS and can be used to branch on specific business-rule failures.

Field-level details

When LMS rejects a request with multiple field errors, each one is appended to the message in [fieldName: detail; ...] form. Example:
{
  "code": "invalid_argument",
  "message": "lms: status=400 code=\"validation.msg.validation.errors.exist\": Validation errors exist. [firstname: The parameter `firstname` is mandatory.; lastname: The parameter `lastname` is mandatory.]"
}
Parse from the first [ to read each rejected field individually. The top-level globalisation code (validation.msg.validation.errors.exist) plus the per-field codes (visible in LMS’s developer messages) give you enough specificity to branch programmatically.

Retrying

CodeRetry?
unavailableYes, with backoff
internalYes once, then surface to caller
abortedNo — fix the request or the resource state
invalid_argument, not_found, unauthenticated, permission_deniedNo — client-side fix

Example: branching on errors

import requests

resp = requests.post(f"{BASE}/v1/customers", json=payload, headers={"Authorization": f"Bearer {token}"})

if resp.ok:
    customer = resp.json()
elif resp.status_code == 400:
    fields = json.loads(resp.json()["message"])
    for fe in fields:
        print(f"  {fe['field']}: {fe['error']}")
elif resp.status_code == 401:
    refresh_token_and_retry()
else:
    raise RuntimeError(resp.json()["message"])