Error Reference
All API errors follow a consistent format with machine-readable codes and human-friendly messages.
{
"code": 400,
"status": "error",
"message": "Validation failed",
"error": {
"code": "VALIDATION_ERROR",
"message": "The title field is required",
"details": {
"field": "title",
"constraint": "required"
}
}
}
| Field | Type | Description |
|---|
code | number | HTTP status code |
status | string | ”OK” or “error” |
message | string | Brief description |
error.code | string | Machine-readable error code |
error.message | string | Human-readable explanation |
error.details | object | Additional context (optional) |
HTTP Status Codes
| Code | Meaning | When It Happens |
|---|
| 400 | Bad Request | Invalid parameters, malformed JSON |
| 401 | Unauthorized | Missing or invalid API key/token |
| 403 | Forbidden | Key doesn’t have access to resource |
| 404 | Not Found | Resource doesn’t exist or is private |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Internal error (contact support) |
| 503 | Service Unavailable | Temporary outage |
Error Codes by Category
Authentication Errors (401)
| Code | Message | Solution |
|---|
MISSING_API_KEY | X-API-Key header required | Add X-API-Key header to token request |
INVALID_API_KEY | API key not found | Check key is correct and not revoked |
EXPIRED_API_KEY | API key has expired | Generate a new key in Studio |
REVOKED_API_KEY | API key was revoked | Generate a new key in Studio |
MISSING_TOKEN | Authorization header required | Get token from POST /auth/token |
TOKEN_INVALID | Token is malformed | Get a fresh token |
TOKEN_EXPIRED | Token has expired | Refresh token via POST /auth/refresh |
Handling 401 errors:
async function handleRequest(path) {
const res = await fetch(path, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.status === 401) {
const { error } = await res.json();
switch (error.code) {
case 'TOKEN_EXPIRED':
// Refresh and retry
token = await refreshToken();
return handleRequest(path);
case 'REVOKED_API_KEY':
case 'INVALID_API_KEY':
// Need new API key - show error to user
throw new Error('API key invalid. Please reconfigure.');
default:
throw new Error(error.message);
}
}
return res.json();
}
Authorization Errors (403)
| Code | Message | Solution |
|---|
STORE_ACCESS_DENIED | Key doesn’t have access to this store | Check key scope in Studio |
WRITE_ACCESS_DENIED | Public keys are read-only | Use secret key (sk_) for writes |
INSUFFICIENT_PERMISSIONS | Missing required permission | Check key permissions |
FORBIDDEN | Access denied | Resource may be private |
Resource Errors (404)
| Code | Message | Solution |
|---|
STORE_NOT_FOUND | Store does not exist | Verify store ID |
LISTING_NOT_FOUND | Listing does not exist | Verify listing ID |
PUBLICATION_NOT_FOUND | Publication does not exist | Verify publication ID |
NOT_FOUND | Resource not found | Check the ID and visibility |
Note: Private resources return 404 (not 403) to avoid leaking information about their existence.
Rate Limit Errors (429)
| Code | Message | Solution |
|---|
RATE_LIMIT_EXCEEDED | Too many requests | Wait for Retry-After period |
DAILY_QUOTA_EXCEEDED | Daily limit reached | Wait until tomorrow or upgrade |
Rate limit responses include helpful headers:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707480000
Retry-After: 60
Content-Type: application/json
{
"code": 429,
"status": "error",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"tier": "public_key"
}
}
Handling rate limits:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = res.headers.get('Retry-After') || 60;
console.log(`Rate limited. Retrying in ${retryAfter}s`);
await sleep(retryAfter * 1000);
continue;
}
return res;
}
throw new Error('Max retries exceeded');
}
Validation Errors (400)
| Code | Message | Solution |
|---|
INVALID_REQUEST | Invalid request format | Check JSON syntax |
VALIDATION_ERROR | Request validation failed | Check required fields |
FIELD_REQUIRED | This field is required | Provide the missing field |
FIELD_INVALID | Field value is invalid | Check field format |
FIELD_TOO_LONG | Field value is too long | Reduce field length |
INVALID_UUID | Invalid UUID format | Use valid CUID/UUID |
Server Errors (500, 503)
| Code | Message | Solution |
|---|
INTERNAL_ERROR | Internal server error | Retry later, contact support if persists |
SERVICE_UNAVAILABLE | Service temporarily unavailable | Retry with exponential backoff |
DATABASE_CONNECTION_ERROR | Database connection error | Retry later |
DATABASE_TIMEOUT | Database operation timed out | Retry later |
Handling server errors:
async function fetchWithBackoff(url, options) {
const delays = [1000, 2000, 4000, 8000]; // Exponential backoff
for (const delay of delays) {
try {
const res = await fetch(url, options);
if (res.status >= 500) {
await sleep(delay);
continue;
}
return res;
} catch (err) {
await sleep(delay);
}
}
throw new Error('Service unavailable');
}
Complete Error Code List
For programmatic error handling, here are all error codes:
type ErrorCode =
// General
| 'UNKNOWN_ERROR'
| 'INTERNAL_ERROR'
| 'INVALID_REQUEST'
| 'UNAUTHORIZED'
| 'FORBIDDEN'
| 'NOT_FOUND'
| 'CONFLICT'
| 'VALIDATION_ERROR'
| 'RATE_LIMIT_EXCEEDED'
| 'SERVICE_UNAVAILABLE'
// Authentication
| 'INVALID_CREDENTIALS'
| 'TOKEN_EXPIRED'
| 'TOKEN_INVALID'
| 'INSUFFICIENT_PERMISSIONS'
| 'ACCOUNT_SUSPENDED'
| 'ACCOUNT_NOT_VERIFIED'
// Resources
| 'ACCOUNT_NOT_FOUND'
| 'LISTING_NOT_FOUND'
| 'PUBLICATION_NOT_FOUND'
| 'MEDIA_NOT_FOUND'
| 'STORE_NOT_FOUND'
// Validation
| 'FIELD_REQUIRED'
| 'FIELD_INVALID'
| 'FIELD_TOO_LONG'
| 'FIELD_TOO_SHORT'
| 'INVALID_EMAIL'
| 'INVALID_UUID'
| 'INVALID_URL'
// Business Logic
| 'LIMIT_EXCEEDED'
| 'OPERATION_NOT_ALLOWED'
| 'RESOURCE_LOCKED'
| 'DEPENDENCY_EXISTS'
| 'DUPLICATE_OPERATION';