error.code is stable and machine-readable. Domain failures use specific codes where the client can take different action, for example invalid_credentials, invoice_already_generated, payment_exceeds_invoice_balance, payment_already_reversed, organization_suspended, and room_has_active_lease.
Login credential failures for POST /api/v1/auth/login and POST /api/v1/tenant/auth/login return HTTP 401 with error.code = "invalid_credentials". Missing or invalid bearer-token authentication on protected routes continues to use unauthorized.
Localization
error.code (top-level) and the validation fields[].path / fields[].code are identical across locales — branch on those, never on message text.
The human-readable error.message is localized to the request’s Accept-Language header. Supported locales are vi (default) and en; when no usable Accept-Language is sent, the API responds in Vietnamese. The negotiated locale is returned in the Content-Language response header. A code with no catalog entry falls back to its English message. The JSON examples on this page show the en text for readability.
Common status codes:
400bad request401unauthorized403forbidden404not found405method not allowed409conflict415unsupported media type429too many requests500internal error