Error Handling
Understand API error responses and how to handle them in your integration. The Agoralia API uses standard HTTP status codes and simple error messages.
Overview
The Agoralia API uses standard HTTP status codes and simple error messages. All errors follow FastAPI's default error format:
Error Response Format:
{
"detail": "Error message here"
}
For validation errors (422), the format includes field-level details:
{
"detail": [
{
"loc": ["body", "field_name"],
"msg": "Error message",
"type": "error_type"
}
]
}
HTTP Status Codes
| Code | Meaning | Retry? | Description |
|---|---|---|---|
| 200 | Success | - | Request succeeded |
| 201 | Created | - | Resource created successfully |
| 400 | Bad Request | ❌ | Invalid request format or parameters |
| 401 | Unauthorized | ❌ | Invalid or missing API key |
| 403 | Forbidden | ❌ | API key missing required scope |
| 404 | Not Found | ❌ | Resource not found |
| 409 | Conflict | ❌ | Idempotency key conflict |
| 422 | Validation Error | ❌ | Request validation failed |
| 429 | Rate Limited | ✅ | Too many requests - retry with backoff |
| 500 | Server Error | ✅ | Internal server error - retry later |
| 503 | Service Unavailable | ✅ | Service temporarily unavailable - retry later |
Common Errors
401 Unauthorized
Cause: Invalid, expired, or missing API key.
Response:
{
"detail": "Invalid API key"
}
Other possible messages:
"Invalid API key format"- Key doesn't match expected format"API key revoked"- Key has been revoked"API key expired"- Key expiration date has passed
Solutions:
- Verify the API key is correct (check for typos)
- Check if the key has been revoked in Settings → Security and API → API Keys
- Verify expiration date hasn't passed
- Ensure key starts with
ago_and is 35 characters long - Create a new API key if needed
Example:
curl -X GET "https://api.agoralia.app/api/v1/agents" \
-H "X-API-Key: invalid-key"
403 Forbidden
Cause: API key doesn't have the required scope for the endpoint.
Response:
{
"detail": "Insufficient permissions. Required scope: campaigns:write"
}
Solutions:
- Check the required scope in the endpoint documentation
- Verify your API key's scopes in Settings → Security and API → API Keys
- Check the
/meendpoint response to seeapi_key_scopes - Create a new API key with the correct scopes
Example:
# API key only has "agents:read" scope
curl -X POST "https://api.agoralia.app/api/v1/campaigns" \
-H "X-API-Key: ago_ETWIQRPbBXBrMxwcyxqUFLlYGErtFOaa" \
-H "Content-Type: application/json" \
-d '{"name": "Campaign"}'
404 Not Found
Cause: Resource not found or doesn't belong to your tenant.
Response:
{
"detail": "Campaign not found"
}
Solutions:
- Verify the resource ID is correct
- Check if the resource belongs to your tenant
- Ensure the resource hasn't been deleted
- Verify you're using the correct endpoint path
Example:
# Campaign ID 99999 doesn't exist
curl -X GET "https://api.agoralia.app/api/v1/campaigns/99999/results" \
-H "X-API-Key: ago_ETWIQRPbBXBrMxwcyxqUFLlYGErtFOaa"
409 Conflict
Cause: Idempotency key was used with different request data.
Response:
{
"detail": "Idempotency key already used with different request body"
}
Solutions:
- Use a new idempotency key
- Match the original request data exactly (including field order)
- See Idempotency for details
Example:
# First request
curl -X POST "https://api.agoralia.app/api/v1/campaigns" \
-H "X-API-Key: ago_..." \
-H "Idempotency-Key: abc123" \
-d '{"name": "Campaign A", "purpose": "quote_request"}'
# Second request with same key but different data
curl -X POST "https://api.agoralia.app/api/v1/campaigns" \
-H "X-API-Key: ago_..." \
-H "Idempotency-Key: abc123" \
-d '{"name": "Campaign B", "purpose": "cold_calling"}' # 409 Conflict
422 Validation Error
Cause: Request body or parameters failed validation.
Response:
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": ["body", "agent_ref_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
Solutions:
- Fix the validation errors listed in
detail - Check the endpoint documentation for required fields and formats
- Ensure data types match (strings, integers, objects, arrays)
- Verify required fields are present
Example:
# Missing required field "name"
curl -X POST "https://api.agoralia.app/api/v1/campaigns" \
-H "X-API-Key: ago_..." \
-H "Content-Type: application/json" \
-d '{"purpose": "quote_request", "agent_ref_id": 1}'
429 Rate Limited
Cause: Rate limit exceeded (too many requests).
Response:
{
"detail": "Too many requests. Please try again later."
}
Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
Solutions:
- Wait
X-RateLimit-Resetseconds before retrying - Implement exponential backoff
- Reduce request frequency
- Batch operations when possible
- See Rate Limits for details
Example:
import time
response = requests.get(url, headers=headers)
if response.status_code == 429:
reset_time = int(response.headers.get('X-RateLimit-Reset', time.time() + 60))
wait_time = max(0, reset_time - int(time.time()))
time.sleep(wait_time)
# Retry request
500 Server Error
Cause: Internal server error (unexpected error on server side).
Response:
{
"detail": "Internal server error"
}
Solutions:
- Retry the request after a delay (exponential backoff)
- Check Agoralia status page for service issues
- Contact support if error persists
- Include request ID or timestamp in support request
Retry Strategy:
import time
for attempt in range(max_retries):
try:
response = requests.post(url, json=data, headers=headers)
if response.status_code < 500:
return response
except requests.exceptions.RequestException:
pass
# Exponential backoff
wait_time = 2 ** attempt
time.sleep(wait_time)
Error Handling Example
Python
import requests
import time
from typing import Optional, Dict, Any
class AgoraliaAPIError(Exception):
"""Base exception for Agoralia API errors"""
def __init__(self, message: str, status_code: int, detail: Any = None):
self.message = message
self.status_code = status_code
self.detail = detail
super().__init__(self.message)
def handle_api_error(response: requests.Response) -> None:
"""Handle API errors and raise appropriate exceptions"""
if response.status_code < 400:
return
try:
error_data = response.json()
detail = error_data.get("detail", "Unknown error")
except ValueError:
detail = response.text or "Unknown error"
status_code = response.status_code
if status_code == 401:
raise AgoraliaAPIError(
"Authentication failed",
status_code,
detail
)
elif status_code == 403:
raise AgoraliaAPIError(
"Permission denied",
status_code,
detail
)
elif status_code == 404:
raise AgoraliaAPIError(
"Resource not found",
status_code,
detail
)
elif status_code == 409:
raise AgoraliaAPIError(
"Idempotency conflict",
status_code,
detail
)
elif status_code == 422:
raise AgoraliaAPIError(
"Validation error",
status_code,
detail
)
elif status_code == 429:
retry_after = int(response.headers.get('X-RateLimit-Reset', time.time() + 60))
raise AgoraliaAPIError(
"Rate limit exceeded",
status_code,
{"detail": detail, "retry_after": retry_after}
)
elif status_code >= 500:
raise AgoraliaAPIError(
"Server error",
status_code,
detail
)
else:
raise AgoraliaAPIError(
f"API error: {detail}",
status_code,
detail
)
def make_request_with_retry(
method: str,
url: str,
headers: Dict[str, str],
max_retries: int = 3,
**kwargs
) -> requests.Response:
"""Make API request with automatic retry on retryable errors"""
for attempt in range(max_retries):
try:
response = requests.request(method, url, headers=headers, **kwargs)
# Handle rate limiting
if response.status_code == 429:
if attempt < max_retries - 1:
reset_time = int(response.headers.get('X-RateLimit-Reset', time.time() + 60))
wait_time = max(0, reset_time - int(time.time()))
time.sleep(wait_time)
continue
# Handle server errors
if response.status_code >= 500:
if attempt < max_retries - 1:
wait_time = 2 ** attempt
time.sleep(wait_time)
continue
# Check for other errors
if response.status_code >= 400:
handle_api_error(response)
return response
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
raise Exception("Max retries exceeded")
# Usage
try:
response = make_request_with_retry(
"GET",
"https://api.agoralia.app/api/v1/agents",
headers={"X-API-Key": "ago_..."}
)
data = response.json()
except AgoraliaAPIError as e:
print(f"API Error {e.status_code}: {e.detail}")
Node.js
const axios = require('axios');
class AgoraliaAPIError extends Error {
constructor(message, statusCode, detail) {
super(message);
this.statusCode = statusCode;
this.detail = detail;
this.name = 'AgoraliaAPIError';
}
}
async function makeRequestWithRetry(method, url, headers, maxRetries = 3, data = null) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const config = {
method,
url,
headers,
data
};
const response = await axios(config);
return response.data;
} catch (error) {
if (!error.response) {
// Network error
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000));
continue;
}
throw error;
}
const statusCode = error.response.status;
const detail = error.response.data?.detail || error.message;
// Retry on rate limit
if (statusCode === 429 && attempt < maxRetries - 1) {
const resetTime = parseInt(error.response.headers['x-ratelimit-reset'] || Date.now() / 1000 + 60);
const waitTime = Math.max(0, resetTime - Math.floor(Date.now() / 1000));
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
continue;
}
// Retry on server errors
if (statusCode >= 500 && attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000));
continue;
}
// Don't retry on client errors
throw new AgoraliaAPIError(
`API Error ${statusCode}`,
statusCode,
detail
);
}
}
throw new Error('Max retries exceeded');
}
Validation Error Details
For 422 validation errors, the detail field contains an array of validation errors:
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": ["body", "agent_ref_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
},
{
"loc": ["body", "extraction_schema"],
"msg": "value is not a valid dict",
"type": "type_error.dict"
}
]
}
Field Descriptions:
loc: Location of the error (array path, e.g.,["body", "field_name"])msg: Human-readable error messagetype: Error type (e.g.,value_error.missing,type_error.integer)
Common Validation Errors:
value_error.missing- Required field is missingtype_error.integer- Value is not an integertype_error.string- Value is not a stringtype_error.dict- Value is not an object/dictvalue_error.str.min_length- String is too shortvalue_error.str.max_length- String is too long
Troubleshooting Checklist
When debugging API errors:
- Check API key: Verify key is correct and not expired/revoked
- Verify scopes: Check API key has required scopes (use
/meendpoint) - Validate payload: Ensure request body matches endpoint schema
- Check rate limits: Monitor
X-RateLimit-Remainingheader - Verify resource IDs: Ensure IDs are correct and belong to your tenant
- Check idempotency: If using idempotency keys, ensure request body matches
- Review error details: Read
detailfield for specific error information - Check service status: Verify Agoralia service is operational
- Retry with backoff: For 429/500/503 errors, implement exponential backoff
Next Steps
- Authentication - Set up API keys correctly
- Idempotency - Handle idempotency conflicts
- Rate Limits - Understand and handle rate limits
- Endpoints Reference - See endpoint-specific requirements