Skip to main content

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

CodeMeaningRetry?Description
200Success-Request succeeded
201Created-Resource created successfully
400Bad RequestInvalid request format or parameters
401UnauthorizedInvalid or missing API key
403ForbiddenAPI key missing required scope
404Not FoundResource not found
409ConflictIdempotency key conflict
422Validation ErrorRequest validation failed
429Rate LimitedToo many requests - retry with backoff
500Server ErrorInternal server error - retry later
503Service UnavailableService 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 /me endpoint response to see api_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-Reset seconds 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 message
  • type: Error type (e.g., value_error.missing, type_error.integer)

Common Validation Errors:

  • value_error.missing - Required field is missing
  • type_error.integer - Value is not an integer
  • type_error.string - Value is not a string
  • type_error.dict - Value is not an object/dict
  • value_error.str.min_length - String is too short
  • value_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 /me endpoint)
  • Validate payload: Ensure request body matches endpoint schema
  • Check rate limits: Monitor X-RateLimit-Remaining header
  • 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 detail field 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