Installation
npm install @moneyline/sdk
# or: pnpm add @moneyline/sdk / bun add @moneyline/sdk
Quick start
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 aresnake_case. Request params mirror the API body exactly (external_id,submission_id), while responses come backcamelCase(externalId,submissionId). This asymmetry is deliberate — the SDK passes your params through untouched.
Configuration
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 aPageIterator, an async iterable that fetches pages as you consume it.
// 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();
listLinked, listEventTypes, getGroups, …) return a plain array.
Resources
Submissions
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
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
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 onsubmissions.
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
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
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
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);
Merchants and categories
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
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
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
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
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 aMoneylineError subclass carrying type, code, param, statusCode, requestId, and docUrl.
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.
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);
});
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):
import type { paths, components, operations } from '@moneyline/sdk';
type SubmissionResponse = paths['/v1/submissions']['post']['responses']['201'];
type Submission = components['schemas']['Submission'];