Skip to content

Repository files navigation

synctera-sdk

An unofficial, production-grade TypeScript SDK for the Synctera Banking-as-a-Service API.

  • Fully typed — 457 generated types and 174 endpoint methods across 28 resource groups, generated directly from Synctera's OpenAPI spec (never hand-guessed).
  • Zero runtime dependencies — built on native fetch, AbortController, and Node's crypto module.
  • Dual ESM/CJS with full .d.ts type declarations.
  • Correct idempotency semantics — Synctera has two genuinely different idempotency behaviors depending on the endpoint, and this SDK models both instead of pretending they're the same (see Idempotency below).
  • Automatic retries with exponential backoff + jitter, honoring Retry-After, limited strictly to the status codes Synctera's docs confirm are safe to retry (429 and 5xx).
  • Webhook signature verification — HMAC-SHA256, secret-rotation support, replay-window protection.
  • Cursor pagination helpers as async iterators.
  • Customer-Device-Info fraud-fingerprinting header built for you from a typed object.

⚠️ Important: spec currency

This SDK is generated from the most recent machine-readable OpenAPI spec I could obtain — the snapshot Synctera checks into their own client-libraries-go scaffold repo. That snapshot covers 114 paths / 174 operations across every core domain (customers, persons, businesses, accounts, cards, ACH, wires, internal transfers, transactions, documents, webhooks, KYC/KYB verification, watchlist monitoring, external accounts/cards, payment schedules, remote check deposit, reconciliations, applications, and sandbox simulations).

It does not yet include a handful of newer resources visible in Synctera's live docs but not present in that spec snapshot: Spend Controls, Evaluation Overrides, Merchants, Institutions, Enhanced Due Diligence (EDD), Customer Risk Rating (CRR), and Incoming Wires as a standalone resource. If you need these, see Regenerating against an updated spec — the codegen pipeline is designed so adding them is a one-command operation once you have an updated openapi.json, not a rewrite.

Install

npm install synctera-sdk

Quick start

import { SyncteraClient } from "synctera-sdk";

const client = new SyncteraClient({
  apiKey: process.env.SYNCTERA_API_KEY, // or omit and set SYNCTERA_API_KEY in the environment
  environment: "sandbox", // "sandbox" | "tMinus10" | "production" — default "sandbox"
});

const person = await client.persons.createPerson({
  body: {
    first_name: "Ada",
    last_name: "Lovelace",
    email: "ada@example.com",
  },
});

Idempotency

Synctera has two distinct idempotency behaviors, and this SDK does not paper over the difference:

  • Standard endpoints (most of the API): an Idempotency-Key is optional. A repeated request with the same key returns the original cached response for 7 days. This SDK auto-generates a key for you on every POST/PUT/PATCH unless you supply one — it's always safe to retry.

  • Ledger / money-movement endpoints (ACH, wires, internal transfers, RDC deposits): the key is required by Synctera, and a repeat with the same key is never served from cache — it's rejected with a 409 or 422. Because a silently-retried request would fail loudly here instead of replaying safely, this SDK will not auto-generate a key for these calls by default:

// Throws SyncteraUsageError — you must supply a key explicitly for ledger calls:
await client.ach.addTransactionOut({ body: { ... } });

// Correct: mint one key per logical operation, reuse it only when retrying that same operation.
const idempotencyKey = crypto.randomUUID();
await client.ach.addTransactionOut({ body: { ... }, idempotencyKey });

If you understand the tradeoff and want auto-generation on ledger calls anyway (e.g. you're doing your own outer-level retry coordination), opt in:

const client = new SyncteraClient({ autoGenerateLedgerIdempotencyKeys: true });

Pagination

All list endpoints return next_page_token. Use paginate for a lazy async iterator, or collectAll to gather everything into an array:

import { paginate, collectAll } from "synctera-sdk";

for await (const person of paginate(
  (params) => client.persons.listPersons({ query: params }),
  (page) => page.persons ?? [],
)) {
  console.log(person.id);
}

const allBusinesses = await collectAll(
  (params) => client.businesses.listBusinesses({ query: params }),
  (page) => page.businesses ?? [],
);

Webhook verification

import { verifyWebhookSignature, SyncteraWebhookSignatureError } from "synctera-sdk";

app.post("/webhooks/synctera", express.raw({ type: "application/json" }), (req, res) => {
  try {
    verifyWebhookSignature({
      payload: req.body, // raw Buffer/string — do NOT JSON.parse before verifying
      signatureHeader: req.header("synctera-signature") ?? "",
      timestampHeader: req.header("synctera-timestamp") ?? "",
      secret: process.env.SYNCTERA_WEBHOOK_SECRET!,
      // previousSecret: process.env.SYNCTERA_WEBHOOK_SECRET_PREVIOUS, // during rotation
    });
  } catch (err) {
    if (err instanceof SyncteraWebhookSignatureError) return res.status(400).send("Invalid signature");
    throw err;
  }

  const event = JSON.parse(req.body);
  // ... handle event
  res.sendStatus(200);
});

Device fingerprinting

await client.accounts.getAccount({
  path: { account_id: id },
  deviceInfo: { customerId: person.id!, ipAddress: req.ip, userAgent: req.get("user-agent") },
});

Or set it once for a whole server-side context (e.g. a batch job) via defaultDeviceInfo in the client config.

Error handling

import {
  SyncteraNotFoundError,
  SyncteraValidationError,
  SyncteraConflictError,
  SyncteraRateLimitError,
} from "synctera-sdk";

try {
  await client.persons.getPerson({ path: { person_id: id } });
} catch (err) {
  if (err instanceof SyncteraNotFoundError) {
    // 404
  } else if (err instanceof SyncteraValidationError) {
    // 422 — includes IDEMPOTENCY_INVALID_REUSE (same key, different payload)
    console.log(err.code, err.body);
  } else if (err instanceof SyncteraConflictError) {
    // 409 — includes IDEMPOTENCY_CONCURRENT_USE
  } else if (err instanceof SyncteraRateLimitError) {
    // 429 — already retried internally up to your configured limit
  }
  throw err;
}

Every error carries .status, .code (Synctera's machine-readable error code, when present), .body, .requestId, .url, and .method.

Configuration

new SyncteraClient({
  apiKey: "...",
  environment: "sandbox", // or "tMinus10" | "production"
  baseUrl: undefined, // override entirely, e.g. for a mock server in tests
  timeoutMs: 30_000,
  retry: { maxRetries: 2, baseDelayMs: 250, maxDelayMs: 8000 },
  autoGenerateLedgerIdempotencyKeys: false,
  defaultDeviceInfo: { customerId: "...", ipAddress: "..." },
  defaultHeaders: {},
  onResponse: ({ method, url, status, requestId, durationMs }) => {
    // hook for logging/metrics
  },
});

Sandbox testing helpers

The sandbox environment exposes simulation endpoints for card authorizations, clearing, reversals, ACH/wire returns, and more — all covered under client.cardTransactionSimulations, client.cardWebhookSimulations, etc. client.sandboxWipe.wipeWorkspace() resets your sandbox workspace to a clean slate (throws outside sandbox).

Regenerating against an updated spec

The entire typed surface (src/generated/) is produced by scripts/generate.ts from an OpenAPI document. To pick up new Synctera resources or fields:

# Drop an updated spec at reference/openapi.json, then:
npm run generate   # regenerates src/generated/types.ts and src/generated/resources/*.ts
npm run typecheck
npm test

The generator resolves $ref parameters, allOf/oneOf/anyOf compositions, and array/object schemas. It intentionally falls back to unknown rather than guessing when it encounters a pattern it doesn't recognize, so a spec update never silently produces an incorrect type — a fallback to unknown is your signal to open scripts/generate.ts and extend the converter.

Development

npm install
npm run generate     # spec -> types + resources
npm run lint
npm run typecheck
npm test
npm run build

License

MIT

About

An unofficial, production-grade TypeScript SDK for the [Synctera](https://synctera.com) Banking-as-a-Service API.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages