Skip to content

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

CodeStatusDescription
200OKRequest successful
201CreatedResource created successfully
204No ContentRequest 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
Example Response:
{
  "error": "Invalid request data",
  "code": "INVALID_REQUEST",
  "details": "Field 'name' is required"
}
How to Handle:
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
Example Response:
{
  "error": "Authentication required",
  "code": "UNAUTHORIZED"
}
How to Handle:
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
Example Response:
{
  "error": "Payment required",
  "code": "PAYMENT_REQUIRED",
  "x402": {
    "version": "1.0",
    "amount": "1000000",
    "currency": "USDC",
    "network": "base",
    "recipient": "0x...",
    "facilitator": "0x..."
  }
}
How to Handle:
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
Example Response:
{
  "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
Example Response:
{
  "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
Example Response:
{
  "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
Example Response:
{
  "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
Example Response:
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT",
  "retry_after": 60,
  "limit": 100,
  "window": 3600
}
How to Handle:
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 authentication
  • INVALID_SIGNATURE - Cryptographic signature invalid
  • EXPIRED_TIMESTAMP - Request timestamp too old
  • INVALID_WALLET - Wallet address invalid

Validation Errors

  • VALIDATION_ERROR - General validation failure
  • INVALID_REQUEST - Malformed request data
  • MISSING_REQUIRED_FIELD - Required field not provided
  • INVALID_FORMAT - Field format invalid

Resource Errors

  • NOT_FOUND - Resource doesn't exist
  • CONFLICT - Resource already exists
  • FORBIDDEN - Access denied
  • GONE - Resource permanently deleted

Payment Errors

  • PAYMENT_REQUIRED - x402 payment needed
  • PAYMENT_FAILED - Payment processing failed
  • INSUFFICIENT_BALANCE - Not enough funds
  • INVALID_PAYMENT - Payment data invalid

Rate Limiting

  • RATE_LIMIT - Too many requests
  • QUOTA_EXCEEDED - Usage quota exceeded

Server Errors

  • INTERNAL_ERROR - Server-side error
  • SERVICE_UNAVAILABLE - Service temporarily down
  • GATEWAY_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