Source: equa-server/modules/auth/src/responses.ts (authErrorKeys, lines 32-45)
Error Codes
The Equa API uses standard HTTP status codes to indicate the outcome of requests. Error responses include a JSON body with a human-readable message.
{
"error": "Description of the error"
}
Some validation errors from vineyard-lawn may include additional detail:
{
"error": "Bad Request",
"message": "Invalid value for field 'email'"
}
Status Codes
200 OK
The request succeeded. The response body contains the requested data.
201 Created
A new resource was successfully created (used on some POST endpoints).
400 Bad Request
The request body is malformed, missing required fields, or contains invalid values. Check the request schema and ensure all required parameters are provided.
Common causes:
- Missing required fields in the request body
- Invalid UUID format for path parameters
- Invalid email format
- Validation errors from vineyard-lawn request schemas
401 Unauthorized
The request lacks a valid session. This occurs when:
- No session cookie is present
- The session has expired (sessions last ~42 minutes with rolling renewal, configurable via
API_SESSION_MAX_AGE)
- The session was invalidated (e.g., after logout)
Resolution: Re-authenticate via one of the login endpoints.
403 Forbidden
The authenticated user does not have permission to perform this action. The Equa API uses role-based access control (RBAC) through vineyard-lawn’s requires declarations.
Common causes:
- Attempting to edit a cap table without
canEditCapTable permission
- Accessing organization data without being a member
- Attempting admin operations without site-level admin privileges
- Performing billing operations without
canEditOrganizationBilling permission
Resolution: Verify the user has the required role/permission within the organization. Organization admins can adjust member permissions.
404 Not Found
The requested resource does not exist or the URL path is incorrect.
Common causes:
- Invalid resource UUID
- Accessing a deleted resource
- Incorrect API path
409 Conflict
The request conflicts with the current state of the resource.
Common causes:
- Creating a resource that already exists (e.g., duplicate email address)
- Attempting a state transition that is not allowed
- Concurrent modification conflicts
500 Internal Server Error
An unexpected error occurred on the server. These errors are logged server-side.
Resolution: Retry the request. If the error persists, contact support with the request details and timestamp.
Application Error Keys
In addition to HTTP status codes, authentication-related errors include an application-specific error key in the response body. These keys are defined in modules/auth/src/responses.ts as authErrorKeys and thrown via BadRequest(message, key).
{
"error": "Bad Request",
"key": "invalidCredentials"
}
| Key | Wire Value | HTTP Status | Description | Triggering Endpoint(s) | Client Handling |
|---|
emailUnavailable | emailUnavailable | 400 | Email address is already registered | POST /v1/user (registration) | Show “email already in use” message |
emailNotVerified | emailNotVerified | 400 | Login attempted with an unverified email | POST /v1/user/login | Prompt user to verify their email |
existingActiveTempPassword | existingActiveTempPassword | 400 | A temporary password is already active | POST /v1/user/password/reset | Inform user a reset email was already sent |
invalidCredentials | invalidCredentials | 400 | Wrong email or password | POST /v1/user/login | Show generic “invalid credentials” message |
invalidEmailVerificationCode | invalidEmailVerificationCode | 400 | Verification code is invalid or expired | POST /v1/user/email/verify | Prompt user to request a new code |
invalidTwoFactorToken | invalidTwoFactorToken | 400 | Wrong 2FA TOTP token | POST /v1/user/2fa/verify, POST /v1/user/2fa | Prompt user to re-enter code |
Unauthorized | Unauthorized | 400 | Generic authentication failure | Various auth endpoints | Redirect to login |
recentEmailVerification | recentEmailVerification | 400 | Verification email sent too recently (cooldown) | POST /v1/user/email/verify/send | Show cooldown timer (default 1800s) |
userNotFound | userNotFound | 400 | No user found for the given identifier | POST /v1/user/password/reset | Show generic “if account exists” message |
emailBlacklisted | EmailBlacklisted | 400 | Email address is on the blocklist | POST /v1/user (registration) | Show “email not allowed” message |
domainBlacklisted | DomainBlacklisted | 400 | Email domain is on the blocklist | POST /v1/user (registration) | Show “email domain not allowed” message |
ipLimit | ipLimitReached | 400 | Too many registrations from this IP address | POST /v1/user (registration) | Show rate limit message, suggest trying later |
The key field in the error key name (left column) is the object property name in TypeScript. The Wire Value column shows the actual string sent in the API response key field. For most keys they match, but emailBlacklisted sends "EmailBlacklisted", domainBlacklisted sends "DomainBlacklisted", and ipLimit sends "ipLimitReached".
Server Error Types (from Confluence KnowledgeBase)
Source: Confluence KnowledgeBase — Server Error Types (by Christopher Johnson)
Beyond HTTP status codes and auth error keys, the server defines these application-level error types:
| Error Key | Description |
|---|
databaseMissing | Database connection lost or database does not exist |
resourceNotFound | Attempting to access a resource that does not exist |
unauthorized | User lacks authentication for the requested resource |
invalidFields | Required field missing in the request |
unsupportedFileExtension | Uploaded file type is not supported |
missingFileArgument | File parameter is required but was not provided |
insufficientShares | Operation requires more shares than are available |
Handling Errors
const response = await fetch('https://api.equa.cc/v1/organization', {
credentials: 'include',
})
if (!response.ok) {
const error = await response.json()
switch (response.status) {
case 401:
// Redirect to login
window.location.href = '/login'
break
case 403:
console.error('Permission denied:', error.error)
break
case 400:
console.error('Validation error:', error.error)
break
default:
console.error('API error:', response.status, error.error)
}
}