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

# Error Codes

> Every error code the API returns, the HTTP status it maps to, and when it occurs.

## Error response format

All API errors use the same envelope:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "TOKEN_REUSE_DETECTED",
    "message": "This refresh token has already been used. Your session has been revoked for security."
  }
}
```

For validation errors, an additional `details` field maps each invalid field to an array of messages:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "email": ["'Email' must not be empty.", "'Email' is not a valid email address."],
      "password": ["'Password' must be at least 8 characters."]
    }
  }
}
```

## Error code reference

| Code                        | HTTP status | When it occurs                                                                                                                                                                       |
| --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `VALIDATION_ERROR`          | 400         | Request body or query parameters failed FluentValidation rules. The `details` field contains field-level messages.                                                                   |
| `EMAIL_NOT_VERIFIED`        | 400         | A user attempts to log in before verifying their email address.                                                                                                                      |
| `INVALID_TOKEN`             | 400         | A token (email verification, password reset, magic link, invitation) is invalid — malformed, already used, or belongs to a different user.                                           |
| `INVITATION_EMAIL_MISMATCH` | 400         | The logged-in user's email does not match the email the invitation was sent to.                                                                                                      |
| `REGISTRATION_REQUIRED`     | 400         | An invitation acceptance was attempted but no account exists for the invited email. The frontend should redirect to registration.                                                    |
| `DUPLICATE_EMAIL`           | 409         | Registration attempted with an email address that is already in use. The error message is generic to avoid confirming whether an account exists.                                     |
| `INVALID_CREDENTIALS`       | 401         | Login failed due to incorrect email or password.                                                                                                                                     |
| `TOKEN_REUSE_DETECTED`      | 401         | A refresh token that has already been used (revoked) was submitted. The entire token family is revoked. The user must log in again.                                                  |
| `TOKEN_EXPIRED`             | 401         | A refresh token has passed its expiry date (7 days).                                                                                                                                 |
| `UNAUTHORIZED`              | 401         | No valid JWT was provided, or the JWT is malformed, expired, or has an invalid signature.                                                                                            |
| `FORBIDDEN`                 | 403         | The authenticated user does not have the required permission for this action.                                                                                                        |
| `NOT_FOUND`                 | 404         | The requested resource does not exist, or exists but belongs to a different tenant (intentionally indistinguishable).                                                                |
| `CONFLICT`                  | 409         | The operation conflicts with the current state. Examples: creating a tenant with a slug already in use, deleting a role that has members assigned, removing the Owner from a tenant. |
| `PAYMENT_REQUIRED`          | 402         | The requested feature requires an active paid subscription.                                                                                                                          |
| `INTERNAL_ERROR`            | 500         | An unexpected server error occurred. In Production, no details are included. In Development, the exception message and stack trace are included.                                     |

## Handling errors in the frontend

The Axios instance in AspFox's frontend has a response interceptor that normalizes error responses. In your feature code, you can check `error.response.data.error.code`:

```typescript theme={null}
import { useMutation } from '@tanstack/react-query';
import { api } from '@/lib/axios';
import { isAxiosError } from 'axios';

const mutation = useMutation({
  mutationFn: async (data: LoginRequest) => {
    const response = await api.post('/api/v1/auth/login', data);
    return response.data.data;
  },
  onError: (error) => {
    if (isAxiosError(error)) {
      const code = error.response?.data?.error?.code;
      if (code === 'EMAIL_NOT_VERIFIED') {
        setShowResendVerification(true);
      } else if (code === 'INVALID_CREDENTIALS') {
        setFieldError('password', 'Incorrect email or password.');
      }
    }
  },
});
```

TanStack Query surfaces errors in `mutation.error` and `query.error`. The Axios interceptor handles `401 UNAUTHORIZED` globally — it attempts a token refresh and retries the request before surfacing the error to your component.
