Skip to main content

Overview

All Nadoo AI API responses follow a consistent envelope format. When an error occurs, the response includes a null data field, an "error" status, the HTTP status code, and a human-readable message describing what went wrong. This predictable structure makes it straightforward to implement centralized error handling in your client.

Error Response Format

Every error response uses this standard structure:
{
  "data": null,
  "status": "error",
  "code": 400,
  "message": "Descriptive error message"
}
FieldTypeDescription
datanullAlways null for error responses
statusstringAlways "error" for error responses
codeintegerThe HTTP status code
messagestringA human-readable description of the error
For comparison, a successful response looks like:
{
  "data": { "id": "app-123", "name": "My Agent" },
  "status": "success",
  "code": 200,
  "message": "OK"
}

HTTP Status Codes

Client Errors (4xx)

The request body or query parameters are invalid. This typically means a required field is missing, a value has the wrong type, or a validation rule was violated.
{
  "data": null,
  "status": "error",
  "code": 400,
  "message": "Invalid request: 'name' field is required"
}
Common causes:
  • Missing required fields in the request body
  • Invalid JSON syntax
  • Field value outside allowed range (e.g., temperature > 2.0)
  • Invalid UUID format for resource IDs
The request lacks valid authentication credentials or the provided token has expired.
{
  "data": null,
  "status": "error",
  "code": 401,
  "message": "Authentication required"
}
Common causes:
  • Missing Authorization header or X-API-Key header
  • Expired JWT access token (refresh it via POST /api/v1/auth/refresh)
  • Revoked API key
  • Invalid token format
The authenticated user does not have permission to perform the requested action on the target resource.
{
  "data": null,
  "status": "error",
  "code": 403,
  "message": "You do not have permission to delete this application"
}
Common causes:
  • Attempting to modify a resource in a workspace where you have a read-only role
  • Accessing an application that belongs to a different workspace
  • API key scoped to a workspace that does not own the resource
  • Public API access disabled for the workspace
The requested resource does not exist or has been deleted.
{
  "data": null,
  "status": "error",
  "code": 404,
  "message": "Application not found"
}
Common causes:
  • Incorrect resource ID
  • Resource was deleted
  • Resource belongs to a different workspace
The request conflicts with the current state of the resource.
{
  "data": null,
  "status": "error",
  "code": 409,
  "message": "A knowledge base with this name already exists in the workspace"
}
Common causes:
  • Duplicate resource name within a workspace
  • Email address already registered
  • Concurrent modification conflict
The uploaded file or request body exceeds the maximum allowed size.
{
  "data": null,
  "status": "error",
  "code": 413,
  "message": "File size exceeds the maximum limit of 100MB"
}
Configuration: The maximum upload size is controlled by the NADOO_MAX_UPLOAD_SIZE environment variable (default: 100 MB).
The request body is syntactically valid JSON but fails semantic validation. This is FastAPI’s default for Pydantic validation errors.
{
  "data": null,
  "status": "error",
  "code": 422,
  "message": "Validation error: temperature must be between 0 and 2"
}
Common causes:
  • Field value violates a Pydantic validation rule
  • Enum value not in the allowed set
  • Nested object fails schema validation
The rate limit has been exceeded. The response includes a Retry-After header indicating how many seconds to wait.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
  "data": null,
  "status": "error",
  "code": 429,
  "message": "Rate limit exceeded. Retry after 12 seconds."
}
Rate limits:
ScopeLimit
IP-based300 requests/min
User-based (authenticated)600 requests/min

Server Errors (5xx)

An unexpected error occurred on the server. These errors are logged with a request ID for troubleshooting.
{
  "data": null,
  "status": "error",
  "code": 500,
  "message": "An internal error occurred. Request ID: req-abc-123"
}
What to do:
  • Retry the request after a short delay
  • If the error persists, contact support with the request ID
  • Check the system health endpoint to verify service status
The server received an invalid response from an upstream service, typically an AI model provider.
{
  "data": null,
  "status": "error",
  "code": 502,
  "message": "AI provider returned an invalid response"
}
Common causes:
  • AI provider (OpenAI, Anthropic, etc.) is temporarily unavailable
  • Invalid API key configured for the model provider
  • Network connectivity issue between Nadoo AI and the provider
The service is temporarily unavailable, usually due to maintenance or overload.
{
  "data": null,
  "status": "error",
  "code": 503,
  "message": "Service temporarily unavailable. Please try again later."
}
The request timed out while waiting for an upstream service (e.g., an LLM call or document processing job).
{
  "data": null,
  "status": "error",
  "code": 504,
  "message": "Request timed out while waiting for AI model response"
}
Common causes:
  • Large prompt or high max_tokens value causing slow LLM response
  • Document processing exceeding the timeout limit
  • Network latency to the AI provider

Streaming Error Events

When an error occurs during SSE or WebSocket streaming, the error is delivered as an event within the stream rather than as an HTTP error response.

SSE Error Event

event: error
data: {"code": "rate_limit", "message": "OpenAI rate limit exceeded. Retrying in 5 seconds."}

WebSocket Error Message

{
  "type": "error",
  "data": {
    "message": "Failed to process message",
    "code": "provider_error"
  }
}

Streaming Error Codes

CodeDescription
rate_limitThe AI provider’s rate limit was exceeded
context_overflowThe input exceeded the model’s context window
tool_errorA tool invocation failed during agent execution
timeoutThe request or LLM call timed out
provider_errorThe AI provider returned an unexpected error
auth_errorAuthentication or authorization failure mid-stream
Streaming errors do not close the connection by default. The server may attempt automatic retries (with exponential backoff) before emitting a terminal error event. Monitor the error event and decide whether to retry or surface the error to the user.

Error Handling Best Practices

Wrap all API calls in a shared error handler that inspects the status and code fields. This avoids duplicating error logic across your application.
async function apiCall(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getToken()}`,
      ...options.headers,
    },
  });

  const body = await response.json();

  if (body.status === 'error') {
    // Handle specific error codes
    switch (body.code) {
      case 401:
        await refreshTokenAndRetry();
        break;
      case 429:
        const retryAfter = response.headers.get('Retry-After');
        await sleep(retryAfter * 1000);
        return apiCall(url, options); // Retry
      default:
        throw new ApiError(body.code, body.message);
    }
  }

  return body.data;
}
JWT access tokens expire after a configurable period (default: 24 hours). When you receive a 401 response, attempt to refresh the token before prompting the user to log in again.
async function refreshTokenAndRetry() {
  const refreshToken = getRefreshToken();

  const response = await fetch('/api/v1/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });

  if (response.ok) {
    const { data } = await response.json();
    setToken(data.access_token);
    // Retry the original request
  } else {
    // Refresh token also expired -- redirect to login
    redirectToLogin();
  }
}
When you receive a 429 response, read the Retry-After header and wait the specified number of seconds before retrying. For burst workloads, implement a token bucket or leaky bucket pattern on the client side to stay under the limit proactively.
ScopeLimitStrategy
IP-based300 req/minThrottle unauthenticated requests
User-based600 req/minQueue and batch API calls
Server error responses (5xx) include a request ID in the message. Save this ID when logging errors on the client side, and provide it when contacting support. This allows the team to trace the exact request through server logs.
Reduce 400 and 422 errors by validating inputs on the client side before sending the request. Match the server’s validation rules:
  • temperature: 0.0 to 2.0
  • max_tokens: positive integer
  • Resource IDs: valid UUID v4 format
  • File uploads: check size and extension against allowed values
SSE and WebSocket errors arrive as in-stream events, not HTTP status codes. Register handlers for the error event type in both SSE (EventSource) and WebSocket (onmessage) listeners. Decide based on the error code whether to retry, reconnect, or surface the error.

Error Response Examples

Validation Error (422)

POST /api/v1/applications
Content-Type: application/json

{
  "name": "",
  "type": "invalid_type"
}
{
  "data": null,
  "status": "error",
  "code": 422,
  "message": "Validation error: 'name' must not be empty; 'type' must be one of: chat, workflow, channel"
}

Resource Not Found (404)

GET /api/v1/applications/00000000-0000-0000-0000-000000000000
Authorization: Bearer eyJ...
{
  "data": null,
  "status": "error",
  "code": 404,
  "message": "Application not found"
}

Rate Limited (429)

# After exceeding 600 requests/min as an authenticated user
POST /api/v1/chat/completions
Authorization: Bearer eyJ...
HTTP/1.1 429 Too Many Requests
Retry-After: 8
Content-Type: application/json
{
  "data": null,
  "status": "error",
  "code": 429,
  "message": "Rate limit exceeded. Retry after 8 seconds."
}

Next Steps