Error Handling
All Thallus API errors return a consistent JSON structure with an error type, human-readable message, and optional details. This page covers the error format, status codes, and when to retry.
Error response format
Every error response follows this structure:
{
"error": "NotFoundError",
"message": "Document not found",
"details": null
}
| Field | Type | Description |
|---|---|---|
error |
string | Error type name (e.g., NotFoundError, ValidationError) |
message |
string | Human-readable explanation of what went wrong |
details |
object or null | Additional context when available |
HTTP status codes
| Code | Meaning | When returned |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource successfully created (registration, new workflow, new document) |
| 400 | Bad Request | Invalid request body, malformed input, business logic violation |
| 401 | Unauthorized | Missing or invalid authentication credentials |
| 403 | Forbidden | Valid credentials but insufficient permissions |
| 404 | Not Found | Requested resource does not exist |
| 409 | Conflict | Resource already exists (duplicate email, active task already running) |
| 422 | Unprocessable Entity | Request body fails schema validation |
| 429 | Too Many Requests | Rate limit exceeded — check Retry-After header |
| 500 | Internal Server Error | Unexpected server error |
Validation errors (422)
When the request body fails schema validation, the response includes a details array describing each field error:
{
"error": "validation_error",
"message": "Invalid request data",
"details": [
{
"type": "value_error",
"loc": ["body", "email"],
"msg": "Invalid email address",
"input": "not-an-email"
}
]
}
Each entry in the details array contains:
| Field | Description |
|---|---|
type |
Error category (value_error, type_error, missing) |
loc |
Path to the invalid field (e.g., ["body", "email"]) |
msg |
Human-readable error message for this field |
input |
The value that was provided |
Multiple fields can fail validation simultaneously — always iterate the full details array.
Authentication errors (401)
A 401 response means your credentials are missing, expired, or invalid.
| Scenario | Error type | Message |
|---|---|---|
| No credentials provided | AuthenticationError |
Not authenticated |
| Expired JWT | AuthenticationError |
Token has expired |
| Invalid JWT signature | AuthenticationError |
Invalid token |
| Invalid API key | AuthenticationError |
Invalid API key |
| Revoked API key | AuthenticationError |
API key has been revoked |
| Expired API key | AuthenticationError |
API key has expired |
Handling 401 errors: If using JWT authentication, attempt a token refresh before retrying. If the refresh also fails, redirect to login. If using an API key, verify the key is active in your dashboard.
Permission errors (403)
A 403 response means your identity is recognized but you lack permission for the requested action.
| Scenario | Error type | Message | Details |
|---|---|---|---|
| Insufficient role | PermissionError |
Admin access required | — |
| Wrong organization | PermissionError |
Not authorized for this organization | — |
| BYOK not configured | byok_config_required |
Model configuration required | {"redirect": "/settings?tab=models"} |
| Feature not available | PermissionError |
Feature requires Pro plan | — |
When you receive a BYOK configuration error, the details.redirect field indicates where the user should configure their model provider. See Model Configuration for setup details.
Rate limit errors (429)
Rate limit responses always include a Retry-After header with the number of seconds to wait:
Retry-After: 60
{
"detail": "Too many requests. Please try again later."
}
Always respect the Retry-After value. See Rate Limiting for the specific limits and lockout rules.
Retry guidance
Not all errors should be retried. Use the status code to determine the correct strategy.
Transient errors — retry with backoff
| Code | Strategy |
|---|---|
| 429 | Wait for Retry-After seconds, then retry once |
| 500 | Exponential backoff: 1s, 2s, 4s, up to 30s max. Stop after 3 attempts |
| 502/503 | Same as 500 — the server is temporarily unavailable |
Permanent errors — do not retry
| Code | What to do |
|---|---|
| 400 | Fix the request body and resubmit |
| 401 | Refresh your token or check your API key |
| 403 | Check your user role or plan tier |
| 404 | Verify the resource ID exists |
| 409 | The resource already exists — fetch it instead of creating |
| 422 | Fix the validation errors listed in details |
Common error scenarios
| Scenario | Code | Error | Resolution |
|---|---|---|---|
| Send chat without auth header | 401 | AuthenticationError |
Add Authorization or X-API-Key header |
| JWT expired mid-session | 401 | AuthenticationError |
Refresh token and retry the request |
| Upload to nonexistent collection | 404 | NotFoundError |
Verify collection ID before uploading |
| Create workflow exceeding plan limit | 403 | PermissionError |
Upgrade plan or delete unused workflows |
| Start a second concurrent task | 409 | Conflict |
Wait for the active task to complete |
| Submit invalid connection credentials | 400 | ValidationError |
Check hostname, port, and credentials |
| Register with existing email | 409 | Conflict |
Use login instead, or reset password |
| Query data connection while rate limited | 429 | RateLimitError |
Wait for Retry-After and reduce query frequency |
Related pages
- API Authentication — credential types and token refresh flow
- Rate Limiting — rate limit tiers and lockout behavior
- Core API Endpoints — endpoint reference with auth requirements
- Security Headers & CSRF — security middleware and CORS errors