Skip to content

Repository files navigation

tipalti-sdk

A production-grade TypeScript client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Zero runtime dependencies. Built entirely on Web-standard APIs — the native fetch, crypto.subtle (Web Crypto), URLSearchParams, AbortController — so it works unmodified in Node 18+, browsers, and edge runtimes (Cloudflare Workers, Deno, Bun) alike. Ships as dual ESM/CJS with full TypeScript declarations.

Installation

npm install tipalti-sdk

Quick start

import { TipaltiClient } from 'tipalti-sdk';

const client = new TipaltiClient({
  mode: 'sandbox',
  rest: { clientId: '...', clientSecret: '...' },
  soap: { payerName: '...', apiKey: '...' },
  procurement: { apiKey: '...' },
});

// Modern REST API
const page = await client.payees.list();

for await (const payee of client.payees.stream()) {
  console.log(payee.id, payee['name']);
}

// Legacy SOAP API
const result = await client.soap.payer.processPayments(
  [{ idap: 'vendor-123', amount: 100.0, currency: 'USD', refCode: 'pay-1' }],
  { paymentGroupTitle: 'Weekly payout' },
);

// Procurement REST API
const pos = await client.procurement.purchaseOrders.list();

Only populate the credential sections you actually use — a client built with just soap is fine as long as you only call client.soap.* methods. Unconfigured API families are undefined on the client instance (and typed that way), so accessing them without configuring them is a compile-time error, not a runtime surprise.

Error handling

Every error extends TipaltiError, so instanceof works uniformly, and each subclass carries the details specific to its failure kind:

import { AuthenticationError, RateLimitError, ValidationError, SoapFaultError } from 'tipalti-sdk';

try {
  await client.payees.get('p_123');
} catch (err) {
  if (err instanceof AuthenticationError) {
    // bad/expired credentials
  } else if (err instanceof RateLimitError) {
    // rate limited; err.retryAfterMs has the hint, if any
  } else if (err instanceof ValidationError) {
    // malformed request; err.errors has field-level details
  } else if (err instanceof SoapFaultError) {
    // SOAP <soap:Fault>; err.faultCode / err.faultString
  } else {
    throw err;
  }
}

Pagination

Every REST list method has a matching stream() returning a native AsyncGenerator — use for await...of, or the collect helper:

import { collect } from 'tipalti-sdk';

for await (const invoice of client.invoices.stream({ status: 'pending' })) {
  // ...
}

const all = await collect(client.invoices.stream({ status: 'pending' }));

SOAP signing

HMAC-SHA256 request signing (built on crypto.subtle, not a Node-specific crypto module) is handled automatically — every client.soap.payee/client.soap.payer method knows its operation's EAT (Encryption Additional Terms) parameter and folds it into the signed request for you. All 45 legacy operations (21 Payee + 24 Payer) are covered.

Procurement employee import

import { readFile } from 'node:fs/promises';

const csv = await readFile('employees.csv', 'utf8');
await client.procurement.employees.importEmployees(csv);

IPN webhooks

import { parseWebhook, webhookEventType } from 'tipalti-sdk';

app.post('/webhooks/tipalti', async (req, res) => {
  const event = parseWebhook(await req.text());
  handleEvent(webhookEventType(event), event);
  res.sendStatus(200);
});

Telemetry

const client = new TipaltiClient({
  rest: { clientId, clientSecret },
  onRequest: (event) => {
    console.log(
      `${event.api}.${event.operation} -> ${event.status ?? event.error} in ${event.durationMs}ms`,
    );
  },
});

Rate limiting

import { RateLimiter } from 'tipalti-sdk';

const limiter = new RateLimiter(5, 60_000); // Procurement API's documented PO-update limit
await limiter.wait();
await client.procurement.purchaseOrders.update(attrs);

Design notes

  • HTTP transport: built on native fetch with AbortController-based timeouts, exponential backoff + full jitter on retries, and a pluggable onRequest telemetry hook — no HTTP client dependency.
  • XML: SOAP responses are parsed with a small, purpose-built tokenizer (src/soap/xml.ts) rather than a general XML library — handles the nested-element/self-closing-tag/entity subset Tipalti's SOAP API actually produces, with namespace-prefix stripping so <soap:Fault> matches the same way local-name() would in XPath. Not a general-purpose XML parser by design.
  • REST endpoint shapes (Payees/Invoices/Payments) follow Tipalti's documented conventions for the modern REST API — see the doc comment on src/rest/client.ts if your instance's exact response envelope differs; the request/auth/error-handling machinery there is meant to be reused as-is.
  • Custom fetch implementation supported via TipaltiConfig.fetch, for non-standard runtimes or test injection.

Quality

npm run typecheck   # tsc --noEmit against both src/ and test/ (strict mode)
npm run lint         # eslint, typescript-eslint strict+stylistic type-checked rulesets
npm run format:check # prettier
npm run test          # vitest — 105 tests
npm run test:coverage # vitest --coverage
npm run build          # tsup — dual ESM/CJS + .d.ts

All clean, verified in a fresh checkout. Test coverage is ~84% statements overall — REST resource modules and the shared HTTP/OAuth2/ config layers are fully covered; the legacy SOAP Payee/Payer wrapper classes (45 thin methods total) are covered by a representative sample per class (idap handling, EAT-parameter extraction, nested/repeated field rendering) rather than one test per method, since they're low-risk, uniform pass-throughs onto the already-thoroughly-tested SoapClient.call engine.

Testing this package

The test suite uses a small node:http-based mock server (test/support/mockServer.ts) instead of nock/msw, keeping the dependency list minimal even for development/test.

npm test

License

MIT

About

A production-grade TypeScript client for the full Tipalti API surface: the modern OAuth2 REST API, the legacy HMAC-signed SOAP Payee/Payer API, and the Procurement REST API.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages