Skip to main content

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

{
  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 StatusCodeWhen
400VALIDATION_ERRORRequest body, query params, or path params fail Zod validation
401UNAUTHORIZEDMissing, invalid, or expired Bearer token
404NOT_FOUNDResource doesn’t exist or doesn’t belong to the user’s workspace
429RATE_LIMITEDRequest exceeds the global or upload rate limit
500INTERNAL_ERRORUnhandled 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:
{
  "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:
// 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:
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.