Guides
Use Cases
Transaction Categorization

Transaction Categorization

Enrich financial transactions with brand data — logos, merchant names, and industry classification — to build better banking and expense management experiences.

The Problem

Bank transaction descriptors are cryptic. STRIPE* VERCEL INC 800-555-0199 CA means nothing to most users. Fintech apps need to decode these into recognizable brands.

The Solution

Use Orsa's Transaction Identifier to match descriptors to brands, then enrich with logos, colors, and NAICS codes for categorization.

Pipeline

Raw Transaction → Orsa Transaction ID → Brand Data → Enriched Transaction
       ↓                    ↓                ↓               ↓
 "STRIPE* VERCEL"    domain: vercel.com   logo: ✓      "Vercel - Software"
                     confidence: 0.92     color: #000   category: "Technology"

Implementation

Single Transaction

import { Orsa } from 'orsa';
 
const orsa = new Orsa({ apiKey: process.env.ORSA_API_KEY });
 
async function enrichTransaction(descriptor: string, country?: string) {
  const result = await orsa.brand.transactionIdentifier({
    transactionInfo: descriptor,
    countryGl: country,
  });
 
  return {
    merchant: result.name,
    domain: result.domain,
    logo: result.logos[0]?.url,
    color: result.colors[0],
    category: result.industry,
    confidence: result.transaction.confidence,
  };
}
 
// Usage
const enriched = await enrichTransaction('STRIPE* VERCEL INC', 'US');
// { merchant: "Vercel", domain: "vercel.com", logo: "...", category: "Cloud Computing", confidence: 0.92 }

Python

from orsa import Orsa
 
orsa = Orsa(api_key=os.environ["ORSA_API_KEY"])
 
def enrich_transaction(descriptor: str, country: str = None) -> dict:
    result = orsa.brand.transaction_identifier(
        transaction_info=descriptor,
        country_gl=country,
    )
 
    return {
        "merchant": result.name,
        "domain": result.domain,
        "logo": result.logos[0].url if result.logos else None,
        "color": result.colors[0] if result.colors else None,
        "category": result.industry,
        "confidence": result.transaction.confidence,
    }

Batch Processing

async function enrichBatch(transactions: Array<{ id: string; descriptor: string }>) {
  const results = await Promise.allSettled(
    transactions.map(async (tx) => ({
      id: tx.id,
      ...(await enrichTransaction(tx.descriptor)),
    }))
  );
 
  return results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
}
 
// Process in chunks to respect rate limits
async function processAll(transactions: Array<{ id: string; descriptor: string }>) {
  const chunkSize = 20;
  const results = [];
 
  for (let i = 0; i < transactions.length; i += chunkSize) {
    const chunk = transactions.slice(i, i + chunkSize);
    const enriched = await enrichBatch(chunk);
    results.push(...enriched);
 
    // Respect rate limits
    if (i + chunkSize < transactions.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
 
  return results;
}

Adding NAICS Classification

For expense categorization, combine transaction identification with NAICS codes:

async function categorizeTransaction(descriptor: string) {
  const brand = await orsa.brand.transactionIdentifier({
    transactionInfo: descriptor,
  });
 
  // Get NAICS codes for expense categorization
  const naics = await orsa.brand.naics({ domain: brand.domain });
 
  return {
    merchant: brand.name,
    logo: brand.logos[0]?.url,
    naicsCode: naics.naics_codes[0]?.code,
    naicsTitle: naics.naics_codes[0]?.title,
    // Map NAICS to your expense categories
    expenseCategory: mapNaicsToCategory(naics.naics_codes[0]?.code),
  };
}
 
function mapNaicsToCategory(naicsCode: string): string {
  const prefix = naicsCode?.slice(0, 2);
  const mapping: Record<string, string> = {
    '44': 'Shopping',
    '45': 'Shopping',
    '51': 'Software & Technology',
    '52': 'Financial Services',
    '72': 'Food & Dining',
    '48': 'Transportation',
    '71': 'Entertainment',
  };
  return mapping[prefix] ?? 'Other';
}

Credit Cost

OperationEndpointCredits
Identify transactionGET /v1/brand/transaction-identifier10
NAICS classification (optional)GET /v1/brand/naics5
Per transaction10-15 credits

Tips

  • Use maxSpeed=true for real-time UIs where you need sub-second responses — it only checks cached data.
  • Cache results by normalized descriptor in your database. The same merchant string will appear repeatedly.
  • Pass country_gl when available — it significantly improves match accuracy for international transactions.
  • Handle low-confidence matches (< 0.7) by showing the raw descriptor alongside the brand match.
  • Batch process historical transactions during off-peak hours to avoid rate limits.