StateSet API uses standard HTTP response codes and provides detailed error information to help you handle issues gracefully.
All errors follow a consistent JSON structure to make error handling predictable:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"type": "error_type",
"details": {
// Additional context-specific information
},
"request_id": "req_1NXWPnCo6bFb1KQto6C8OWvE",
"timestamp": "2024-01-15T10:30:00Z",
"documentation_url": "https://docs.stateset.com/errors/ERROR_CODE"
}
}
HTTP Status Codes
Status Code | Meaning | Common Causes |
---|
200 | OK | Request succeeded |
201 | Created | Resource successfully created |
202 | Accepted | Request accepted for async processing |
204 | No Content | Request succeeded with no response body |
400 | Bad Request | Invalid request parameters or malformed syntax |
401 | Unauthorized | Missing or invalid authentication |
403 | Forbidden | Valid auth but insufficient permissions |
404 | Not Found | Resource doesn’t exist |
409 | Conflict | Resource conflict or duplicate |
422 | Unprocessable Entity | Valid syntax but semantic errors |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server error - retry with backoff |
502 | Bad Gateway | Upstream service error |
503 | Service Unavailable | Temporary service outage |
Error Types and Codes
Authentication Errors (401)
Error Code | Description | Resolution |
---|
INVALID_API_KEY | API key is invalid or revoked | Check key in dashboard |
EXPIRED_API_KEY | API key has expired | Generate new API key |
MISSING_AUTH_HEADER | No Authorization header provided | Include Bearer token |
INVALID_TOKEN_FORMAT | Token format is incorrect | Use Bearer sk_... format |
INACTIVE_ACCOUNT | Account is suspended or inactive | Contact support |
Authorization Errors (403)
Error Code | Description | Resolution |
---|
INSUFFICIENT_PERMISSIONS | API key lacks required permissions | Use key with proper scopes |
RESOURCE_ACCESS_DENIED | Cannot access this specific resource | Check resource ownership |
PLAN_LIMIT_EXCEEDED | Feature not available on current plan | Upgrade plan |
IP_NOT_ALLOWED | Request from unauthorized IP | Add IP to allowlist |
Validation Errors (400/422)
Error Code | Description | Resolution |
---|
VALIDATION_ERROR | Request validation failed | Check field requirements |
MISSING_REQUIRED_FIELD | Required field not provided | Include all required fields |
INVALID_FIELD_VALUE | Field value doesn’t meet requirements | Validate field format |
INVALID_ENUM_VALUE | Value not in allowed enum list | Use accepted values |
FIELD_TOO_LONG | Field exceeds maximum length | Truncate field value |
INVALID_DATE_FORMAT | Date format incorrect | Use ISO 8601 format |
Resource Errors (404/409)
Error Code | Description | Resolution |
---|
RESOURCE_NOT_FOUND | Requested resource doesn’t exist | Verify resource ID |
RESOURCE_ALREADY_EXISTS | Duplicate resource creation attempt | Use existing resource |
RESOURCE_LOCKED | Resource is locked for editing | Wait and retry |
RESOURCE_ARCHIVED | Resource has been archived | Unarchive or use different resource |
PARENT_NOT_FOUND | Parent resource doesn’t exist | Create parent first |
Business Logic Errors (422)
Error Code | Description | Resolution |
---|
INSUFFICIENT_INVENTORY | Not enough inventory available | Reduce quantity or check stock |
PAYMENT_FAILED | Payment processing failed | Verify payment details |
INVALID_STATE_TRANSITION | Invalid status change | Check allowed transitions |
OUTSIDE_RETURN_WINDOW | Return period expired | Check return policy |
DUPLICATE_REQUEST | Duplicate request detected | Use idempotency key |
Rate Limiting Errors (429)
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded",
"type": "rate_limit_error",
"details": {
"limit": 100,
"remaining": 0,
"reset_at": "2024-01-15T10:35:00Z",
"retry_after": 300
}
}
}
Error Handling Best Practices
1. Implement Exponential Backoff
class APIClient {
async requestWithRetry(url, options, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('X-RateLimit-Retry-After');
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 32000);
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
if (!response.ok) {
const error = await response.json();
throw new APIError(error);
}
return response.json();
} catch (error) {
lastError = error;
if (attempt < maxRetries && this.isRetryable(error)) {
const delay = Math.min(1000 * Math.pow(2, attempt), 32000);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw lastError;
}
isRetryable(error) {
const retryableCodes = [429, 500, 502, 503, 504];
return error.status && retryableCodes.includes(error.status);
}
}
2. Handle Specific Error Types
class ErrorHandler {
handle(error) {
switch (error.code) {
case 'INVALID_API_KEY':
// Refresh API key or prompt for new one
return this.refreshApiKey();
case 'INSUFFICIENT_PERMISSIONS':
// Inform user about permission requirements
return this.showPermissionError(error.details);
case 'VALIDATION_ERROR':
// Show field-specific errors
return this.showValidationErrors(error.details);
case 'RATE_LIMITED':
// Implement backoff and queue
return this.queueRequest(error.details.retry_after);
case 'INSUFFICIENT_INVENTORY':
// Update UI with available quantity
return this.updateInventory(error.details);
default:
// Generic error handling
return this.showGenericError(error.message);
}
}
}
3. Implement Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.failureThreshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED';
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
4. Log and Monitor Errors
class ErrorLogger {
log(error, context) {
const errorData = {
timestamp: new Date().toISOString(),
request_id: error.request_id,
error_code: error.code,
message: error.message,
context: {
user_id: context.userId,
endpoint: context.endpoint,
method: context.method,
...context
},
stack_trace: error.stack
};
// Send to logging service
logger.error(errorData);
// Track in analytics
analytics.track('API_ERROR', errorData);
// Alert on critical errors
if (this.isCritical(error)) {
alerting.notify(errorData);
}
}
isCritical(error) {
const criticalCodes = [
'PAYMENT_FAILED',
'SERVICE_UNAVAILABLE',
'DATABASE_ERROR'
];
return criticalCodes.includes(error.code);
}
}
Error Recovery Strategies
Idempotency for Safe Retries
// Use idempotency keys to safely retry requests
const createOrder = async (orderData) => {
const idempotencyKey = `order-${uuidv4()}`;
try {
return await api.post('/orders', orderData, {
headers: {
'Idempotency-Key': idempotencyKey
}
});
} catch (error) {
if (error.code === 'DUPLICATE_REQUEST') {
// Return existing order from previous request
return error.details.existing_resource;
}
throw error;
}
};
Graceful Degradation
class ResilientClient {
async getProductWithFallback(productId) {
try {
// Try primary API
return await this.api.get(`/products/${productId}`);
} catch (error) {
if (error.code === 'SERVICE_UNAVAILABLE') {
// Fall back to cache
const cached = await this.cache.get(`product:${productId}`);
if (cached) {
return { ...cached, from_cache: true };
}
// Fall back to secondary service
return await this.secondaryApi.get(`/products/${productId}`);
}
throw error;
}
}
}
Common Error Scenarios
Handling Validation Errors
// Request
POST /v1/orders
{
"customer_email": "invalid-email",
"items": []
}
// Response (422)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"type": "validation_error",
"details": {
"errors": [
{
"field": "customer_email",
"message": "Invalid email format",
"expected": "valid email address"
},
{
"field": "items",
"message": "At least one item is required",
"expected": "non-empty array"
}
]
},
"request_id": "req_abc123"
}
}
Handling Resource Conflicts
// Request
POST /v1/customers
{
"email": "existing@example.com"
}
// Response (409)
{
"error": {
"code": "RESOURCE_ALREADY_EXISTS",
"message": "Customer with this email already exists",
"type": "conflict_error",
"details": {
"existing_id": "cus_123abc",
"field": "email",
"value": "existing@example.com"
},
"request_id": "req_def456"
}
}
Error Webhooks
Configure error webhooks to receive notifications for critical failures:
{
"event": "api.error",
"created": "2024-01-15T10:30:00Z",
"data": {
"error_code": "PAYMENT_FAILED",
"request_id": "req_xyz789",
"customer_id": "cus_123",
"order_id": "ord_456",
"amount": 9999,
"currency": "usd"
}
}
SDK Error Handling
Our SDKs provide built-in error handling:
try {
const order = await stateset.orders.create({
customer_email: 'customer@example.com',
items: [...]
});
} catch (error) {
if (error.code === 'INSUFFICIENT_INVENTORY') {
// Handle inventory shortage
const available = error.details.available_items;
console.log(`Only ${available} items available`);
} else if (error.code === 'VALIDATION_ERROR') {
// Handle validation errors
error.details.errors.forEach(err => {
console.log(`${err.field}: ${err.message}`);
});
} else {
// Generic error handling
console.error('Unexpected error:', error.message);
}
}
Testing Error Scenarios
Use test mode to simulate error conditions:
# Trigger specific error in test mode
curl -X POST https://api.sandbox.stateset.com/v1/orders \
-H "Authorization: Bearer sk_test_..." \
-H "X-Test-Error-Code: INSUFFICIENT_INVENTORY" \
-d '{...}'
Support
If you encounter persistent errors or need help with error handling:
Related: Rate Limiting | Authentication | Webhooks