API Recipes

Practical accounting API recipes for SaaS apps.

Use these server-side integration patterns to wire customers, invoices, payments, vendors, bills, documents, reports, settings, events, and error handling into your app. Examples use fake placeholders and are implementation guidance, not production approval.

Quickstart Order

Use recipes after the safe setup path is working.

Step 1

Authenticate first

Start with `GET /api/v1/auth/check` from server-side code.

Step 2

Read before writing

Use the read-only starter and API Reference to verify setup.

Step 3

Mutate only in test mode

Use opt-in workflow scripts with fake data and a non-placeholder `ll_test_` key.

Step 4

Check accounting impact

Review whether the call stays operational, posts a journal, or updates a report.

Pick By Workflow

Recipes are grouped by what your app is trying to do.

If you are not sure which group applies, start with Build with AI to generate a setup plan and then return here for exact endpoint details.

Money In

Customers, estimates, invoices, payments, and A/R readback.

  • Money-in workflow
  • Invoice lifecycle trust loop
  • Estimate workflow

Money Out

Vendors, bills, vendor payments, A/P, and 1099 support.

  • Money-out workflow
  • Reports workflow

Documents

Evidence files, document links, shares, renders, and private downloads.

  • Document / evidence workflow
  • Embedded settings workflow

Developer Operations

Webhooks, events, errors, test/live separation, and safe retries.

  • Webhook / event workflow
  • Error handling workflow
  • Test/live environment workflow

Money-in workflow

Create a customer, create and post an invoice, record a payment, and read the Accounts Receivable summary.

Invoice lifecycle trust loop

Create a customer, create an estimate, convert it to an invoice, post the invoice, record trusted payment confirmation, and read reports.

Estimate workflow

Create an estimate, move it through status, convert it to a draft invoice, and post the invoice separately.

Money-out workflow

Create a vendor, create and post a bill, create and post a vendor payment, and read the Accounts Payable summary.

Document / evidence workflow

Upload, list, link, and archive private evidence files through server-routed APIs.

Reports workflow

Read summary, A/R, A/P, and 1099 support reports after posting workflows run.

Embedded settings workflow

Read connected-app capabilities and update safe document branding or defaults through public APIs.

Webhook / event workflow

Send events with retry-safe identifiers and receive outbound webhooks when configured.

Error handling workflow

Handle public API failures through stable error codes and avoid branching on message text.

Test/live environment workflow

Keep test and live environments separate, use external ids for retry safety, and avoid trusted scope fields.

Money-in workflow

Create a customer, create and post an invoice, record a payment, and read the Accounts Receivable summary.

const customer = await createCustomer({
  baseUrl,
  apiKey,
  customer: {
    externalCustomerId: "cus_example",
    name: "Example Customer",
  },
});

const invoice = await createInvoice({
  baseUrl,
  apiKey,
  invoice: {
    invoiceNumber: "INV-1001",
    externalInvoiceId: "inv_example",
    customerId: customer.data.id,
    issueDate: "2026-05-25",
    dueDate: "2026-06-24",
    lineItems: [{ description: "Subscription", quantity: 1, unitAmountCents: 25000 }],
  },
});

await postInvoice({ baseUrl, apiKey, invoiceId: invoice.data.id });
await createPayment({
  baseUrl,
  apiKey,
  payment: {
    externalPaymentId: "pay_example",
    invoiceId: invoice.data.id,
    amountCents: 25000,
    receivedAt: "2026-05-25T12:00:00Z",
    source: "manual",
  },
});
Invoice posting creates Accounts Receivable and revenue. Customer payment posting reduces Accounts Receivable and does not create revenue.

Invoice lifecycle trust loop

Create a customer, create an estimate, convert it to an invoice, post the invoice, record trusted payment confirmation, and read reports.

const customer = await ledgerline.customers.create({ name: "Example Customer" });
const estimate = await ledgerline.estimates.create({
  customer_id: customer.data.id,
  estimate_number: "EST-1001",
  line_items: [{ description: "Subscription", quantity: 1, unit_amount_cents: 25000 }],
});

const invoice = await ledgerline.estimates.convert(estimate.data.id, {
  invoice_number: "INV-1001",
});

await ledgerline.documentShares.create({ document_type: "invoice", invoice_id: invoice.data.id });
await ledgerline.invoices.post(invoice.data.id, { posted_at: "2026-05-25T12:00:00Z" });
await ledgerline.payments.create({
  invoice_id: invoice.data.id,
  external_payment_id: "pay_example",
  amount_cents: 25000,
  received_at: "2026-05-25T12:05:00Z",
  source: "manual",
});

const ar = await ledgerline.reports.accountsReceivable({ asOf: "2026-05-31" });
The estimate is non-accounting. Invoice posting creates A/R and revenue. Payment confirmation reduces A/R. Reports and webhooks are readback/notification surfaces after committed work.

Estimate workflow

Create an estimate, move it through status, convert it to a draft invoice, and post the invoice separately.

const estimate = await createEstimate({
  baseUrl,
  apiKey,
  estimate: {
    estimateNumber: "EST-1001",
    customerName: "Example Customer",
    issueDate: "2026-05-25",
    lineItems: [{ description: "Implementation", quantity: 1, unitAmountCents: 50000 }],
  },
});

await updateEstimateStatus({ baseUrl, apiKey, estimateId: estimate.data.id, status: "sent" });
const converted = await convertEstimateToInvoice({ baseUrl, apiKey, estimateId: estimate.data.id });
Estimates are non-accounting documents. Accounting starts when the converted invoice is posted.

Money-out workflow

Create a vendor, create and post a bill, create and post a vendor payment, and read the Accounts Payable summary.

const vendor = await createVendor({
  baseUrl,
  apiKey,
  vendor: {
    externalVendorId: "ven_example",
    vendorType: "contractor",
    displayName: "Example Vendor",
  },
});

const bill = await createBill({
  baseUrl,
  apiKey,
  bill: {
    billNumber: "BILL-1001",
    externalBillId: "bill_example",
    vendorId: vendor.data.id,
    issueDate: "2026-05-25",
    dueDate: "2026-06-24",
    lineItems: [{ description: "Hosting", quantity: 1, unitAmountCents: 12000 }],
  },
});

await postBill({ baseUrl, apiKey, billId: bill.data.id });
const payment = await createVendorPayment({
  baseUrl,
  apiKey,
  vendorPayment: {
    vendorId: vendor.data.id,
    externalVendorPaymentId: "vp_example",
    paymentDate: "2026-05-25",
    amountCents: 12000,
    cashAccountId: "00000000-0000-4000-8000-000000000020",
    status: "ready",
    allocations: [{ billId: bill.data.id, amountCents: 12000 }],
  },
});
await postVendorPayment({ baseUrl, apiKey, vendorPaymentId: payment.data.id });
Bill posting creates expense and Accounts Payable. Vendor payment posting reduces Accounts Payable and credits cash, bank, or clearing.

Document / evidence workflow

Upload, list, link, and archive private evidence files through server-routed APIs.

const document = await createDocument({
  baseUrl,
  apiKey,
  file: new Blob(["example"], { type: "application/pdf" }),
  fileName: "invoice-evidence.pdf",
  title: "Invoice evidence",
  documentType: "receipt",
  status: "needs_review",
});

await linkDocument({
  baseUrl,
  apiKey,
  documentId: document.data.id,
  targetType: "invoice",
  targetId: "inv_example",
});
Documents and evidence files remain private/server-routed and do not mutate accounting. OCR/AI parsing remains deferred.

Reports workflow

Read summary, A/R, A/P, and 1099 support reports after posting workflows run.

const summary = await getReportSummary({ baseUrl, apiKey });
const ar = await getAccountsReceivableSummary({ baseUrl, apiKey });
const ap = await getAccountsPayableSummary({ baseUrl, apiKey });
const taxSupport = await get1099Summary({ baseUrl, apiKey, year: 2026 });
Reports are read-only. The 1099 summary is tax-support reporting only, not IRS form generation, e-filing, payroll, or tax advice.

Embedded settings workflow

Read connected-app capabilities and update safe document branding or defaults through public APIs.

const capabilities = await getSettingsCapabilities({ baseUrl, apiKey });

await updateInvoiceDefaults({
  baseUrl,
  apiKey,
    defaults: {
    defaultInvoiceTerms: "Due on receipt.",
    defaultPaymentInstructions: "Pay by ACH.",
    documentFooter: "Thank you.",
  },
});
Connected apps manage settings through public APIs and server-side auth, not direct database writes.

Webhook / event workflow

Send events with retry-safe identifiers and receive outbound webhooks when configured.

await sendLedgerlineEvent({
  baseUrl,
  apiKey,
  eventType: "payment.received",
  externalEventId: "evt_example",
  idempotencyKey: "evt_example",
  payload: {
    external_payment_id: "pay_example",
    external_invoice_id: "inv_example",
    amount_cents: 25000,
  },
});
Webhook secrets stay server-side. The repo-local receiver example uses fake payloads and demo-only in-memory idempotency. SendGrid, Stripe, Plaid, OCR/AI, and other providers are not required for core accounting.

Error handling workflow

Handle public API failures through stable error codes and avoid branching on message text.

try {
  await checkLedgerlineAuth({ baseUrl, apiKey });
} catch (error) {
  if (error instanceof LedgerlineApiError) {
    if (error.code === "module_disabled") {
      // Future hard enforcement: enable the required module and retry.
    }
    if (error.code === "rate_limited") {
      // Back off and retry later.
    }
  }
}
Error handling does not change accounting. Use stable error codes such as validation_failed, unauthorized, forbidden, not_found, conflict, rate_limited, and the planned future module_disabled code.

Test/live environment workflow

Keep test and live environments separate, use external ids for retry safety, and avoid trusted scope fields.

const auth = await checkLedgerlineAuth({
  baseUrl: "https://your-ledgerline-domain.com",
  apiKey: process.env.LEDGERLINE_API_KEY!,
});

// Store ll_test_replace_me only as a fake docs placeholder.
// Use the real test key only in server-side environment variables.
Test/live separation protects setup and retry behavior. Do not send organization_id, connected_app_id, app_environment_id, environment, or mode as trusted client scope fields.

Product Truth

Recipes are useful, but they are not production approval.

  • API keys must stay server-side. Do not put test or live keys in browser bundles, mobile clients, public repos, or static pages.
  • The API Reference is read-only and never asks for real API keys.
  • Core accounting does not require Stripe, SendGrid, Plaid, OCR/AI, IRS e-filing, or payroll providers.
  • Real email sending, Stripe Checkout/payment collection, Plaid bank feeds, OCR/AI parsing, IRS e-filing, and payroll/HR remain deferred unless implemented later.
  • Recipes are implementation guidance, not production approval.
  • Production approval still requires setup, migrations, private buckets, RLS/Storage dry-run, smoke/security QA, and rollout planning.

Developer Docs

Use recipes with the rest of the docs

Pair workflow examples with setup guidance, accounting rules, security boundaries, and the read-only API Reference.