Error Handling and Retry Mechanisms
Overview
- Function: Comprehensive error handling and retry strategies for AgentTech SDK operations
- Use Cases: Handle API errors, network failures, rate limiting, implement robust retry logic
- Authentication: Applies to all operations (with or without authentication)
Error Types
1. RequestError
Returned for HTTP 4xx/5xx responses from the API.
TypeScript/JavaScript:
import { PayApiError } from '@cross402/usdc';
try {
const intent = await client.createIntent({...});
} catch (error) {
if (error instanceof PayApiError) {
console.log(`HTTP ${error.statusCode}: ${error.body}`);
// Handle specific status codes
switch (error.statusCode) {
case 400:
console.error('Bad request:', error.body);
break;
case 401:
console.error('Unauthorized - check credentials');
break;
case 429:
console.error('Rate limited');
break;
}
}
}
Go:
import "github.com/cross402/usdc-sdk-go"
resp, err := client.CreateIntent(ctx, &pay.CreateIntentRequest{...})
var reqErr *pay.RequestError
if errors.As(err, &reqErr) {
log.Printf("HTTP %d: %s", reqErr.StatusCode, reqErr.Body)
switch reqErr.StatusCode {
case 400:
log.Println("Bad request:", reqErr.Body)
case 401:
log.Println("Unauthorized - check credentials")
case 429:
log.Println("Rate limited")
}
}
2. ValidationError
Returned when the SDK rejects a request before it reaches the API (e.g., empty intent ID).
Go:
var valErr *pay.ValidationError
if errors.As(err, &valErr) {
log.Printf("Invalid input: %s", valErr.Message)
// Check for specific validation errors
if errors.Is(err, pay.ErrEmptyIntentID) {
log.Println("Intent ID was empty")
}
}
3. UnexpectedError
Wraps unexpected internal errors (JSON marshal failure, request creation, etc.).
Go:
var unexpErr *pay.UnexpectedError
if errors.As(err, &unexpErr) {
log.Printf("Unexpected error: %v", unexpErr.Err)
}
HTTP Status Codes
Retry Strategies
1. Simple Retry with Fixed Delay
async function retryWithFixedDelay<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
delayMs: number = 1000
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
// Only retry on retryable errors
if (error instanceof PayApiError) {
if (error.statusCode === 429 || error.statusCode === 503) {
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
2. Exponential Backoff
async function retryWithExponentialBackoff<T>(
fn: () => Promise<T>,
maxRetries: number = 5,
initialDelayMs: number = 1000
): Promise<T> {
let delay = initialDelayMs;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
if (error instanceof PayApiError) {
// Only retry on retryable errors
if (error.statusCode === 429 || error.statusCode === 503) {
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 30000); // Max 30 seconds
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
3. Exponential Backoff with Jitter
function getDelayWithJitter(baseDelay: number): number {
// Add random jitter (±25%)
const jitter = baseDelay * 0.25 * (Math.random() * 2 - 1);
return Math.max(0, baseDelay + jitter);
}
async function retryWithJitter<T>(
fn: () => Promise<T>,
maxRetries: number = 5,
initialDelayMs: number = 1000
): Promise<T> {
let delay = initialDelayMs;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
if (error instanceof PayApiError) {
if (error.statusCode === 429 || error.statusCode === 503) {
const jitteredDelay = getDelayWithJitter(delay);
console.log(`Retry ${i + 1}/${maxRetries} after ${jitteredDelay}ms`);
await new Promise(resolve => setTimeout(resolve, jitteredDelay));
delay = Math.min(delay * 2, 30000);
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
Go Retry Implementation
Exponential Backoff
package main
import (
"context"
"errors"
"fmt"
"math"
"time"
"github.com/cross402/usdc-sdk-go"
)
func retryWithBackoff(ctx context.Context, maxRetries int, fn func() error) error {
delay := time.Second
maxDelay := 30 * time.Second
for i := 0; i < maxRetries; i++ {
err := fn()
if err == nil {
return nil
}
if i == maxRetries-1 {
return err
}
// Check if error is retryable
var reqErr *pay.RequestError
if errors.As(err, &reqErr) {
if reqErr.StatusCode == 429 || reqErr.StatusCode == 503 {
fmt.Printf("Retry %d/%d after %v\n", i+1, maxRetries, delay)
time.Sleep(delay)
delay = time.Duration(math.Min(float64(delay*2), float64(maxDelay)))
continue
}
}
return err
}
return fmt.Errorf("max retries exceeded")
}
Rate Limiting Handling
Handle HTTP 429
async function handleRateLimit<T>(fn: () => Promise<T>): Promise<T> {
let delay = 1000;
const maxDelay = 60000; // 1 minute
let retries = 0;
const maxRetries = 10;
while (retries < maxRetries) {
try {
return await fn();
} catch (error) {
if (error instanceof PayApiError && error.statusCode === 429) {
retries++;
console.log(`Rate limited, waiting ${delay}ms (retry ${retries}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
continue;
}
throw error;
}
}
throw new Error('Rate limit retries exceeded');
}
Complete Error Handling Example
TypeScript/JavaScript
import { PayClient, PayApiError, PayValidationError } from '@cross402/usdc';
async function createIntentWithRetry(
client: PayClient,
params: {
email: string;
amount: string;
payerChain: string;
}
): Promise<any> {
const maxRetries = 5;
let delay = 1000;
const maxDelay = 30000;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.createIntent(params);
} catch (error) {
// Don't retry on validation errors
if (error instanceof PayValidationError) {
throw error;
}
// Don't retry on client errors (4xx except 429)
if (error instanceof PayApiError) {
if (error.statusCode === 400 ||
error.statusCode === 401 ||
error.statusCode === 403 ||
error.statusCode === 404) {
throw error;
}
// Retry on rate limit and server errors
if (error.statusCode === 429 || error.statusCode === 503) {
if (attempt === maxRetries - 1) {
throw error;
}
console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
continue;
}
}
// Retry on network errors
if (attempt === maxRetries - 1) {
throw error;
}
console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
}
}
throw new Error('Max retries exceeded');
}
Go
package main
import (
"context"
"errors"
"log"
"math"
"time"
"github.com/cross402/usdc-sdk-go"
)
func createIntentWithRetry(
ctx context.Context,
client *pay.Client,
req *pay.CreateIntentRequest,
) (*pay.CreateIntentResponse, error) {
maxRetries := 5
delay := time.Second
maxDelay := 30 * time.Second
for attempt := 0; attempt < maxRetries; attempt++ {
resp, err := client.CreateIntent(ctx, req)
if err == nil {
return resp, nil
}
// Don't retry on validation errors
var valErr *pay.ValidationError
if errors.As(err, &valErr) {
return nil, err
}
// Don't retry on client errors (4xx except 429)
var reqErr *pay.RequestError
if errors.As(err, &reqErr) {
if reqErr.StatusCode == 400 ||
reqErr.StatusCode == 401 ||
reqErr.StatusCode == 403 ||
reqErr.StatusCode == 404 {
return nil, err
}
// Retry on rate limit and server errors
if reqErr.StatusCode == 429 || reqErr.StatusCode == 503 {
if attempt == maxRetries-1 {
return nil, err
}
log.Printf("Retry %d/%d after %v", attempt+1, maxRetries, delay)
time.Sleep(delay)
delay = time.Duration(math.Min(float64(delay*2), float64(maxDelay)))
continue
}
}
// Retry on other errors
if attempt == maxRetries-1 {
return nil, err
}
log.Printf("Retry %d/%d after %v", attempt+1, maxRetries, delay)
time.Sleep(delay)
delay = time.Duration(math.Min(float64(delay*2), float64(maxDelay)))
}
return nil, errors.New("max retries exceeded")
}
Best Practices
1. Retry Only Transient Errors
- Retry: 429 (rate limit), 503 (service unavailable), network errors
- Don't Retry: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found)
2. Use Exponential Backoff
- Start with short delays (1-2 seconds)
- Double delay on each retry
- Cap maximum delay (e.g., 30 seconds)
3. Add Jitter
- Prevents thundering herd problem
- Randomize delay slightly (±25%)
4. Set Maximum Retries
- Avoid infinite retry loops
- Typical: 3-5 retries for transient errors
- More retries for critical operations
5. Log Retry Attempts
- Track retry attempts for debugging
- Monitor retry rates for system health
6. Handle Timeouts
- Set request timeouts
- Set overall operation timeouts
- Fail fast on timeout
Sentinel Errors
Use errors.Is (Go) to check for specific validation failures:
Go Example:
if errors.Is(err, pay.ErrEmptyIntentID) {
log.Println("Intent ID was empty")
}
Related Links
- Create Intent
- Execute Intent
- Submit Proof
- Query Intent Status
- Payment Polling
- API Documentation: Error Codes