> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> Common HTTP error codes returned by the Equa API and how to handle them

> **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 Response Format

```json theme={null}
{
  "error": "Description of the error"
}
```

Some validation errors from vineyard-lawn may include additional detail:

```json theme={null}
{
  "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](/api/authentication).

### 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)`.

```json theme={null}
{
  "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 |

<Note>
  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"`.
</Note>

***

## Server Error Types (from Confluence KnowledgeBase)

> **Source:** Confluence KnowledgeBase — [Server Error Types](https://equa.atlassian.net/wiki/spaces/KNOWLEDGEB/pages/35684648/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

```javascript theme={null}
const response = await fetch('/api/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)
  }
}
```
