Errors and error codes
Every error returns the same JSON envelope:
{ "code": "not_found", "message": "resource not found" }Branch on code, never on message. Codes are a stable contract; messages
are human-readable text that may be reworded or translated. Some messages —
notably validation_failed — carry detail about the specific field that failed,
so they are worth surfacing to a user but not worth parsing.
Authentication and authorization
Section titled “Authentication and authorization”| Code | Status | Meaning |
|---|---|---|
unauthorized |
401 | No valid credential was presented |
invalid_credentials |
401 | The email or password is wrong |
invalid_token |
401 | The token is malformed or expired |
token_reuse |
401 | A refresh token was replayed; the session has been revoked |
webhook_unauthorized |
401 | Webhook signature verification failed |
forbidden |
403 | Authenticated, but lacking the required permission |
csrf_failed |
403 | The X-CSRF-Token header is missing or does not match the cookie |
account_deactivated |
403 | The account exists but has been deactivated |
token_reuse is worth handling deliberately. It means a refresh token was used
twice, which indicates the token was captured. Aletheia revokes the whole session
rather than issuing new tokens. Send the user back to login.
Validation and not found
Section titled “Validation and not found”| Code | Status | Meaning |
|---|---|---|
validation_failed |
400 | The request body or parameters are invalid. The message names the problem |
invalid_state |
400 | An install or callback state token is invalid or expired |
unsafe_base_url |
400 | The supplied base URL is not permitted |
not_found |
404 | The resource does not exist, or is not visible to your organization |
not_found is also what you get when a resource exists but belongs to another
organization. Aletheia does not distinguish the two, because doing so would let a
caller probe for the existence of other tenants’ resources.
Conflicts
Section titled “Conflicts”| Code | Status | Meaning |
|---|---|---|
email_taken |
409 | An account with this email already exists |
slug_taken |
409 | The organization slug is already in use |
name_taken |
409 | A connection with this name already exists |
account_exists |
409 | This AWS account is already connected |
connection_exists |
409 | A source-control connection already exists for this organization |
scan_conflict |
409 | An equivalent scan is already queued or running |
invitation_invalid |
409 | The invitation has been used, revoked, or has expired |
last_admin |
409 | The action would leave the organization with no active platform administrator |
conflict |
409 | A resource with this identifier already exists |
scan_conflict is not a failure to handle defensively — it means Aletheia is
already doing what you asked. Treat it as success unless you specifically need a
fresh scan.
Rate limiting and availability
Section titled “Rate limiting and availability”| Code | Status | Meaning |
|---|---|---|
rate_limited |
429 | Too many attempts. Back off and retry |
provider_unavailable |
503 | The source-control provider is not configured |
internal_error |
500 | An unexpected failure. The details are logged server-side, not returned |
internal_error never carries diagnostic detail in the response, deliberately —
error text is a common way for internals to leak. If you hit one repeatedly,
the X-Request-Id header on the response identifies the exact request in the
server logs, so quote it when asking for help.
Request identifiers
Section titled “Request identifiers”Every response carries an X-Request-Id header. Log it. It is the fastest way to
correlate something you saw with what the server recorded.
Where to go next
Section titled “Where to go next”- Authentication for how to avoid the 401s.
- API for the base URL and versioning.