Error Handling
Complete guide to error responses, codes, and retry strategies for the Affinity AI Bid API.
Error Response Format
All errors follow a consistent structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
"field": "specific.field.path",
"reason": "detailed_reason"
},
"request_id": "uuid-v4-string"
}
}
Error Codes
Client Errors (4xx)
| Code | HTTP Status | Description | Retry |
|---|---|---|---|
INVALID_REQUEST | 400 | Malformed OpenRTB request | No |
MISSING_REQUIRED_FIELD | 400 | Required field missing | No |
INVALID_FIELD_VALUE | 400 | Field value invalid | No |
UNSUPPORTED_FORMAT | 400 | Requested format not supported | No |
RATE_LIMIT_EXCEEDED | 429 | Too many requests | Yes (after delay) |
Server Errors (5xx)
| Code | HTTP Status | Description | Retry |
|---|---|---|---|
INTERNAL_ERROR | 500 | Server error | Yes |
BACKEND_ERROR | 502 | Backend bidder error | Yes |
SERVICE_UNAVAILABLE | 503 | Service temporarily down | Yes |
GATEWAY_TIMEOUT | 504 | Backend timeout | Yes |
Error Examples
Invalid Request (400)
Malformed OpenRTB request:
{
"error": {
"code": "INVALID_REQUEST",
"message": "Invalid OpenRTB request format",
"details": {
"field": "imp[0].native",
"reason": "missing required field 'request'"
},
"request_id": "req-abc123"
}
}
Resolution: Fix the request structure and retry.
Missing Required Field (400)
Required field not provided:
{
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "Required field missing",
"details": {
"field": "id",
"reason": "BidRequest must include 'id' field"
},
"request_id": "req-def456"
}
}
Resolution: Add the missing field and retry.
Invalid Field Value (400)
Field value doesn't meet validation rules:
{
"error": {
"code": "INVALID_FIELD_VALUE",
"message": "Field value invalid",
"details": {
"field": "imp[0].native.request",
"reason": "width must be a positive integer, got -300"
},
"request_id": "req-ghi789"
}
}
Resolution: Correct the field value and retry.
Unsupported Format (400)
Requested creative format not supported:
{
"error": {
"code": "UNSUPPORTED_FORMAT",
"message": "Requested format not supported",
"details": {
"field": "imp[0].ext.aura.adcpFormats[0].id",
"reason": "format 'custom_format_xyz' is not supported"
},
"request_id": "req-jkl012"
}
}
Resolution: Use a supported format from the AdCP formats list.
Rate Limit Exceeded (429)
Too many requests from publisher or IP:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"details": {
"limit": 1000,
"window": "1 second",
"retry_after": 30
},
"request_id": "req-mno345"
}
}
Response Headers:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200
Retry-After: 30
Resolution: Wait for the specified retry_after seconds before retrying.
Internal Server Error (500)
Server-side processing error:
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error processing bid request",
"request_id": "req-pqr678"
}
}
Resolution: Retry with exponential backoff.
Backend Error (502)
Backend bidder returned an error:
{
"error": {
"code": "BACKEND_ERROR",
"message": "Backend bidder error",
"details": {
"backend": "affilizz-search-product-ad",
"reason": "backend returned 500"
},
"request_id": "req-stu901"
}
}
Resolution: Retry with exponential backoff.
Service Unavailable (503)
Service temporarily unavailable:
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Backend bidders temporarily unavailable",
"retry_after": 30,
"request_id": "req-vwx234"
}
}
Resolution: Wait for retry_after seconds and retry.
Gateway Timeout (504)
Backend timeout exceeded:
{
"error": {
"code": "GATEWAY_TIMEOUT",
"message": "Backend timeout exceeded",
"details": {
"timeout_ms": 500
},
"request_id": "req-yz567"
}
}
Resolution: Retry with exponential backoff.
Retry Logic
Retryable Errors (5xx)
For server errors (500, 502, 503, 504), implement exponential backoff:
Attempt 1: Immediate
Attempt 2: Wait 1 second
Attempt 3: Wait 2 seconds
Attempt 4: Wait 4 seconds
Max: 3 retries
Algorithm:
async function retryRequest(request, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await sendRequest(request)
return response
} catch (error) {
if (attempt === maxRetries) throw error
// Check if error is retryable (5xx)
if (error.status >= 500 && error.status < 600) {
const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s
await sleep(delay)
continue
}
// Non-retryable error
throw error
}
}
}
Non-Retryable Errors (4xx)
For client errors (400, 429), do not retry without fixing the request:
- 400 errors: Fix the request structure/values
- 429 errors: Wait for
Retry-Afterheader value
Rate Limit Handling
Respect rate limit headers:
function handleRateLimit(response) {
const retryAfter = response.headers['Retry-After'];
const resetTime = response.headers['X-RateLimit-Reset'];
if (retryAfter) {
// Wait for specified seconds
await sleep(parseInt(retryAfter) * 1000);
} else if (resetTime) {
// Wait until reset time
const now = Date.now() / 1000;
const waitTime = Math.max(0, resetTime - now);
await sleep(waitTime * 1000);
}
}
Rate Limiting
Limits
| Scope | Limit | Window | Response |
|---|---|---|---|
| Global | 10,000 req/sec | 1 second | 503 Service Unavailable |
| Per Publisher | 1,000 req/sec | 1 second | 429 Too Many Requests |
| Per IP | 100 req/sec | 1 second | 429 Too Many Requests |
Rate Limit Headers
All responses include rate limit information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1704067200
When rate limited:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200
Retry-After: 30
Best Practices
1. Implement Exponential Backoff
Always use exponential backoff for 5xx errors to avoid overwhelming the service during incidents.
2. Respect Rate Limits
Monitor rate limit headers and implement client-side throttling to stay within limits.
3. Log Request IDs
Always log the request_id from error responses for troubleshooting and support
requests.
4. Handle No Bid Gracefully
A 204 No Content response is not an error - handle it gracefully without displaying errors to users.
5. Set Appropriate Timeouts
Set client timeouts slightly higher than the service timeout (500ms) to allow for network latency:
const timeout = 600 // 600ms client timeout
6. Monitor Error Rates
Track error rates by code to identify integration issues or service degradation:
- Alert on sustained 4xx rates (integration issues)
- Alert on 5xx rates above baseline (service issues)
Troubleshooting
Common Issues
Issue: Consistent 400 errors
- Cause: Invalid request format
- Solution: Validate request against OpenRTB 2.6 spec
- Tool: Use test mode (
test: 1) to validate requests
Issue: Intermittent 503 errors
- Cause: Backend temporarily unavailable
- Solution: Implement retry logic with exponential backoff
- Expected: Occasional 503s are normal during deployments
Issue: 429 rate limit errors
- Cause: Exceeding request rate limits
- Solution: Implement client-side throttling and respect
Retry-Afterheaders
Issue: 504 timeout errors
- Cause: Backend taking too long to respond
- Solution: Retry with exponential backoff
- Note: May indicate backend performance issues
Support
If you encounter persistent errors:
- Check the API Status Page for known issues
- Review error details and
request_idfrom responses - Contact support with:
- Request ID
- Timestamp
- Full error response
- Sample request (sanitized)
Next Steps
- Request Format - Validation rules