Error codes
Auth Service uses a structured error response format for the errors raised by its own API routes (/api/* routes, except the BetterAuth handler):
{
"error": {
"code": "APP_002",
"message": "Application not found",
"details": {}
}
}
details is included only when the error carries some (for example validation errors, or balance and required on WAL_002). A route may replace the default message with a more specific one (for example AUTH_011 with "Only superadmins can change machine scopes", or SRV_004 with the message reported by the gateway); the tables below list the default messages of the registry in src/errors.ts.
Other responses you may meet:
- A request body or query that fails validation answers
400with codeAPP_001, message "Validation error" and the flattened validation issues indetails. - Any other unhandled error answers
500 SRV_001"Internal server error". - An unknown path answers
404with{ "error": { "code": "SRV_001", "message": "Not found" } }. - The BetterAuth handler under
/api/auth/answers with its own error bodies, and the OAuth endpoints with the OAutherror/error_descriptionformat. - The Stripe webhook answers
503with codeSRV_002and the message "Stripe is not configured" when Stripe is not set up.
Codes are never renumbered or reused: a gap in a domain is a retired code. Every code in the registry is raised by at least one route or service.
Authentication (AUTH_)
| Code | HTTP | Default message |
|---|---|---|
AUTH_001 | 401 | Missing or invalid authentication token |
AUTH_009 | 400 | Password too weak |
AUTH_011 | 403 | Insufficient permissions |
AUTH_012 | 400 | Invalid, expired or already used registration invitation |
AUTH_013 | 409 | An account already exists for this email |
AUTH_014 | 403 | This invitation was issued for a different email address |
AUTH_015 | 403 | Verify your email address before accepting this invitation |
AUTH_016 | 404 | Session not found |
Application (APP_)
| Code | HTTP | Default message |
|---|---|---|
APP_001 | 400 | Invalid application data |
APP_002 | 404 | Application not found |
APP_003 | 409 | Application slug already exists |
APP_005 | 403 | User does not have access to this application |
APP_006 | 403 | Social provider not enabled for this application |
APP_007 | 403 | System applications cannot be deleted |
Permissions (PERM_)
| Code | HTTP | Default message |
|---|---|---|
PERM_001 | 400 | Invalid permission format |
PERM_002 | 404 | Role not found |
PERM_003 | 404 | Permission not found |
PERM_004 | 409 | Role name already exists in this application |
PERM_005 | 409 | Permission already defined |
PERM_006 | 400 | Cannot delete a role that is assigned to users |
Subscriptions (SUB_)
| Code | HTTP | Default message |
|---|---|---|
SUB_001 | 404 | Subscription plan not found |
SUB_002 | 409 | User already has an active subscription |
SUB_003 | 400 | Plan has active subscribers, cannot delete |
SUB_005 | 403 | Self-service plan changes require Stripe to be configured |
SUB_007 | 409 | A paid subscription already exists for this application: change its plan instead |
SUB_008 | 404 | This user has no Stripe subscription for this application |
Consumption (CONS_)
| Code | HTTP | Default message |
|---|---|---|
CONS_001 | 400 | Invalid key format |
CONS_002 | 400 | Value must be a finite number |
CONS_003 | 404 | User and application combination not found |
CONS_004 | 403 | Caller not authorized (requires client_credentials) |
CONS_005 | 400 | Invalid consumption identifier |
Billing (BIL_)
| Code | HTTP | Default message |
|---|---|---|
BIL_001 | 503 | Billing is not configured |
BIL_002 | 400 | Invalid billing configuration |
BIL_003 | 403 | Metered usage limit reached |
BIL_004 | 502 | The payment provider rejected the request |
BIL_003 is raised when a metered consumption report would cross the plan price's hard cap. BIL_004 is raised when Stripe refuses a plan change or a cancellation change, or returns no secure checkout or portal URL.
Wallet (WAL_)
| Code | HTTP | Default message |
|---|---|---|
WAL_001 | 404 | Wallet account not found |
WAL_002 | 402 | Insufficient credit balance |
WAL_003 | 409 | Idempotency key already used with different parameters |
WAL_004 | 400 | Invalid wallet amount |
WAL_005 | 400 | Invalid pagination cursor |
WAL_006 | 503 | Online payment is not configured |
WAL_007 | 503 | Usage pricing is not configured |
Users (USR_)
| Code | HTTP | Default message |
|---|---|---|
USR_001 | 404 | User not found |
USR_002 | 400 | Cannot delete the last superadmin |
USR_003 | 400 | Invalid user data |
Organizations (ORG_)
| Code | HTTP | Default message |
|---|---|---|
ORG_001 | 404 | Organization not found |
ORG_002 | 409 | Organization slug already exists |
ORG_003 | 400 | Invalid organization data |
ORG_004 | 404 | Member not found in organization |
ORG_005 | 409 | User is already a member of this organization |
Server (SRV_)
| Code | HTTP | Default message |
|---|---|---|
SRV_001 | 500 | Internal server error |
SRV_002 | 503 | Service temporarily unavailable |
SRV_003 | 500 | Database error |
SRV_004 | 502 | LLM gateway unavailable |
SRV_004 is raised, with the message reported by the gateway client, when saving an application or creating or renaming a plan requires gateway teams that could not be created; nothing is saved in that case.
Outbound email (MAIL_)
| Code | HTTP | Default message |
|---|---|---|
MAIL_002 | 502 | Mail transport failure |
MAIL_003 | 429 | Too many email requests, please retry later |
Rate limiting (RATE_)
| Code | HTTP | Default message |
|---|---|---|
RATE_001 | 429 | Too many authentication attempts, please retry later |
RATE_001 (10 requests per minute and IP on the sign-in, sign-up, password-reset and two-factor verification paths of /api/auth/, and 60 per minute on /api/auth/device when device authorization is enabled) and MAIL_003 (5 per minute and IP on the email-sending paths of /api/auth/) are returned by the /api/auth/ rate-limit hook. Other rate-limited routes (the global limit of 600 requests per minute per IP, the registration routes) answer with the framework's default 429 body.