---
title: "API Error Reference"
description: "Every HTTP status the API returns, what each one means in BareLedger, the body shape to expect, and what to do next."
url: "https://bareledger.app/guides/api-errors"
category: "Developers & API"
updated: "2026-11-06"
publisher: "BareLedger (Syneria Technologies OPC)"
---

# API Error Reference

> Every HTTP status the API returns, what each one means in BareLedger, the body shape to expect, and what to do next.

Errors use standard JSON responses (`{"message": ..., "errors": {...}}` for validation) plus a handful of app-specific status/body conventions noted below.

| HTTP status | Meaning here | Likely cause | Corrective action |
|---|---|---|---|
| **400 Bad Request** | Malformed request | Rarely used directly — most bad input surfaces as 422 | Check request shape against the endpoint's documented body |
| **401 Unauthorized** | Missing/invalid/expired bearer token; also used for webhook signature failures | Token not sent, expired (>7 days), or revoked; or a billing webhook with a bad/missing HMAC signature | Re-authenticate (login/SSO) and resend with a fresh `Authorization: Bearer` header. For webhooks: verify `PADDLE_WEBHOOK_SECRET`/`PAYMONGO_WEBHOOK_SECRET` match the gateway dashboard exactly |
| **402 Payment Required** | Subscription lockout | Tenant's subscription lapsed/absent and the request is a write, not a `*/billing/*` route | Subscribe or resolve billing via `POST billing/subscribe`; reads still work |
| **402 Payment Required** | `EnsureSeatWithinPlan` (`seat_over_limit`) | The organization has more active sign-ins than its plan allows, the 14-day grace has ended, and the caller is one of the newest extra sign-ins; the request is a write | Owner upgrades, adds seats or suspends someone; reads still work |
| **403 Forbidden** | Permission denial, or a feature-gate denial | Caller's effective permissions don't include the route's required `permission:<key>`; or `feature:<key>` not entitled on the plan | Grant the permission (People → Customize access) or upgrade plan. Response body includes the specific permission/feature key that was missing |
| **404 Not Found** | Resource doesn't exist, OR belongs to another tenant, OR `country:<ISO2>` gate mismatch | UUID route-model binding is tenant-scoped — a cross-tenant record 404s rather than 403ing, so as not to confirm its existence; country-gated routes (e.g. PH-only WHT endpoints) also 404 outright for tenants outside the allowed country | Verify the UUID belongs to the active tenant (`X-Tenant` header); verify the tenant's country supports the feature |
| **409 Conflict** | No active tenant context | `X-Tenant` header missing or doesn't match an active membership | Select/send a valid tenant id the user actually belongs to |
| **422 Unprocessable Entity** | Validation failure | Inline `$request->validate([...])` rules failed | Response `errors` object lists per-field messages; fix and resubmit |
| **429 Too Many Requests** | Rate limit exceeded | Global 300/min per user/IP, or a tighter per-route throttle (e.g. login 20/min, register 10/min), or TOTP's independent 5/min cap | Back off and retry after the window; check `Retry-After` if present |
| **500 Internal Server Error** | Unhandled exception | Application bug or unexpected external-service failure | Check `storage/logs` / the configured `LOG_CHANNEL`; correlate via the `X-Request-Id`/`X-Correlation-Id` header echoed on the request |

## App-specific response bodies worth knowing

- **Change-request flow**: several settings/config writes (organization profile, branches, and period generation or status) return **202 Accepted** with a pending `ChangeRequest`, not the applied result — the caller must follow up with `POST change-requests/{id}/confirm` and an emailed OTP before the change takes effect.
- **Generic auth responses**: registration, password-forgot, and email-resend endpoints deliberately return the same generic success response whether or not the email/account exists, to avoid an account-enumeration oracle.
- **Limit-denial bodies**: `402 plan_limit_reached` (user seats — invites, reactivations, onboarding approvals that create a login, employee invite-login; every active sign-in counts, employees included; includes `addon_available`), `402 seat_over_limit` (the caller's own sign-in is beyond the plan's user limit after the 14-day grace, so it is read-only; `upgrade_required: true`) and `402 usage_limit_reached` (monthly transaction cap at the approve/post step; includes `used`, `limit`, `period` — the record's transaction month — and `addon_available`). `plan_limit_reached` and `usage_limit_reached` are audit-logged with tenant/user/plan/limit/route.

See [API Authentication](https://bareledger.app/guides/api-authentication) for the auth-specific flow and [Security](https://bareledger.app/guides/security) for the underlying middleware implementations.
