Error Handling
Common API errors and how to handle them effectively.
Overview
The Grapevine API uses standard HTTP status codes to indicate success or failure. This guide covers common error scenarios and proper handling strategies.
HTTP Status Codes
2xx Success
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 204 | No Content | Request successful, no content returned |
4xx Client Errors
400 - Bad Request
Invalid request data or parameters.
Common Causes:- Missing required fields
- Invalid data format
- Malformed JSON body
- Invalid query parameters
{
"error": "Invalid request data",
"code": "INVALID_REQUEST",
"details": "Field 'name' is required"
}try {
const response = await fetch('/api/feeds', options);
if (response.status === 400) {
const error = await response.json();
console.error('Bad Request:', error.details);
// Fix request data and retry
}
} catch (error) {
console.error('Request failed:', error);
}401 - Unauthorized
Missing or invalid authentication.
Common Causes:- Missing authentication headers
- Invalid signature
- Expired timestamp
- Wrong wallet address
{
"error": "Authentication required",
"code": "UNAUTHORIZED"
}if (response.status === 401) {
console.error('Authentication failed - check wallet signature');
// Regenerate signature and retry
const newSignature = await signMessage(message);
// Retry request with new signature
}402 - Payment Required
x402 micropayment needed to access paid content.
Common Causes:- Accessing paid entry without payment
- Insufficient USDC balance
- Payment header missing or invalid
{
"error": "Payment required",
"code": "PAYMENT_REQUIRED",
"x402": {
"version": "1.0",
"amount": "1000000",
"currency": "USDC",
"network": "base",
"recipient": "0x...",
"facilitator": "0x..."
}
}if (response.status === 402) {
const paymentData = await response.json();
console.log('Payment required:', paymentData.x402.amount, 'USDC');
// Process payment using x402 data
const paymentHeader = await createX402Payment(paymentData.x402);
// Retry request with payment header
const retryResponse = await fetch(url, {
...options,
headers: {
...options.headers,
'X-PAYMENT': paymentHeader
}
});
}403 - Forbidden
Insufficient permissions for the requested operation.
Common Causes:- Trying to modify another user's content
- Accessing restricted resources
- Invalid wallet permissions
{
"error": "Access denied",
"code": "FORBIDDEN",
"details": "You can only modify your own feeds"
}404 - Not Found
Requested resource doesn't exist.
Common Causes:- Invalid resource ID
- Deleted resource
- Typo in endpoint URL
{
"error": "Resource not found",
"code": "NOT_FOUND",
"resource": "feed",
"id": "invalid-uuid"
}409 - Conflict
Resource already exists or conflicting state.
Common Causes:- Duplicate resource creation
- Concurrent modifications
- Business rule violations
{
"error": "Resource conflict",
"code": "CONFLICT",
"details": "Feed with this name already exists"
}422 - Unprocessable Entity
Valid JSON but invalid business logic.
Common Causes:- Invalid field values
- Business rule violations
- Data validation failures
{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"fields": {
"price": "Must be greater than 0",
"tags": "Maximum 10 tags allowed"
}
}429 - Too Many Requests
Rate limit exceeded.
Common Causes:- Too many requests in short period
- Batch operations without delays
- Automated scripts without throttling
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT",
"retry_after": 60,
"limit": 100,
"window": 3600
}if (response.status === 429) {
const error = await response.json();
console.log(`Rate limited. Retry after ${error.retry_after} seconds`);
// Wait and retry
await new Promise(resolve =>
setTimeout(resolve, error.retry_after * 1000)
);
// Retry request
}5xx Server Errors
500 - Internal Server Error
Server-side error, usually temporary.
How to Handle:if (response.status === 500) {
console.error('Server error - retrying in 5 seconds');
await new Promise(resolve => setTimeout(resolve, 5000));
// Retry request
}502 - Bad Gateway
Gateway or proxy error.
503 - Service Unavailable
Service temporarily unavailable.
504 - Gateway Timeout
Request timeout at gateway level.
Error Response Format
All API errors follow a consistent format:
{
"error": "Human readable error message",
"code": "MACHINE_READABLE_CODE",
"details": "Additional context (optional)",
"fields": {
"field_name": "Field-specific error (for validation errors)"
},
"request_id": "uuid-for-support",
"timestamp": 1234567890
}Error Codes
Authentication Errors
UNAUTHORIZED- Missing or invalid authenticationINVALID_SIGNATURE- Cryptographic signature invalidEXPIRED_TIMESTAMP- Request timestamp too oldINVALID_WALLET- Wallet address invalid
Validation Errors
VALIDATION_ERROR- General validation failureINVALID_REQUEST- Malformed request dataMISSING_REQUIRED_FIELD- Required field not providedINVALID_FORMAT- Field format invalid
Resource Errors
NOT_FOUND- Resource doesn't existCONFLICT- Resource already existsFORBIDDEN- Access deniedGONE- Resource permanently deleted
Payment Errors
PAYMENT_REQUIRED- x402 payment neededPAYMENT_FAILED- Payment processing failedINSUFFICIENT_BALANCE- Not enough fundsINVALID_PAYMENT- Payment data invalid
Rate Limiting
RATE_LIMIT- Too many requestsQUOTA_EXCEEDED- Usage quota exceeded
Server Errors
INTERNAL_ERROR- Server-side errorSERVICE_UNAVAILABLE- Service temporarily downGATEWAY_TIMEOUT- Request timeout
Best Practices
1. Always Check Status Codes
const response = await fetch('/api/feeds');
if (!response.ok) {
const error = await response.json();
console.error(`API Error (${response.status}):`, error);
switch (response.status) {
case 401:
// Handle authentication
break;
case 402:
// Handle payment
break;
case 429:
// Handle rate limiting
break;
default:
// Handle other errors
}
}2. Implement Retry Logic
async function apiCallWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
if (response.ok) {
return response;
}
// Retry server errors (5xx)
if (attempt === maxRetries) {
throw new Error(`Max retries reached: ${response.status}`);
}
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
}3. Handle Rate Limiting
async function handleRateLimit(response) {
if (response.status === 429) {
const error = await response.json();
const retryAfter = error.retry_after || 60;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise(resolve =>
setTimeout(resolve, retryAfter * 1000)
);
return true; // Indicate retry needed
}
return false;
}4. Log Errors Properly
function logApiError(response, error, context) {
console.error('API Error:', {
status: response.status,
code: error.code,
message: error.error,
details: error.details,
requestId: error.request_id,
context,
timestamp: new Date().toISOString()
});
}5. Provide User-Friendly Messages
function getUserFriendlyMessage(status, error) {
switch (status) {
case 400:
return 'Please check your input and try again';
case 401:
return 'Please connect your wallet to continue';
case 402:
return 'This content requires payment to access';
case 403:
return 'You don\'t have permission for this action';
case 404:
return 'The requested content was not found';
case 429:
return 'Too many requests. Please wait a moment';
case 500:
return 'Server error. Please try again later';
default:
return error.error || 'An unexpected error occurred';
}
}Related
- SDK Error Handling - Client-side error handling
- x402 Payments - Handle payment errors
- Testing Guide - Test error scenarios