#Error Responses
The AiDial API returns structured JSON error responses for application-level failures. The response format varies by endpoint domain because some routers expose domain-specific contracts.
Infrastructure-level failures, such as an upstream proxy rate limit, may return a non-JSON body. Check the Content-Type header before parsing an error response.
#Error Response Shapes
| Endpoint Domain | Shape | Fields |
|---|---|---|
| HTTPException-based endpoints (general) | General | error, status_code |
| FastAPI request validation (422) | Validation | error, details, status_code |
Partner API endpoints using core.error_envelope | Partner | error.code, error.message, error.message_key, error.status, error.request_id, error.details |
| Billing and portal settings endpoints | Domain | code, message, field_errors, block_reason, retry_after_seconds |
| Calls export and transcript-download endpoints | Export/download | error, code, message, messageKey, retry_after_seconds |
| Compliance audit endpoints | Security | error.code, error.message, error.message_key, error.request_id, error.details |
| Health endpoint | Health | status, version, database, secret_service, billing, reports_runtime |
#General Error Response
Used when a route raises HTTPException and the global handler in api.main serializes it. Most error values are strings. Some dependencies pass structured detail objects, in which case error is that object.
| Field | Type | Description |
|---|---|---|
error | string or object | Error description or structured detail |
status_code | integer | HTTP status code (matches response status) |
{
"error": "Error description here",
"status_code": 401
}#Validation Error Response (422)
Returned when FastAPI request parameters or body validation fails and the global validation handler processes the error.
| Field | Type | Description |
|---|---|---|
error | string | Always "Validation error" |
details | array | List of validation error objects |
details[].loc | array | Path to the invalid field (for example, ["query", "page"]) |
details[].msg | string | Human-readable validation message |
details[].type | string | Error type (for example, missing, greater_than_equal) |
status_code | integer | Always 422 |
Some domain routers parse request bodies themselves and return 400 or 422 in their domain error shape instead of this framework validation shape. POST /v1/compliance/audit-events/decisions has a special validation handler that returns a 400 security envelope with error.code set to validation_failed.
#Partner Error Response
Used by partner-facing endpoints that call core.error_envelope, including partner identity/API-key/webhook routes and several partner project, phone-number, knowledge-base, catalog, and call-control routes.
| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code |
error.message | string | Human-readable, non-disclosing message |
error.message_key | string | Localisation key |
error.status | integer | HTTP status code |
error.request_id | string | Correlation identifier |
error.details | object (optional) | Structured details, such as field_errors |
{
"error": {
"code": "not_found",
"message": "Resource not found.",
"message_key": "not_found",
"status": 404,
"request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de"
}
}Partner validation errors use the same envelope with field errors under error.details.field_errors:
{
"error": {
"code": "validation_error",
"message": "Validation failed.",
"message_key": "validation_error",
"status": 422,
"request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de",
"details": {
"field_errors": [
{
"field": "project_id",
"message": "Project is required.",
"code": "required"
}
]
}
}
}#Domain Error Response
Used by billing endpoints and several portal-facing settings/compliance endpoints, such as business hours, call limits, compliance copy, transfer settings, tenant settings, notification preferences, MFA lifecycle, IP allowlist, retention policy, and deletion requests.
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code |
message | string | Human-readable error description |
field_errors | object (optional) | Field-level validation errors |
block_reason | string (optional) | Reason a settings update is blocked |
retry_after_seconds | integer (optional) | Seconds to wait before retrying |
{
"code": "validation_failed",
"message": "Request validation failed.",
"field_errors": {
"client_id": "client_id is required."
}
}Billing 429 responses include a Retry-After response header. MFA lifecycle rate-limit responses include both Retry-After and retry_after_seconds.
#Export/Download Error Response
Used by POST /v1/calls/export, GET /v1/calls/{call_id}/transcript-download, and GET /v1/calls/{call_id}/transcript/pdf.
| Field | Type | Description |
|---|---|---|
error | string | Error category (for example, rate_limited, content_too_large, not_found, bad_request) |
code | string | Machine-readable error code |
message | string | Human-readable description |
messageKey | string | Localisation key |
retry_after_seconds | integer (optional) | Seconds to wait before retrying |
field_errors | array (optional) | Field-level validation errors on request-validation failures |
{
"error": "rate_limited",
"code": "EXPORT_THROTTLED",
"message": "Export throttled. Please wait and try again.",
"messageKey": "calls.export.messages.throttled",
"retry_after_seconds": 60
}#Security Error Response
Used by compliance audit endpoints that return SecurityErrorEnvelope.
| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code |
error.message | string | User-safe non-disclosing message |
error.message_key | string | Localisation key |
error.request_id | string | Correlation identifier |
error.details | object (optional) | Structured details for validation contexts |
{
"error": {
"code": "not_found",
"message": "Requested resource not found.",
"message_key": "security.not_found",
"request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de"
}
}#Health Error Response
GET /v1/health does not use the general error envelope. It returns the health payload with status 200 when healthy and 503 when unhealthy. The database block checks the local API metadata store and includes database.db_writable when the database check succeeds.
Overall health is currently driven by the local API metadata store and secret-service checks. billing and reports_runtime are included for operational visibility and do not currently determine whether the endpoint returns 200 or 503.
{
"status": "unhealthy",
"version": "1.0.0",
"database": {
"status": "error",
"message": "Connection refused"
},
"secret_service": {
"status": "connected",
"host": "secret-service"
},
"billing": {
"status": "error",
"code": "billing_configuration_invalid",
"message": "Billing Stripe key must be a restricted key."
},
"reports_runtime": {
"status": "degraded",
"schedules_table": false,
"deliveries_table": false
}
}#Authentication and Scope Semantics
Protected routes using the shared authentication dependency resolve credentials in this order:
X-API-KeyAuthorization: Bearer <jwt>
Both modes resolve to an AuthContext. Bearer authentication requires ZITADEL_ISSUER, ZITADEL_AUDIENCE, and ZITADEL_JWKS_URL; these settings are required and have no fallback defaults.
Missing, invalid, revoked, or expired credentials return 401. If a valid bearer token has no active portal assignment, the API returns 404 to avoid tenant enumeration. Out-of-scope resources also return 404, not 403.
Analytics and call-data access is routed by the strict (client_id, data_env) mapping in the tenant routing map. A missing route is server misconfiguration and returns 500. A routed host, tunnel, or database outage returns 503.
#PII Failure Mode
PII-sensitive call responses respect the client-level PII flags in aidial_clients. Phone and transcript decryption goes through server-side decryption helpers in the server-side decryption layer. When permissions are missing or decryption/retrieval fails, the API fails closed by redacting or omitting PII. Raw encrypted database fields such as *_encrypted, ciphertext, nonces, and wrapped keys are not partner response fields.
#Status Code Examples
#400 Bad Request
Domain routers use 400 for invalid JSON, invalid filters, stale settings state, or domain validation failures.
{
"code": "validation_failed",
"message": "Request validation failed.",
"field_errors": {
"updates": "Duplicate categories are not allowed."
}
}#401 Unauthorized
Missing, invalid, revoked, or expired API key or bearer token.
{
"error": "Missing authentication. Provide X-API-Key or Authorization: Bearer token.",
"status_code": 401
}#403 Forbidden
Valid authentication, but the requested operation is explicitly forbidden. Tenant scope failures normally return 404 instead.
{
"error": "Session management requires portal authentication.",
"status_code": 403
}Authenticated requests blocked by the tenant's own IP allowlist use a domain response:
{
"code": "ip_allowlist_restricted",
"message": "Access restricted."
}#404 Not Found
Resource does not exist, is outside your client scope, or is hidden to avoid tenant enumeration.
Anti-enumeration behaviour: Accessing resources outside your client scope returns
404 Not Found(not403 Forbidden). This prevents discovery of resources belonging to other organisations.
{
"error": "Not found",
"status_code": 404
}#409 Conflict
The requested operation conflicts with current state, such as duplicate idempotency use, an export already in progress, or a stale revision.
{
"error": {
"code": "conflict",
"message": "A matching request with this Idempotency-Key is already in progress or has already completed.",
"message_key": "conflict",
"status": 409,
"request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de"
}
}#413 Payload Too Large
The request or generated content exceeds a route-specific limit.
{
"error": "content_too_large",
"code": "DOWNLOAD_PDF_TOO_LARGE",
"message": "Transcript is too large for PDF generation. Please use TXT format instead.",
"messageKey": "calls.transcriptDownload.messages.pdfTooLarge"
}#422 Unprocessable Entity
Framework request validation failed. Check the details array for specific field errors.
{
"error": "Validation error",
"details": [
{
"type": "greater_than_equal",
"loc": ["query", "page"],
"msg": "Input should be greater than or equal to 1"
}
],
"status_code": 422
}#429 Too Many Requests
Rate limit exceeded. See Rate Limiting for handling guidance.
HTTPException-based 429:
{
"error": "Rate limit exceeded",
"status_code": 429
}Partner 429 (with Retry-After header):
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry shortly.",
"message_key": "rate_limited",
"status": 429,
"request_id": "9b603a24-4d95-4d0b-a865-20c0f8a2c5de"
}
}Billing 429 (with Retry-After header):
{
"code": "billing_provider_rate_limited",
"message": "Billing provider rate limit exceeded."
}Export/download 429 (with retry timing in body):
{
"error": "rate_limited",
"code": "EXPORT_THROTTLED",
"message": "Export throttled. Please wait and try again.",
"messageKey": "calls.export.messages.throttled",
"retry_after_seconds": 60
}#500 Internal Server Error
An unexpected server error or server-side misconfiguration occurred. Contact support if this persists.
Examples include missing required bearer-auth configuration or a missing (client_id, data_env) route in the tenant routing map.
{
"error": "Internal server error",
"status_code": 500
}#503 Service Unavailable
A dependency is temporarily unavailable. Retry with exponential backoff.
Examples include authentication dependency lookup failures, routed analytics host/tunnel/database outages, secret-service health failures, and domain-service outages.
{
"error": "Data host is currently unavailable. Please try again later.",
"status_code": 503
}#Related Pages
- Authentication — API key and bearer setup
- Rate Limiting — handling 429 responses
- Health — health response format
- API Reference — endpoint index