Skip to main content
Follow these best practices to get the most out of the Triqai API while building reliable, efficient integrations.

Data Quality

Send Complete Transaction Data

Include the full, original transaction string:
{
  "title": "POS 4392 STARBUCKS STORE #1234 NEW YORK NY 10001",
  "country": "US",
  "type": "expense"
}

Use Accurate Country Codes

The country code significantly affects matching accuracy:
  • Use the transaction’s origin country, not user’s country
  • Use ISO 3166-1 alpha-2 codes (US, NL, GB)
  • Default to account country if unknown

Set Correct Transaction Type

The type field affects category selection:
TransactionType
Purchases, payments, feesexpense
Salary, refunds, depositsincome

Performance Optimization

Deduplicate Similar Transactions

Group identical transactions before enriching:
function deduplicateTransactions(transactions) {
  const groups = new Map();
  
  for (const tx of transactions) {
    const key = `${tx.title.toUpperCase()}|${tx.country}|${tx.type}`;
    if (!groups.has(key)) {
      groups.set(key, { representative: tx, all: [] });
    }
    groups.get(key).all.push(tx);
  }
  
  return groups;
}

async function enrichBatch(transactions) {
  const groups = deduplicateTransactions(transactions);
  const results = new Map();
  
  for (const [key, group] of groups) {
    const result = await enrichTransaction(group.representative);
    // Apply to all transactions in group
    for (const tx of group.all) {
      results.set(tx.id, result);
    }
  }
  
  return results;
}

Rate Limit Management

Control request flow to stay within limits:
class RequestQueue {
  constructor(requestsPerSecond) {
    this.interval = 1000 / requestsPerSecond;
    this.lastRequest = 0;
  }

  async throttle() {
    const now = Date.now();
    const timeSinceLastRequest = now - this.lastRequest;
    
    if (timeSinceLastRequest < this.interval) {
      await sleep(this.interval - timeSinceLastRequest);
    }
    
    this.lastRequest = Date.now();
  }

  async request(fn) {
    await this.throttle();
    return fn();
  }
}

// Usage: 5 requests per second
const queue = new RequestQueue(5);

for (const tx of transactions) {
  await queue.request(() => enrichTransaction(tx));
}

Error Handling

Implement Robust Retry Logic

async function enrichWithRetry(transaction, options = {}) {
  const {
    maxRetries = 3,
    retryableStatuses = [429, 500, 502, 503, 504]
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await enrichTransaction(transaction);
    } catch (error) {
      const shouldRetry = 
        attempt < maxRetries && 
        retryableStatuses.includes(error.status);

      if (!shouldRetry) throw error;

      const delay = error.status === 429
        ? parseInt(error.retryAfter || '1000')
        : Math.pow(2, attempt) * 1000;

      await sleep(delay);
    }
  }
}

Handle Partial Results

Don’t discard partial results, use available data:
function processResult(result) {
  if (!result.success) {
    return handleError(result.error);
  }

  // Even if partial, extract what's available
  const processed = {
    merchant: null,
    location: null,
    category: result.data.transaction.category
  };

  if (result.data.enrichments.merchant.status === 'found') {
    processed.merchant = result.data.enrichments.merchant.data;
  }

  if (result.data.enrichments.location.status === 'found') {
    processed.location = result.data.enrichments.location.data;
  }

  // Log partial results for monitoring
  if (result.partial) {
    console.warn('Partial result:', result.meta.errors);
  }

  return processed;
}

Security

Protect API Keys

  • Store keys in environment variables
  • Never commit keys to version control
  • Use separate keys for dev/staging/prod
  • Rotate keys periodically
// Good: Environment variable
const apiKey = process.env.TRIQAI_API_KEY;

// Bad: Hardcoded
const apiKey = 'triq_abc123...';  // Never do this!

Make Requests Server-Side

Never expose your API key in client-side code:
// Server-side endpoint
app.post('/api/enrich', async (req, res) => {
  const result = await triqaiClient.enrich({
    title: req.body.title,
    country: req.body.country,
    type: req.body.type
  });
  
  res.json(result);
});

Monitoring and Observability

Track Key Metrics

Monitor these metrics for your integration:
MetricWhy It Matters
Success rateDetect issues early
Latency (p50, p95, p99)Identify performance problems
Partial result rateTrack data quality
Error rate by codeUnderstand failure patterns
Credit consumptionManage costs

Structured Logging

function logEnrichment(transaction, result, duration) {
  console.log(JSON.stringify({
    event: 'enrichment',
    timestamp: new Date().toISOString(),
    requestId: result.meta.requestId,
    success: result.success,
    partial: result.partial,
    durationMs: duration,
    merchantFound: result.data?.enrichments?.merchant?.status === 'found',
    confidence: result.data?.transaction?.confidence
  }));
}

Architecture Patterns

Async Processing for Bulk Data

For large volumes, process asynchronously:
// Producer: Queue transactions
async function queueTransactions(transactions) {
  for (const tx of transactions) {
    await messageQueue.send({
      type: 'enrich',
      payload: tx
    });
  }
}

// Consumer: Process from queue
async function processQueue() {
  while (true) {
    const message = await messageQueue.receive();
    const result = await enrichWithRetry(message.payload);
    await saveResult(message.payload.id, result);
  }
}

Graceful Degradation

Design for partial failures:
async function getTransactionDisplay(tx) {
  try {
    const enrichment = await enrichTransaction(tx);
    return formatEnrichedTransaction(tx, enrichment);
  } catch (error) {
    // Fall back to raw display
    console.error('Enrichment failed:', error);
    return formatRawTransaction(tx);
  }
}

Checklist

Use this checklist when building your integration:
  • API key stored securely in environment
  • Full transaction strings sent, not truncated
  • Caching implemented for duplicate transactions
  • Retry logic with exponential backoff
  • Rate limiting handled gracefully
  • Partial results processed correctly
  • Errors logged with requestId
  • Metrics and monitoring in place
  • Credit usage tracked

Next Steps