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

# Error Responses

> The standard error response format used by all folksbase API endpoints.

## Overview

Every error response from the folksbase API follows a consistent JSON shape. This makes it straightforward to build error handling in your client — you always know what to expect.

## Error Shape

```typescript theme={"dark"}
{
  code: string;       // Machine-readable error code (e.g., "VALIDATION_ERROR")
  message: string;    // Human-readable description
  details?: unknown;  // Optional additional context (e.g., Zod validation issues)
}
```

The `code` field is stable and safe to use in programmatic checks. The `message` field is for display purposes and may change between releases.

## Error Codes

| HTTP Status | Code               | When                                                             |
| ----------- | ------------------ | ---------------------------------------------------------------- |
| `400`       | `VALIDATION_ERROR` | Request body, query params, or path params fail Zod validation   |
| `401`       | `UNAUTHORIZED`     | Missing, invalid, or expired Bearer token                        |
| `404`       | `NOT_FOUND`        | Resource doesn't exist or doesn't belong to the user's workspace |
| `429`       | `RATE_LIMITED`     | Request exceeds the global or upload rate limit                  |
| `500`       | `INTERNAL_ERROR`   | Unhandled server error                                           |

## Validation Errors

When a request fails Zod validation, the `details` field contains the array of Zod issues. Each issue describes exactly which field failed and why:

```json theme={"dark"}
{
  "code": "VALIDATION_ERROR",
  "message": "Invalid request data",
  "details": [
    {
      "code": "invalid_type",
      "expected": "string",
      "received": "undefined",
      "path": ["email"],
      "message": "Required"
    },
    {
      "code": "invalid_string",
      "validation": "uuid",
      "path": ["id"],
      "message": "Invalid uuid"
    }
  ]
}
```

The `path` array tells you which field caused the error. Nested fields use dot-separated paths (e.g., `["custom_fields", "department"]`).

## Authentication Errors

Auth errors always return `401` with the `UNAUTHORIZED` code. The `message` varies depending on the cause:

```json theme={"dark"}
// Missing or malformed header
{ "code": "UNAUTHORIZED", "message": "Missing or invalid Authorization header" }

// Expired or invalid token
{ "code": "UNAUTHORIZED", "message": "Invalid or expired token" }

// User has no workspace
{ "code": "UNAUTHORIZED", "message": "User has no workspace" }

// Generic auth failure
{ "code": "UNAUTHORIZED", "message": "Authentication failed" }
```

## How Errors Are Handled

The API uses a global error handler middleware. Route handlers don't catch errors themselves — they let exceptions propagate to the middleware, which formats them consistently.

The middleware handles three categories:

1. **Zod errors** — caught by `instanceof ZodError`, returned as `400` with validation details
2. **Auth errors** — detected by message content (`"Unauthorized"` or JWT-related), returned as `401`
3. **Everything else** — logged with stack trace, returned as `500` with a generic message

This means you'll never see inconsistent error shapes across different endpoints. The format is always `{ code, message }` with an optional `details` field.

## Client Error Handling

A simple pattern for handling API errors:

```typescript theme={"dark"}
const response = await fetch('/api/contacts', { ... });

if (!response.ok) {
  const error = await response.json();

  switch (error.code) {
    case 'VALIDATION_ERROR':
      // Show field-level errors from error.details
      break;
    case 'UNAUTHORIZED':
      // Redirect to login or refresh token
      break;
    case 'RATE_LIMITED':
      // Wait and retry (check Retry-After header)
      break;
    default:
      // Show generic error message
  }
}
```

The frontend already handles transient server errors (502, 503, 504) with automatic retry — one retry after a 1-second delay. Client errors (4xx) are never retried automatically.
