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

HTTP statusMeaning hereLikely causeCorrective action
400 Bad RequestMalformed requestRarely used directly — most bad input surfaces as 422Check request shape against the endpoint's documented body
401 UnauthorizedMissing/invalid/expired bearer token; also used for webhook signature failuresToken not sent, expired (>7 days), or revoked; or a billing webhook with a bad/missing HMAC signatureRe-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 RequiredSubscription lockoutTenant's subscription lapsed/absent and the request is a write, not a */billing/* routeSubscribe or resolve billing via POST billing/subscribe; reads still work
403 ForbiddenPermission denial, or a feature-gate denialCaller's effective permissions don't include the route's required permission:<key>; or feature:<key> not entitled on the planGrant the permission (Team & Access) or upgrade plan. Response body includes the specific permission/feature key that was missing
404 Not FoundResource doesn't exist, OR belongs to another tenant, OR country:<ISO2> gate mismatchUUID 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 countryVerify the UUID belongs to the active tenant (X-Tenant header); verify the tenant's country supports the feature
409 ConflictNo active tenant contextX-Tenant header missing or doesn't match an active membershipSelect/send a valid tenant id the user actually belongs to
422 Unprocessable EntityValidation failureInline $request->validate([...]) rules failedResponse errors object lists per-field messages; fix and resubmit
429 Too Many RequestsRate limit exceededGlobal 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 capBack off and retry after the window; check Retry-After if present
500 Internal Server ErrorUnhandled exceptionApplication bug or unexpected external-service failureCheck 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; includes addon_available) 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) — both audit-logged with tenant/user/plan/limit/route.

See API Authentication for the auth-specific flow and Security for the underlying middleware implementations.