The official Node.js / TypeScript SDK for the Triqai Transaction Enrichment API.
Enrich raw bank transaction descriptions into structured data: merchants, categories, locations, intermediaries, and more.
npm install triqaiyarn add triqaipnpm add triqaiimport Triqai from "triqai";
const triqai = new Triqai("triq_your_api_key");
const result = await triqai.transactions.enrich({
title: "STARBUCKS SEATTLE WA",
country: "US",
type: "expense",
});
console.log(result.data.transaction.category.primary.name);
// => "Food & Dining"
console.log(result.data.entities);
// => [{ type: "merchant", data: { name: "Starbucks", ... } }, ...]- Node.js 18+ (uses native
fetch) - Also works in Bun, Deno, and Cloudflare Workers
const triqai = new Triqai("triq_your_api_key", {
// Base URL (default: https://api.triqai.com)
baseUrl: "https://api.triqai.com",
// Retry configuration
maxRetries: 3, // default: 3
retryDelay: 500, // base delay in ms (default: 500)
maxRetryDelay: 30_000, // max delay in ms (default: 30000)
// Request timeout in ms (default: 60000)
timeout: 60_000,
// Extra headers for every request
defaultHeaders: {
"X-Custom-Header": "value",
},
// Debug hooks
onRequest: (info) => console.log(`${info.method} ${info.url}`),
onResponse: (info) => console.log(`${info.status} in ${info.durationMs}ms`),
});const result = await triqai.transactions.enrich({
title: "AMAZON MKTPLACE PMTS AMZN.COM/BILL WA",
country: "US",
type: "expense",
options: {
// Skip specific entity extraction
filters: {
noLocation: true,
noIntermediary: true,
},
// Pre-fill known data to improve accuracy
merchant: {
name: "Amazon",
},
},
});
// API v1.1.11+: `data.transaction.channel` is no longer returned.
// Confidence reasons are dynamic and may include tags like:
// `venue_or_attraction`, `merchant_location_crosscheck_corrected`,
// `merchant_location_crosscheck_recovered`, `merchant_location_mismatch`, `geo_mismatch`.const result = await triqai.transactions.enrich(
{ title: "STARBUCKS", country: "US", type: "expense" },
{ idempotencyKey: "my-unique-key-123", force: false },
);// Get a single page
const page = await triqai.transactions.list({
page: 1,
size: 50,
startDate: "2026-01-01T00:00:00Z",
endDate: "2026-03-01T00:00:00Z",
});
console.log(page.data); // EnrichedTransaction[]
console.log(page.pageInfo); // { page, size, total, totalPages }
// Navigate pages
if (page.hasNextPage) {
const nextPage = await page.nextPage();
}const page = await triqai.transactions.list();
for await (const tx of page) {
console.log(tx.id, tx.raw, tx.status);
}const tx = await triqai.transactions.get("550e8400-e29b-41d4-a716-446655440000");const { deleted, transactionId } = await triqai.transactions.delete(
"550e8400-e29b-41d4-a716-446655440000",
);const { categories, total, categoryVersion } = await triqai.categories.list();
for (const cat of categories) {
console.log(`${cat.name} (level ${cat.level}, type: ${cat.type})`);
}const merchant = await triqai.merchants.get("merchant-uuid");
console.log(merchant.name, merchant.website, merchant.icon);const location = await triqai.locations.get("location-uuid");
console.log(location.formatted, location.structured.city);const intermediary = await triqai.intermediaries.get("intermediary-uuid");
console.log(intermediary.name); // "PayPal", "Square", etc.const report = await triqai.issueReports.create({
transactionId: "tx-uuid",
description: "Merchant was identified incorrectly",
fields: ["entities.merchant.data.name"],
});const page = await triqai.issueReports.list({
status: "pending",
transactionId: "tx-uuid",
});
// Auto-paginate
for await (const report of page) {
console.log(report.id, report.status);
}const report = await triqai.issueReports.get("report-uuid");All API errors are thrown as TriqaiError. Common HTTP statuses are mapped to typed subclasses:
import Triqai, {
TriqaiError,
AuthenticationError,
ValidationError,
RateLimitError,
InsufficientCreditsError,
NotFoundError,
} from "triqai";
try {
await triqai.transactions.enrich({ title: "", country: "US", type: "expense" });
} catch (err) {
if (err instanceof ValidationError) {
console.log("Field errors:", err.fieldErrors);
// { title: ["Title cannot be empty"] }
} else if (err instanceof RateLimitError) {
console.log("Retry after:", err.rateLimitInfo.retryAfter, "seconds");
} else if (err instanceof AuthenticationError) {
console.log("Check your API key");
} else if (err instanceof InsufficientCreditsError) {
console.log("Top up credits at https://triqai.com/dashboard");
} else if (err instanceof NotFoundError) {
console.log("Resource not found");
} else if (err instanceof TriqaiError) {
console.log(`API error ${err.statusCode}: ${err.message} [${err.code}]`);
console.log("Request ID:", err.requestId);
}
}| Class | Status | When |
|---|---|---|
AuthenticationError |
401 | Invalid or missing API key |
InsufficientCreditsError |
402 | No credits remaining |
AuthorizationError |
403 | Key valid but not authorized |
NotFoundError |
404 | Resource does not exist |
ClientDisconnectedError |
499 | Client disconnected before completion |
DuplicateRequestError |
409 | Idempotency key reused |
ValidationError |
422 | Request body validation failed |
RateLimitError |
429 | Rate limit exceeded |
InternalServerError |
500 | Server error (retried automatically) |
ServiceUnavailableError |
503 | Service temporarily down (retried automatically) |
GatewayTimeoutError |
504 | Upstream timeout |
ConnectionError |
- | Network/DNS failure |
TimeoutError |
- | Request exceeded timeout |
The SDK automatically retries on transient errors (429, 500, 503, 504, and network errors) with exponential backoff:
- GET and DELETE requests are always retried
- POST requests are only retried when an
idempotencyKeyis provided - The
Retry-Afterheader is respected when present - Default: 3 retries, 500ms base delay, 30s max delay
Disable retries:
const triqai = new Triqai("triq_your_api_key", { maxRetries: 0 });Rate limit information is available on successful responses. The SDK handles 429 responses automatically, but you can monitor usage via the onResponse hook or rawGet/rawPost/rawDelete methods:
// Option 1: Debug hook
const triqai = new Triqai("triq_your_api_key", {
onResponse: (info) => {
// info.headers contains X-RateLimit-* values
},
});
// Option 2: Raw request with full response
const resp = await triqai.rawGet("/v1/categories");
console.log(resp.rateLimitInfo.remaining); // tokens left
console.log(resp.rateLimitInfo.limit); // max tokens
console.log(resp.rateLimitInfo.concurrencyRemaining); // concurrent slots left// Check API health
const health = await triqai.health();
console.log(health.data.status); // "healthy"
// Get API version and endpoint directory
const info = await triqai.apiInfo();
console.log(info.data.version); // "v1"
console.log(info.data.endpoints);For endpoints not yet covered by a resource class, or when you need the full HTTP response (headers, rate limit info):
import type { HttpResponse } from "triqai";
// Full response with headers and rate limit info
const resp: HttpResponse<any> = await triqai.rawGet("/v1/categories");
console.log(resp.data); // response body
console.log(resp.rateLimitInfo); // { limit, remaining, ... }The Triqai API uses a credit-based pricing model. See triqai.com/pricing for details.
This package is written in TypeScript and ships with full type definitions. All request/response types are exported:
import type {
EnrichRequest,
EnrichSuccessResponse,
EnrichedTransaction,
MerchantData,
LocationData,
CategoryInfo,
EntityResult,
TransactionType,
EnrichmentFieldPath,
} from "triqai";See CONTRIBUTING.md for development setup and guidelines.
MIT - see LICENSE for details.
The Triqai API itself is a commercial product. This SDK is open source, but API usage requires an API key and is subject to Triqai's terms of service.