Beginner7 min read
Edit on GitHubError 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
| Code | Message | Description |
|---|---|---|
-32700 | Parse error | Invalid JSON received |
-32600 | Invalid request | Missing required fields |
-32601 | Method not found | Unknown method name |
-32602 | Invalid params | Wrong parameter types |
-32603 | Internal error | Server-side error |
WebSocket-Specific Errors
| Code | Message | Description |
|---|---|---|
-32029 | Rate limit exceeded | WebSocket 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
| Code | Message | Cause |
|---|---|---|
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | API key inactive or insufficient permissions |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side issue |
502 | Bad Gateway | Upstream service unavailable |
Transaction Errors (3000-3999)
Transaction submission can fail for many reasons:
| Code | Message | Solution |
|---|---|---|
3001 | Insufficient funds | Add more ADA to cover fees |
3002 | Invalid signature | Check transaction signing |
3003 | Expired TTL | Rebuild with future TTL |
3004 | Script execution failed | Debug 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(notqueryLedgerState/Epoch)queryNetwork/tip(notqueryNetworkTip)
"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:
- Implement request queuing
- Use WebSocket for multiple queries (single connection)
- 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?