Skip to main content
This guide covers how to handle errors from the Triqai API, including common error codes, troubleshooting tips, and best practices.

Error Response Format

All error responses follow a consistent structure:
{
  "success": false,
  "error": {
    "code": "error_code",
    "message": "Human-readable description",
    "details": { /* optional additional info */ }
  },
  "meta": {
    "generatedAt": "2026-01-19T10:30:00Z",
    "requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a123",
    "version": "0.1.0"
  }
}

Error Codes

Authentication Errors (401)

CodeMessageSolution
authentication_errorInvalid or missing API keyCheck your X-API-Key header
authentication_errorInvalid API key formatEnsure key starts with triq_
if (error.code === 'authentication_error') {
  // Check API key configuration
  console.error('API key invalid. Please check your configuration.');
}

Payment Errors (402)

CodeMessageSolution
insufficient_creditsInsufficient creditsEnable overages or upgrade plan
if (error.code === 'insufficient_credits') {
  // Credit balance exhausted
  // Option 1: Notify user
  // Option 2: Queue for later processing
  // Option 3: Enable overages in dashboard
}

Not Found Errors (404)

CodeMessageSolution
not_foundResource not foundVerify the ID exists
if (error.code === 'not_found') {
  // Transaction, merchant, or other resource doesn't exist
  console.error('Resource not found:', requestId);
}

Validation Errors (422)

CodeMessageDetails
validation_errorValidation failedfieldErrors object with per-field errors
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "fieldErrors": {
        "title": ["Title is required"],
        "country": ["Invalid country code. Use ISO 3166-1 alpha-2 format."],
        "type": ["Type must be 'expense' or 'income'"]
      }
    }
  }
}
if (error.code === 'validation_error' && error.details?.fieldErrors) {
  for (const [field, messages] of Object.entries(error.details.fieldErrors)) {
    console.error(`${field}: ${messages.join(', ')}`);
  }
}

Rate Limit Errors (429)

CodeMessageHeaders
rate_limitedRate limit exceededRetry-After, X-RateLimit-*
if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After');
  console.log(`Rate limited. Retry after ${retryAfter}ms`);
  await sleep(parseInt(retryAfter));
  // Retry the request
}

Server Errors (500)

CodeMessageSolution
internal_errorAn unexpected error occurredRetry with exponential backoff
if (error.code === 'internal_error') {
  // Temporary server issue - retry with backoff
  await retryWithBackoff(request, { maxRetries: 3 });
}

Implementing Retry Logic

Exponential Backoff

async function retryWithBackoff(requestFn, options = {}) {
  const { maxRetries = 3, baseDelay = 1000 } = options;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await requestFn();
    } catch (error) {
      // Don't retry client errors (4xx except 429)
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error;
      }
      
      if (attempt === maxRetries - 1) {
        throw error;
      }
      
      const delay = baseDelay * Math.pow(2, attempt);
      const jitter = Math.random() * 1000;
      await sleep(delay + jitter);
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Rate Limit Aware Retries

async function enrichWithRateLimitHandling(transaction) {
  const maxRetries = 5;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch('https://api.triqai.com/v1/transactions/enrich', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': process.env.TRIQAI_API_KEY
      },
      body: JSON.stringify(transaction)
    });

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '1000');
      console.log(`Rate limited. Waiting ${retryAfter}ms...`);
      await sleep(retryAfter);
      continue;
    }

    return await response.json();
  }

  throw new Error('Max retries exceeded');
}

Error Handling Pattern

Here’s a complete error handling implementation:
class TriqaiClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.triqai.com';
  }

  async enrich(transaction) {
    const response = await this.request('POST', '/v1/transactions/enrich', transaction);
    return response;
  }

  async request(method, path, body = null) {
    const options = {
      method,
      headers: {
        'Content-Type': 'application/json',
        'X-API-Key': this.apiKey
      }
    };

    if (body) {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(`${this.baseUrl}${path}`, options);
    const data = await response.json();

    if (!response.ok) {
      throw new TriqaiError(data.error, response.status, data.meta?.requestId);
    }

    return data;
  }
}

class TriqaiError extends Error {
  constructor(error, status, requestId) {
    super(error.message);
    this.name = 'TriqaiError';
    this.code = error.code;
    this.status = status;
    this.requestId = requestId;
    this.details = error.details;
  }

  isRetryable() {
    return this.status === 429 || this.status >= 500;
  }

  isAuthError() {
    return this.code === 'authentication_error';
  }

  isRateLimited() {
    return this.code === 'rate_limited';
  }

  isValidationError() {
    return this.code === 'validation_error';
  }
}

// Usage
try {
  const result = await client.enrich(transaction);
} catch (error) {
  if (error instanceof TriqaiError) {
    if (error.isAuthError()) {
      // Re-authenticate or check API key
    } else if (error.isRateLimited()) {
      // Wait and retry
    } else if (error.isValidationError()) {
      // Fix request data
      console.log('Validation errors:', error.details?.fieldErrors);
    } else if (error.isRetryable()) {
      // Retry with backoff
    }
  }
}

Logging and Monitoring

Request Logging

async function enrichWithLogging(transaction) {
  const startTime = Date.now();
  
  try {
    const result = await client.enrich(transaction);
    
    console.log({
      event: 'enrichment_success',
      requestId: result.meta.requestId,
      duration: Date.now() - startTime,
      partial: result.partial
    });
    
    return result;
  } catch (error) {
    console.error({
      event: 'enrichment_error',
      requestId: error.requestId,
      code: error.code,
      duration: Date.now() - startTime
    });
    
    throw error;
  }
}

Error Tracking

function trackError(error, context = {}) {
  // Send to your error tracking service (Sentry, etc.)
  errorTracker.captureException(error, {
    tags: {
      service: 'triqai',
      errorCode: error.code
    },
    extra: {
      requestId: error.requestId,
      ...context
    }
  });
}

Best Practices

The meta.requestId helps with debugging and support requests.
If errors persist, temporarily stop requests to prevent cascading failures.
Partial results (partial: true) are successes with some failures handle them differently from full failures.
Catch validation errors client-side when possible to reduce failed API calls.
Create error classes that make it easy to check error types and extract details.

Next Steps