Beginner7 min read
Edit on GitHub

Error Handling

Handle API errors gracefully in your applications

Learn how to handle errors from the Nacho API and build resilient applications.

Error Response Format

All errors follow the JSON-RPC 2.0 error format:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32600,
    "message": "Invalid request",
    "data": { "details": "Additional context" }
  },
  "id": "request-123"
}

Error Categories

JSON-RPC Standard Errors

CodeMessageDescription
-32700Parse errorInvalid JSON received
-32600Invalid requestMissing required fields
-32601Method not foundUnknown method name
-32602Invalid paramsWrong parameter types
-32603Internal errorServer-side error

WebSocket-Specific Errors

CodeMessageDescription
-32029Rate limit exceededWebSocket message rate limit exceeded

The rate limit error includes additional data:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32029,
    "message": "Rate limit exceeded. Please slow down.",
    "data": {
      "retryAfter": 1000,
      "remaining": 0,
      "limit": 100
    }
  },
  "id": "request-id"
}

HTTP Errors

CodeMessageCause
401UnauthorizedMissing or invalid API key
403ForbiddenAPI key inactive or insufficient permissions
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side issue
502Bad GatewayUpstream service unavailable

Transaction Errors (3000-3999)

Transaction submission can fail for many reasons:

CodeMessageSolution
3001Insufficient fundsAdd more ADA to cover fees
3002Invalid signatureCheck transaction signing
3003Expired TTLRebuild with future TTL
3004Script execution failedDebug Plutus script

Handling Errors in Code

async function safeQuery(method, params = {}) {
try {
  const response = await fetch('https://api.nacho.builders/v1/ogmios', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'apikey': process.env.NACHO_API_KEY
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method,
      params
    })
  });

  // Handle HTTP errors
  if (!response.ok) {
    if (response.status === 429) {
      // Rate limited - wait and retry
      await sleep(1000);
      return safeQuery(method, params);
    }
    throw new Error(`HTTP ${response.status}`);
  }

  const data = await response.json();

  // Handle JSON-RPC errors
  if (data.error) {
    throw new Error(`[${data.error.code}] ${data.error.message}`);
  }

  return data.result;
} catch (error) {
  console.error('API Error:', error.message);
  throw error;
}
}

Retry Strategies

Exponential Backoff

For rate limits and transient errors:

/**
 * Retry a function with exponential backoff on failure.
 * Delays increase: 1s -> 2s -> 4s -> 8s (capped at 10s)
 *
 * @param fn - Async function to execute
 * @param maxRetries - Maximum number of attempts
 */
async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();  // Try to execute the function
    } catch (error) {
      // If this was the last attempt, give up and throw
      if (attempt === maxRetries - 1) throw error;

      // Calculate delay with exponential backoff: 1s, 2s, 4s, etc.
      // Cap at 10 seconds to avoid excessive waits
      const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Circuit Breaker

For production applications, implement a circuit breaker:

/**
 * Circuit breaker pattern for fault tolerance.
 *
 * States:
 * - CLOSED: Normal operation, requests pass through
 * - OPEN: Too many failures, requests are rejected immediately
 * - HALF-OPEN: Testing if service recovered, allows one request
 *
 * This prevents cascading failures when a service is down.
 */
class CircuitBreaker {
  constructor(threshold = 5, resetTimeout = 30000) {
    this.failures = 0;           // Consecutive failure count
    this.threshold = threshold;  // Failures before opening circuit
    this.resetTimeout = resetTimeout;  // Time before trying again (ms)
    this.state = 'closed';       // Start in normal operation mode
  }

  async call(fn) {
    // Reject immediately if circuit is open (service is down)
    if (this.state === 'open') {
      throw new Error('Circuit breaker is open');
    }

    try {
      const result = await fn();
      this.onSuccess();  // Reset on success
      return result;
    } catch (error) {
      this.onFailure();  // Track failure
      throw error;
    }
  }

  onSuccess() {
    // Success - reset failure count and close circuit
    this.failures = 0;
    this.state = 'closed';
  }

  onFailure() {
    this.failures++;
    // Too many failures - open the circuit
    if (this.failures >= this.threshold) {
      this.state = 'open';
      // After timeout, allow one test request (half-open)
      setTimeout(() => {
        this.state = 'half-open';
      }, this.resetTimeout);
    }
  }
}

Common Issues

"Method not found"

Check the method name spelling. Valid methods include:

  • queryLedgerState/epoch (not queryLedgerState/Epoch)
  • queryNetwork/tip (not queryNetworkTip)

"Invalid params"

Ensure parameters match expected types:

// Wrong - string instead of object
{ "params": "addr1..." }

// Correct - object with named parameter
{ "params": { "addresses": ["addr1..."] } }

Rate Limit Exceeded

If you're hitting rate limits:

  1. Implement request queuing
  2. Use WebSocket for multiple queries (single connection)
  3. Consider upgrading to PAID tier (500 req/s)

Use WebSocket connections for high-frequency queries. A single WebSocket can handle many requests without per-request overhead.

Was this page helpful?