> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moneyline.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js / TypeScript SDK

> Official Node.js SDK for the Moneyline API — every resource typed, with auto-complete for parameters and responses.

## Installation

```bash theme={null}
npm install @moneyline/sdk
# or: pnpm add @moneyline/sdk / bun add @moneyline/sdk
```

## Quick start

```typescript theme={null}
import { readFileSync } from 'node:fs';
import { Moneyline } from '@moneyline/sdk';

// apiKey defaults to process.env.MONEYLINE_API_KEY
const client = new Moneyline();

const submission = await client.submissions.create({
  external_id: 'acme-001',
  name: 'Acme Corp',
});

await client.documents.upload({
  submission_id: submission.id,
  file: readFileSync('bank-statement.pdf'),
  filename: 'bank-statement.pdf',
});

const scorecard = await client.analytics.getScorecard(submission.id);
```

> **Parameters are `snake_case`.** Request params mirror the API body exactly
> (`external_id`, `submission_id`), while responses come back `camelCase`
> (`externalId`, `submissionId`). This asymmetry is deliberate — the SDK passes
> your params through untouched.

## Configuration

```typescript theme={null}
const client = new Moneyline({
  apiKey: 'ml_live_xxxxx',             // or MONEYLINE_API_KEY env var
  baseUrl: 'https://api.moneyline.co', // or MONEYLINE_BASE_URL
  timeout: 60_000,                     // ms
  maxRetries: 3,                       // auto-retry on 429/5xx
});
```

| Option | Type | Default | Description |
| - | - | - | - |
| `apiKey` | `string` | `MONEYLINE_API_KEY` | API key. Throws if absent. |
| `baseUrl` | `string` | `https://api.moneyline.co` | API base URL |
| `timeout` | `number` | `60000` | Request timeout (ms) |
| `maxRetries` | `number` | `3` | Max retries on transient errors |

## Pagination

Methods that list a collection return a `PageIterator`, an async iterable that fetches pages as you consume it.

```typescript theme={null}
// Stream every match, one page at a time.
for await (const txn of client.transactions.list({ submission_id: id })) {
  console.log(txn.amount, txn.category);
}

// Or collect eagerly.
const all = await client.transactions.list({ submission_id: id }).toArray();
const firstFifty = await client.transactions.list({ submission_id: id }).toArray(50);
const newest = await client.transactions.list({ submission_id: id }).first();
```

Non-paginated methods (`listLinked`, `listEventTypes`, `getGroups`, …) return a plain array.

## Resources

### Submissions

```typescript theme={null}
const sub = await client.submissions.create(
  {
    external_id: 'acme-001',
    name: 'Acme Corp',
    metadata: { source: 'broker-portal' },
    company_info: { ein: '12-3456789' },
  },
  { idempotencyKey: 'acme-001-create' },
);

const fetched = await client.submissions.retrieve(sub.id);
const updated = await client.submissions.update(sub.id, { name: 'Acme Corp LLC' });

for await (const s of client.submissions.list({ limit: 25 })) console.log(s.externalId);

await client.submissions.delete(sub.id);

await client.submissions.startWorkflow(sub.id);
const status = await client.submissions.getStatus(sub.id);

// priority is a positional string, not an options object.
await client.submissions.enrich(sub.id, 'high');

const dupes = await client.submissions.findDuplicates(sub.id);

// Polls retrieve() until status is processed / reviewed / failed.
const done = await client.submissions.waitForProcessing(sub.id, {
  timeout: 300_000,
  pollInterval: 5_000,
});
```

### Documents

```typescript theme={null}
const doc = await client.documents.upload({
  submission_id: sub.id,
  file: readFileSync('statement.pdf'),
  filename: 'statement.pdf',
  document_class: 'bank_statement', // optional; skips auto-classification
});

const fetched = await client.documents.retrieve(doc.id);

// Documents for one submission — submissionId is positional.
for await (const d of client.documents.list(sub.id)) console.log(d.documentClass);

// Every document in the tenant.
for await (const d of client.documents.listAll({ limit: 50 })) console.log(d.id);

const { url } = await client.documents.getDownloadUrl(doc.id);
await client.documents.reclassify(doc.id, 'tax_return');
await client.documents.parse(doc.id);
const parts = await client.documents.split(doc.id);
await client.documents.delete(doc.id);
```

### Transactions

```typescript theme={null}
for await (const txn of client.transactions.list({ submission_id: sub.id })) {
  console.log(txn.postedAt, txn.amount, txn.category);
}

// Enrich raw transactions without persisting them.
const enriched = await client.transactions.enrich({
  transactions: [{ description: 'AMZN Mktp US*2A4B', amount: -42.11, date: '2026-01-14' }],
});

// Bulk-create against a submission.
await client.transactions.createBulk(sub.id, [
  { description: 'DEPOSIT', amount: 5000, date: '2026-01-02' },
]);

// Re-run enrichment for a whole submission.
await client.transactions.enrichSubmission(sub.id);

// Correct a categorization; feedback trains the enrichment model.
await client.transactions.submitFeedback(txnId, {
  type: 'category',
  previous_value: 'shopping',
  corrected_value: 'inventory',
});

await client.transactions.delete(txnId);
```

### Analytics

Every calculation is keyed by submission ID and lives here — not on `submissions`.

```typescript theme={null}
const scorecard = await client.analytics.getScorecard(sub.id);
const cashflow  = await client.analytics.getCashflow(sub.id);
const risk      = await client.analytics.getRiskScore(sub.id);
const balances  = await client.analytics.getBalances(sub.id);
const bank      = await client.analytics.getBankSummary(sub.id);
const stats     = await client.analytics.getStatistics(sub.id);
const anomalies = await client.analytics.getAnomalies(sub.id);
const forecast  = await client.analytics.getForecast(sub.id);
const recurring = await client.analytics.getRecurring(sub.id);
const merchants = await client.analytics.getMerchants(sub.id);
const categories  = await client.analytics.getCategories(sub.id);
const metrics     = await client.analytics.getMetrics(sub.id);
const financials  = await client.analytics.getFinancials(sub.id);
const coverage    = await client.analytics.getCoverage(sub.id);
const benchmarks  = await client.analytics.getBenchmarks(sub.id);
```

### Policies

```typescript theme={null}
const evaluations = await client.policies.evaluate(sub.id);
const all = await client.policies.evaluateAll(sub.id);
await client.policies.progress(sub.id, 'approve');

const funderPolicies = await client.policies.listFunderPolicies(funderId);
await client.policies.createFunderPolicy(funderId, {
  name: 'Min monthly revenue',
  metric: 'monthly_revenue',
  operator: 'gte',
  threshold: 25000,
});
```

### Rules

```typescript theme={null}
const rule = await client.rules.create({
  name: 'Low average balance',
  metric: 'average_daily_balance',
  operator: 'lt',
  threshold: 5000,
  severity: 'warning',
  message: 'Average daily balance below $5,000',
});

const fetched = await client.rules.retrieve(rule.id);
await client.rules.update(rule.id, { threshold: 7500 });
for await (const r of client.rules.list()) console.log(r.name);
await client.rules.delete(rule.id);

// Metrics available to build rules against.
const metrics = await client.rules.getMetrics();
```

### Webhooks

```typescript theme={null}
const hook = await client.webhooks.create({
  url: 'https://example.com/moneyline',
  events: ['submission.processed', 'document.parsed'],
});

const fetched = await client.webhooks.retrieve(hook.id);
await client.webhooks.update(hook.id, { is_active: false });
for await (const w of client.webhooks.list()) console.log(w.url);
await client.webhooks.delete(hook.id);

const result = await client.webhooks.test(hook.id);
const eventTypes = await client.webhooks.listEventTypes();

const deliveries = await client.webhooks.listDeliveries(hook.id, { limit: 50 });
await client.webhooks.replay(hook.id, deliveryId);
await client.webhooks.retry(hook.id, deliveryId);
```

See [Webhooks](https://docs.moneyline.co/guides/webhooks.md) for signature verification.

### Merchants and categories

```typescript theme={null}
const merchant = await client.merchants.retrieve(merchantId);
const results  = await client.merchants.search('amazon');
const groups   = await client.merchants.getGroups();

// Resolve a raw bank description to a merchant.
const resolved = await client.merchants.extract('AMZN Mktp US*2A4B9');

const categories = await client.categories.list();
```

### Funders

```typescript theme={null}
for await (const f of client.funders.list()) console.log(f.name);
const funder = await client.funders.retrieve(funderId);
const linked = await client.funders.listLinked();

await client.funders.submit({ funder_id: funderId, submission_id: sub.id });
const submissions = await client.funders.listSubmissions(funderId);
```

### Integrations

```typescript theme={null}
const integration = await client.integrations.create({
  type: 'plaid',
  name: 'Plaid production',
});

for await (const i of client.integrations.list()) console.log(i.type);
await client.integrations.update(integration.id, { is_active: false });

const links = await client.integrations.listLinks(integration.id);

await client.integrations.import({
  submission_id: sub.id,
  source: 'plaid',
  data: { accessToken: '...' },
});
```

### Entities

```typescript theme={null}
const entity = await client.entities.create({ name: 'Owner', type: 'person' });
const fetched = await client.entities.retrieve(entity.id);
for await (const e of client.entities.list()) console.log(e.name);

await client.entities.bulkSave([{ id: entity.id, name: 'Beneficial owner' }]);
const exported = await client.entities.export();
```

### ISO applications

```typescript theme={null}
const app = await client.isoApplications.upload({
  file: readFileSync('iso-application.pdf'),
  filename: 'iso-application.pdf',
  submissionId: sub.id, // optional; omit to upload standalone
});

const fetched = await client.isoApplications.retrieve(app.id);
const results = await client.isoApplications.getResults(app.id);
await client.isoApplications.reprocess(app.id);
```

## Error handling

Every failure is a `MoneylineError` subclass carrying `type`, `code`, `param`, `statusCode`, `requestId`, and `docUrl`.

```typescript theme={null}
import {
  MoneylineError,
  AuthenticationError,
  PermissionError,
  NotFoundError,
  ValidationError,
  ConflictError,
  RateLimitError,
  ServerError,
} from '@moneyline/sdk';

try {
  await client.submissions.retrieve('does-not-exist');
} catch (err) {
  if (err instanceof NotFoundError) {
    // 404
  } else if (err instanceof ValidationError) {
    // 400/422 — inspect err.message
  } else if (err instanceof RateLimitError) {
    // 429 — already retried maxRetries times
  } else if (err instanceof MoneylineError) {
    console.error(err.code, err.requestId);
  } else {
    throw err;
  }
}
```

`RateLimitError` and `ServerError` are retried automatically up to `maxRetries` before they surface.

## Webhook signature verification

`constructEvent` verifies the signature and returns the parsed event; `verifySignature` verifies without parsing and throws `WebhookSignatureVerificationError` on mismatch.

```typescript theme={null}
import { constructEvent } from '@moneyline/sdk';

app.post('/moneyline', (req, res) => {
  const event = constructEvent(
    req.rawBody, // the raw body, not the parsed object
    req.headers['x-moneyline-signature'] as string,
    process.env.MONEYLINE_WEBHOOK_SECRET!,
  );
  res.sendStatus(200);
});
```

For Express and Fastify there are ready-made handlers that do the same thing:

```typescript theme={null}
import { expressMiddleware, fastifyPlugin } from '@moneyline/sdk';

app.post('/moneyline', expressMiddleware(process.env.MONEYLINE_WEBHOOK_SECRET!), handler);
```

## Spec-accurate types

Alongside the hand-written types, the SDK re-exports types generated from the OpenAPI document (`bun run gen:types`):

```typescript theme={null}
import type { paths, components, operations } from '@moneyline/sdk';

type SubmissionResponse = paths['/v1/submissions']['post']['responses']['201'];
type Submission = components['schemas']['Submission'];
```

Use these when you want the exact wire shape of an endpoint the hand-written resources do not cover.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.