WarmblyDocs

Error codes

Reference for all API error codes and their meanings.

The Warmbly API uses standard HTTP status codes and returns structured error responses in JSON format.

Error response format

All errors follow this structure:

{
  "error": "Error Type",
  "message": "Human-readable description of what went wrong.",
  "code": "machine_readable_code",
  "request_id": "req_or_uuid_for_support"
}

error and message are for people. Client logic should use code, HTTP status, and endpoint-specific fields such as retry_after. Include request_id when contacting support.

HTTP status codes

Client errors (4xx)

CodeErrorDescription
400Bad RequestInvalid request syntax or parameters
401UnauthorizedMissing or invalid authentication
402Payment RequiredOut of AI credits (insufficient_credits)
403ForbiddenAuthenticated but lacks permission
404Not FoundResource doesn't exist
409ConflictResource already exists
422UnprocessableValidation failed
429Too Many RequestsRate limit or AI usage cap exceeded (rate_limit_exceeded, usage_cap_exceeded)

Server errors (5xx)

CodeErrorDescription
500Internal Server ErrorUnexpected server error
501Not ImplementedFeature not available
503Service UnavailableService temporarily down

Error details

400 Bad Request

Returned when the request cannot be processed due to invalid syntax.

Common causes:

  • Invalid JSON in request body
  • Missing required fields
  • Invalid field types
  • Values outside allowed ranges

Example:

{
  "error": "Bad Request",
  "message": "invalid request body",
  "code": "bad_request",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Check that your JSON is valid
  • Verify all required fields are present
  • Ensure field values match expected types

401 Unauthorized

Returned when authentication fails.

Common causes:

  • Missing Authorization header
  • Invalid API key format
  • Expired API key
  • Revoked API key

Example:

{
  "error": "Unauthorized",
  "message": "Token not found.",
  "code": "unauthorized",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Include the Authorization: Bearer wmbly_... header
  • Verify your API key is correct
  • Check if your key has expired or been revoked
  • Generate a new key if necessary

One 401 variant is not about API keys at all. setup_token_invalid is returned by the first-run claim endpoint when the setup link is invalid, already used or expired. Print a new one with warmblyctl setup-link, described in first run.

402 Payment Required

Returned when an AI action is requested but the organization is out of credits. The response carries the stable code insufficient_credits.

{
  "error": "Payment Required",
  "message": "You're out of AI credits. Add more to keep using the assistant.",
  "code": "insufficient_credits",
  "request_id": "req_..."
}

How to fix:

  • Wait for the monthly allowance to reset, or buy a top-up pack (see AI credits)
  • Related: a 429 with code usage_cap_exceeded means a short-term AI usage cap was hit; retry later

403 Forbidden

Returned when authenticated but lacking necessary permissions.

Common causes:

  • API key lacks required permission
  • Request IP not in allowlist
  • Email account not in allowlist
  • Organization access restricted

Example:

{
  "error": "Forbidden",
  "message": "You don't have access to this feature.",
  "code": "forbidden",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Check your API key's permissions
  • Verify IP restrictions if configured
  • Request additional permissions if needed

Registration and invitation refusals

Signup and invitation refusals carry their own code, so a client can branch on the specific condition instead of matching on text. They describe the deployment's policy and never say anything about whether a given address exists.

codeStatusMeaning
registration_invite_only403The server runs DISABLE_REGISTRATION=invite_only. Creating an account requires an invitation link, which carries the token that permits the signup
registration_closed403The server runs DISABLE_REGISTRATION=true. Signups are off and invitations do not override it
invitation_invalid403The invitation is expired, cancelled, already used, or was issued for a different email address
setup_already_complete403The first-run claim was attempted on an instance that already has an account
{
  "error": "Forbidden",
  "message": "This server is invite only. Ask an administrator to invite you, then open the link in the invitation to create your account.",
  "code": "registration_invite_only",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix: on a self-hosted deployment these are configuration, not faults. See accounts and access.

404 Not Found

Returned when the requested resource doesn't exist.

Common causes:

  • Invalid resource ID
  • Resource was deleted
  • Resource belongs to different organization
  • Typo in endpoint URL

Example:

{
  "error": "Not Found",
  "message": "Resource not found.",
  "code": "not_found",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Verify the resource ID is correct
  • Check that the resource hasn't been deleted
  • Ensure you're using the correct endpoint

409 Conflict

Returned when the request conflicts with existing data.

Common causes:

  • Trying to create a resource that already exists
  • Duplicate unique values

Example:

{
  "error": "Conflict",
  "message": "resource already exists",
  "code": "conflict",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

422 Unprocessable

Returned when validation fails on the request data.

Common causes:

  • Invalid email format
  • String exceeds maximum length
  • Number outside valid range
  • Invalid enum value

Example:

{
  "error": "Unprocessable",
  "message": "validation failed",
  "code": "unprocessable",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

500 Internal Server Error

Returned when an unexpected error occurs on the server.

Example:

{
  "error": "Internal Server Error",
  "message": "Something went wrong.",
  "code": "internal_error",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Retry the request after a short delay
  • If persistent, contact support with request details

One internal_error variant is worth distinguishing. When an authentication endpoint cannot send its email, the message names that specifically rather than reporting a generic fault:

{
  "error": "Internal Server Error",
  "message": "We couldn't send the email. If you administer this server, check the mail transport configuration.",
  "code": "internal_error",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

On a self-hosted deployment that means MAIL_TRANSPORT and the SMTP_ variables. See self-hosting.

503 Service Unavailable

Returned when the service is temporarily unavailable.

Example:

{
  "error": "Service Unavailable",
  "message": "service unavailable",
  "code": "service_unavailable",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Wait and retry with exponential backoff
  • Check status page for incidents

mailbox_provider_not_configured

A 503 whose code is mailbox_provider_not_configured is not transient and retrying will not help. It means the deployment has no OAuth client for the mailbox provider the request asked for, which only happens on a self-hosted install.

{
  "error": "Service Unavailable",
  "message": "Gmail is not configured on this deployment. Set BOX_GOOGLE_CLIENT_ID and BOX_GOOGLE_CLIENT_SECRET in your .env, then restart.",
  "code": "mailbox_provider_not_configured",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

How to fix:

  • Set BOX_GOOGLE_CLIENT_ID and BOX_GOOGLE_CLIENT_SECRET, or BOX_OUTLOOK_CLIENT_ID and BOX_OUTLOOK_CLIENT_SECRET, in the .env at your install root, then restart
  • Or connect the mailbox over SMTP and IMAP instead, which needs no configuration
  • Full walkthrough: connect mailboxes

Error handling best practices

Implement retry logic

For transient errors (5xx, 429), implement exponential backoff:

async function requestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.ok) {
        return response.json();
      }

      // Don't retry client errors (4xx) except rate limits
      if (response.status >= 400 && response.status < 500 && response.status !== 429) {
        throw new Error(`Client error: ${response.status}`);
      }

      // Retry server errors and rate limits
      if (attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
    }
  }
}

Parse error responses

Always parse and handle error responses:

async function apiRequest(url, options) {
  const response = await fetch(url, options);

  if (!response.ok) {
    const error = await response.json();
    throw new ApiError(response.status, error.error, error.message);
  }

  return response.json();
}

class ApiError extends Error {
  constructor(status, type, message) {
    super(message);
    this.status = status;
    this.type = type;
  }
}

Rate limiting

When you exceed rate limits, you'll receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Please retry after 60 seconds.",
  "code": "rate_limit_exceeded",
  "request_id": "4bbbd1b2-8f86-47dd-8a7f-9476501ad20e"
}

Use the Retry-After header to determine when to retry.

See also

On this page