Error Responses
Understanding and handling API error responses.
Error Format
All errors follow a consistent format:
{
"success": false,
"error": "Human-readable description",
"code": "BAD_REQUEST",
"requestId": "req_abc123xyz",
"details": [{ "field": "...", "message": "..." }]
}
| Field | Description |
|---|---|
| success | Always false for error responses |
| error | Human-readable error description |
| code | Error code string (e.g. UNAUTHORIZED, RATE_LIMITED) |
| requestId | Unique request ID for debugging (optional) |
| details | Field-level validation errors (optional) |
| retryAfter | Present on some 429 responses (body field; optional) |
Some handlers also include route-specific fields such as hint, required, status, reason, or message.
HTTP Status Codes
2xx Success
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
4xx Client Errors
| Code | Meaning |
|---|---|
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 413 | Payload Too Large |
| 429 | Too Many Requests |
5xx Server Errors
| Code | Meaning |
|---|---|
| 500 | Internal Server Error |
| 503 | Service Unavailable |
| 504 | Gateway Timeout |
Common Errors
400 Bad Request
Invalid request parameters.
{
"success": false,
"error": "Invalid market address format",
"code": "VALIDATION_ERROR",
"requestId": "req_abc123xyz",
"details": [{ "field": "address", "message": "Must be a valid base58 public key" }]
}
Common causes:
- Invalid public key format
- Missing required parameters
- Invalid parameter values
401 Unauthorized
Authentication failed.
{
"success": false,
"error": "Invalid API key",
"code": "UNAUTHORIZED",
"requestId": "req_abc123xyz"
}
Common causes:
- Missing API key
- Invalid API key
- Expired API key
403 Forbidden
Authenticated but not allowed.
{
"success": false,
"error": "API key does not have access to this resource",
"code": "FORBIDDEN",
"requestId": "req_abc123xyz"
}
Common causes:
- Insufficient permissions
- Accessing another user's data
- Feature not available for tier
404 Not Found
Resource doesn't exist.
{
"success": false,
"error": "Market not found",
"code": "NOT_FOUND",
"requestId": "req_abc123xyz"
}
Common causes:
- Invalid address
- Resource deleted
- Wrong environment (mainnet vs devnet)
413 Payload Too Large
Request body exceeds configured API limits.
{
"success": false,
"error": "Request body too large",
"code": "BAD_REQUEST",
"requestId": "req_abc123xyz"
}
429 Too Many Requests
Rate limit exceeded (global IP limit or API key limit).
{
"success": false,
"error": "API key rate limit exceeded",
"code": "RATE_LIMITED",
"requestId": "req_abc123xyz",
"retryAfter": "42s"
}
Headers:
Retry-AfterX-RateLimit-Limit/X-RateLimit-Remaining/X-RateLimit-ResetX-RateLimit-Limit-Key/X-RateLimit-Remaining-Key/X-RateLimit-Reset-Key
500 Internal Server Error
Server-side error.
{
"success": false,
"error": "Internal server error",
"code": "INTERNAL_ERROR",
"requestId": "req_abc123xyz"
}
Action: Contact support with the requestId.
503 Service Unavailable
Temporary service issue.
{
"success": false,
"error": "Database temporarily unavailable",
"code": "SERVICE_UNAVAILABLE",
"requestId": "req_abc123xyz"
}
Error Handling
JavaScript/TypeScript
async function fetchMarket(address: string) {
try {
const response = await fetch(`/api/markets/${address}`);
if (!response.ok) {
const error = await response.json();
switch (error.code) {
case 'UNAUTHORIZED':
throw new Error('Please check your API key');
case 'NOT_FOUND':
throw new Error('Market not found');
case 'RATE_LIMITED':
const retryAfter = error.retryAfter || 60;
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
default:
throw new Error(error.error || 'Unknown error');
}
}
return response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
Python
import requests
def fetch_market(address):
response = requests.get(f'{BASE_URL}/api/markets/{address}')
if response.status_code == 200:
return response.json()
error = response.json()
if response.status_code == 401:
raise AuthenticationError(error['error'])
elif response.status_code == 404:
raise NotFoundError(f"Market {address} not found")
elif response.status_code == 429:
raise RateLimitError(error.get('retryAfter', 60))
else:
raise APIError(error['error'])
React
function useMarket(address: string) {
const [data, setData] = useState(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(`/api/markets/${address}`);
if (!response.ok) {
const errorData = await response.json();
setError(getErrorMessage(errorData));
return;
}
setData(await response.json());
setError(null);
} catch (e) {
setError('Network error. Please try again.');
} finally {
setLoading(false);
}
}
fetchData();
}, [address]);
return { data, error, loading };
}
function getErrorMessage(error: any): string {
switch (error.code) {
case 'NOT_FOUND':
return 'Market not found. Please check the address.';
case 'RATE_LIMITED':
return 'Too many requests. Please wait a moment.';
case 'INTERNAL_ERROR':
return 'Server error. Please try again later.';
default:
return error.error || 'An error occurred.';
}
}
Validation Errors
Detailed validation error format:
{
"success": false,
"error": "Validation error",
"code": "VALIDATION_ERROR",
"requestId": "req_abc123xyz",
"details": [
{ "field": "limit", "message": "Must be between 1 and 100" },
{ "field": "status", "message": "Must be one of: active, matured, all" }
]
}
Retry Logic
When to Retry
| HTTP Status | Retry? | Strategy |
|---|---|---|
| 400 | No | Fix request |
| 401 | No | Check credentials |
| 403 | No | Check permissions |
| 404 | No | Check resource |
| 413 | No | Reduce payload size |
| 429 | Yes | Honor Retry-After |
| 500 | Yes | Exponential backoff + jitter |
| 503 | Yes | Exponential backoff + jitter |
| 504 | Yes | Exponential backoff + jitter |
Retry Implementation
async function fetchWithRetry(url: string, maxRetries = 3, initialDelay = 1000) {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (response.ok) {
return response.json();
}
const error = await response.json();
// Retry rate limits using Retry-After when present
if (response.status === 429) {
const retryAfterHeader = response.headers.get('retry-after');
const retryAfterSeconds = retryAfterHeader ? Number(retryAfterHeader) : NaN;
const retryDelay =
Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
? retryAfterSeconds * 1000
: initialDelay * Math.pow(2, attempt);
await sleep(retryDelay);
continue;
}
// Don't retry other client errors
if (response.status >= 400 && response.status < 500) {
throw new Error(error.error);
}
// Server error - use backoff
await sleep(initialDelay * Math.pow(2, attempt));
} catch (error) {
lastError = error as Error;
}
}
throw lastError || new Error('Max retries exceeded');
}
Debugging
Using Request ID
Every response includes a request ID:
X-Request-Id: req_abc123xyz
Include this when contacting support.
Logging
Log errors with full context:
function logApiError(error: ApiError, context: object) {
console.error('API Error', {
code: error.code,
error: error.error,
requestId: error.requestId,
...context,
timestamp: new Date().toISOString(),
});
}