LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Advisor firms
  • Configuration
  • API Reference
ErrorsVersioning & DeprecationRate limitsPaginationIdempotencyMetadata
Conventions

Errors

The LedgerBee API uses conventional HTTP status codes and returns every error as a JSON envelope. The status code tells you whether retrying can help; the machine-readable code tells you exactly what went wrong.

The error envelope

Every error body carries at least statusCode, timestamp, path, and message. Most errors also include code and details:

Code
{ "statusCode": 400, "timestamp": "2026-06-10T12:34:56.789Z", "path": "/v1/customers", "message": "Bad Request Exception", "code": "SHARED_VALIDATION_FAILED", "details": { "validationErrors": [ { "property": "email", "constraints": { "isEmail": "email must be an email" } } ] } }
FieldAlways presentDescription
statusCodeYesThe HTTP status, repeated in the body.
timestampYesISO 8601 time the error was produced.
pathYesThe request path, relative to the /api mount (e.g. /v1/customers).
messageYesHuman-readable summary. For logging only — its wording may change.
codeNoStable machine-readable identifier (e.g. UNAUTHORIZED, LICENSE.REQUIRED). Branch on this, never on message.
detailsNoContext for the specific code — failing fields, required scopes, the missing license key.

Validation errors (400)

Request bodies are validated strictly: unknown fields are rejected, not ignored. A failed validation returns code: "SHARED_VALIDATION_FAILED" with one details.validationErrors entry per failing field:

  • property — dot-separated path to the field, including array indices (e.g. lines.0.quantity)
  • constraints — map of failed rule name to a human-readable explanation

Status codes

StatusWhen
400 Bad RequestValidation failed (above), or the request violates a business rule — the code says which.
401 UnauthorizedMissing, invalid, expired, or IP-restricted API key / OAuth token. The body has code: "UNAUTHORIZED" and message: "Invalid API key". See Authentication.
403 ForbiddenThe credential is valid but lacks a required scope (code: "INSUFFICIENT_PERMISSIONS", with details.requiredScopes), or your company lacks a required license (code: "LICENSE.REQUIRED", with details.licenseKey). See Authentication for scopes and Licenses for modules.
404 Not FoundNo such route, or the referenced resource doesn't exist in your company.
413 Payload Too LargeThe request body exceeds the size limit — message states the received size and the limit.
429 Too Many RequestsYou hit a rate limit. Back off and retry — see Rate limits.
500 Internal Server ErrorSomething failed on our side. message is a generic Internal Server Error — retry with backoff and contact support if it persists.

A 401 or 403 on the OAuth path carries a standard WWW-Authenticate challenge header. Every challenge carries resource_metadata, the URL of the protected-resource document naming which authorization server covers that endpoint. On a company-scoped MCP endpoint it names that company's own resource, which is what binds the resulting grant to the right company.

The requestThe challenge says
carried no credentialno error code — you have not authenticated yet, and resource_metadata is where to start
carried a token that is invalid, expired or revokederror="invalid_token" — refresh or re-authorize
carried a valid token without a scope the call needserror="insufficient_scope", with scope listing the full set to ask for

A request authenticated with an API key receives no challenge: the challenge describes the OAuth flow, which an API key does not take part in.

Request IDs

Every response, success or error, carries an X-Request-ID header with a UUID unique to that request. The same ID is stamped on our server-side traces. Include it in support requests so we can locate the exact request in our logs.

TerminalCode
curl -i https://api.ledgerbee.com/api/v1/company \ -H "x-api-key: YOUR_API_KEY" # HTTP/1.1 200 OK # X-Request-ID: 7f9c2ba4-1c3d-4e5f-8a9b-0c1d2e3f4a5b

Error codes are stable

code values such as SHARED_VALIDATION_FAILED, UNAUTHORIZED, and LICENSE.REQUIRED are stable identifiers, safe to branch on. They are never renamed or repurposed; message text may be reworded at any time. New codes may be added over time, so treat any unrecognized code as a generic failure for its status code.

A refusal about a billing group or one of its members answers a SUBSCRIPTION_BILLING_GROUP_* code, described on Billing groups. A refusal about an add-on answers a SUBSCRIPTION_ADDON_* code; the ones a public request can receive are described on Add-ons. Both families name subscriptions by their stable id (details.subscriptionId, details.addonSubscriptionId), and SUBSCRIPTION_BILLING_GROUP_MEMBER_LIMIT reports details.slotsUsed, details.slotsRequested and details.maxSlots. details.billingGroupId appears only on the /v1/billing-groups endpoints, and SUBSCRIPTION_BILLING_GROUP_NOT_FOUND always answers 404.

When the subscription a request is about is itself an add-on, the refusal is an add-on code even where the same request on a billing-group member answers a SUBSCRIPTION_BILLING_GROUP_* one. An add-on bills on its parent's invoice and billing dates, so it cannot be moved, re-timed or re-configured on its own. Each of these answers 400, and details.subscriptionId names the subscription the request was about — the add-on, or for a cancellation refused because of its add-ons, the parent:

codeWhenOther detailsWhat to do
SUBSCRIPTION_ADDON_FOLLOWS_PARENT_GRIDA change to the add-on's cadence, billing direction or send offset, a pause or resume, a merge, a billing-anchor reset, or a rebuild of an add-on that has not billed yet.kind (the change requested) on a timeline change, field where one field was refused; a merge or a rebuild carries neitherCancel the add-on and add it again with the new setup.
SUBSCRIPTION_ADDON_DETAILS_FOLLOW_PARENTAn update to a billing detail of the add-on: payment terms, department, contact, send offset, payment providers, bank details, extra invoice recipients, automatic card billing or payment-method mode.fieldChange the detail on the parent subscription. The add-on follows it.
SUBSCRIPTION_ADDON_IMMEDIATE_PRORATION_NOT_ALLOWEDA change to the add-on asked to invoice its proration immediately.kindLeave the proration behaviour unset. The change is settled on the parent's next invoice.
SUBSCRIPTION_ADDON_PREPAID_COMMITMENT_NOT_SUPPORTEDA change would give the add-on a prepaid usage commitment, in the request or on the plan it is replaced with.kind, planId when the plan carries itUse a plan without a commitment for the add-on.
SUBSCRIPTION_ADDON_REFUND_LAST_INVOICE_NOT_SUPPORTEDA cancellation of an add-on, or of a subscription that has add-ons and bills on its own invoice, asked to credit the whole last invoice. That invoice carries the add-ons' lines beside the parent's. A member of a billing group answers SUBSCRIPTION_BILLING_GROUP_REFUND_LAST_INVOICE_NOT_SUPPORTED instead, add-ons or not.kindCancel with a prorated refund or with no refund.
SUBSCRIPTION_ADDON_PARENT_MISMATCHThe subscription added as an add-on differs from its parent in customer, currency, billing direction, revenue deferral or Leverandørservice collection. The first difference is reported.field (customerId, currency, billingDirection, deferRevenue or collectViaLeverandorservice), value (the add-on's), parentValue (the parent's)Align the field on the add-on, or add a subscription that already matches.
Last modified on November 6, 2026
Versioning & Deprecation
On this page
  • The error envelope
  • Validation errors (400)
  • Status codes
  • Request IDs
  • Error codes are stable
JSON