Watsi

Watsi API

Error handling

All Watsi API surfaces — Fastify services and Next.js API routes — return a consistent error envelope. This page documents the standard shape, every error code, and recommended retry strategies.

Standard error response

Every error response has the same envelope. The top-level error field is always an object with code and message:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "email is required",
    "details": {}          // optional — extra context when available
  }
}
FieldTypeDescription
error.codestringMachine-readable error code (e.g. UNAUTHORIZED)
error.messagestringHuman-readable description of what went wrong
error.detailsobject?Optional object with field-level errors or extra context

Error codes

StatusCodeDescription
400VALIDATION_ERRORThe request body or query parameters failed validation. Check the message for details.
400BAD_REQUESTThe request is malformed or cannot be processed in its current state.
401UNAUTHORIZEDMissing or invalid authentication credentials. Include a valid API key or session.
403FORBIDDENThe authenticated user does not have permission to perform this action.
404NOT_FOUNDThe requested resource does not exist or is not accessible.
405METHOD_NOT_ALLOWEDThe HTTP method is not supported for this endpoint.
409CONFLICTThe request conflicts with the current state (e.g. duplicate resource).
429RATE_LIMITEDToo many requests. Back off and retry after the Retry-After interval.
500INTERNAL_ERRORAn unexpected server error occurred. Retry with exponential backoff.
502EXTERNAL_API_ERRORAn upstream service (WhatsApp, AI provider) returned an error.
503SERVICE_UNAVAILABLEThe service is temporarily unavailable. Retry after a short delay.

Example responses

Validation error (400)

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "email is required"
  }
}

Unauthorized (401)

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Unauthorized"
  }
}

Not found (404)

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Chat not found"
  }
}

Rate limited (429)

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 30 seconds."
  }
}

Internal error (500)

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  }
}

Retry strategies

CodeRetryable?Strategy
VALIDATION_ERRORNoFix the request parameters before retrying.
UNAUTHORIZEDNoCheck your API key or re-authenticate.
FORBIDDENNoVerify you have the required permissions (admin role, etc.).
NOT_FOUNDNoVerify the resource ID exists.
CONFLICTNoThe resource already exists. Fetch the existing resource or use a different identifier.
RATE_LIMITEDYesWait for the duration in the Retry-After header, then retry. Default limit: 100 req/min per API key.
INTERNAL_ERRORYesRetry with exponential backoff: 1s, 2s, 4s, up to 3 attempts.
EXTERNAL_API_ERRORYesAn upstream provider failed. Retry with exponential backoff.
SERVICE_UNAVAILABLEYesThe service is temporarily down. Retry after 5–30 seconds.

Handling errors in code

const res = await fetch('https://api.watsi.ai/api/v1/conversations', {
  headers: { Authorization: `Bearer ${apiKey}` },
})

if (!res.ok) {
  const body = await res.json()
  const { code, message } = body.error

  switch (code) {
    case 'RATE_LIMITED':
      const retryAfter = res.headers.get('Retry-After') ?? '30'
      await sleep(Number(retryAfter) * 1000)
      return retry()
    case 'UNAUTHORIZED':
      throw new Error('Invalid API key')
    default:
      throw new Error(`API error [${code}]: ${message}`)
  }
}