# Accounts Receivable Automation

Create invoices in your customer's accounting system, record payments after they settle, and verify the remaining balance. Your application owns approvals, payment collection, duplicate prevention, and reconciliation; Apideck provides normalized access to supported accounting operations.

## Explore the demo

**Invoicing demo**

Explore customer sync, invoice creation, payment recording, and reconciliation.

Choose Mock for synthetic sample data. Live mode requires your own credentials and an authorized connection; requests can read or write real records. Check connector coverage before building.

[Open demo](https://demos.apideck.com/vertical-saas) · [API endpoints](/apis/accounting/reference?useCase=accounts-receivable)

## Your build path

| Step | Result |
| --- | --- |
| Check support | Confirm customer, invoice, and payment operations for the selected connector. |
| Connect | The customer's administrator authorizes access through Vault. |
| Resolve dependencies | Store the customer ID and the required revenue, tax, item, and currency references. |
| Create and verify | Store the invoice ID and read back the saved invoice. |
| Record and reconcile | After settlement, allocate the payment and confirm the resulting balance. |

Start with the [AR-filtered API reference](/apis/accounting/reference?useCase=accounts-receivable) and [connector coverage](/apis/accounting/coverage). Coverage and required fields differ by connector; a demo is not evidence that an operation is supported for a customer's account.

### Developer setup

Enable Accounting and the selected connector, keep the API key on your server, and map your customer tenant to a stable consumer ID. Follow [Vault setup](/guides/vault) and use the same consumer and connector for all related records. The examples below target QuickBooks and SDK `@apideck/unify@0.52.2`; use a test company and replace illustrative accounting IDs before executing writes.

### Customer administrator setup

Ask an authorized administrator to connect the intended company and confirm its currency, revenue accounts, tax configuration, and invoice-numbering rules. Use the maintained [QuickBooks](/apis/accounting/quickbooks), [Xero](/apis/accounting/xero), or [NetSuite](/apis/accounting/netsuite) setup instructions for provider-specific prerequisites. Start with a read-only customer lookup to verify access.

_Your product sends invoices and payments to the Apideck Accounting API, which writes them into whichever ledger your customer uses and returns the outstanding balance._

Payment allocations are what let the downstream system flip invoice status to paid using its own rules, [Vault](/guides/vault) handles auth and token refresh, and the `accounting.invoice.updated` and `accounting.payment.created` webhooks keep reconciliation in sync without polling.

## Resource mapping

| Connector    | Invoice maps to                                 |
| ------------ | ----------------------------------------------- |
| QuickBooks   | `Invoice`                                       |
| Xero         | `Invoice` (type `ACCREC`)                       |
| NetSuite     | `Invoice` (transaction type)                    |
| Sage Intacct | `AR Invoice`                                    |
| Exact Online | `Sales Invoice`                                 |
| Stripe       | `Invoice` (with auto-collected `PaymentIntent`) |
| Odoo         | `account.move` (type `out_invoice`)             |
| FreshBooks   | `Invoice`                                       |
| Zoho Books   | `Invoice`                                       |
| Workday      | `Customer Invoice`                              |

## Walkthrough

The flow is the same for every connector that supports the full path: resolve the customer, create the invoice, then record a payment with an allocation back to that invoice.

Each step below shows the raw request body first, then the same call through the [`@apideck/unify`](/sdks/node) SDK. The SDK takes camelCase and handles serialization, so pick whichever fits your stack. Every snippet is verified against `@apideck/unify` v0.52.2.

```ts
import { Apideck } from '@apideck/unify'

const apideck = new Apideck({
  apiKey: process.env.API_KEY!,
  appId: process.env.APP_ID!,
  consumerId: 'cons_01H8X9Y2A3K4M5N6P7Q8R9S0T1'
})
```

### 1. Resolve or create the customer

Before pushing an invoice, you need a `customer.id` that exists in the downstream system. Search by email or display name first to avoid creating duplicates, then fall back to creating a new customer if no match is found.

```json
{
  "display_name": "Beachside Surf Co.",
  "company_name": "Beachside Surf Co. LLC",
  "emails": [
    {
      "email": "ap@beachsidesurf.com",
      "type": "primary"
    }
  ],
  "currency": "USD",
  "addresses": [
    {
      "type": "billing",
      "line1": "221 Pacific Ave",
      "city": "Santa Cruz",
      "state": "CA",
      "postal_code": "95060",
      "country": "US"
    }
  ],
  "phone_numbers": [
    {
      "number": "+1-831-555-0144",
      "type": "primary"
    }
  ],
  "payment_method": "credit_card"
}
```

Send this to [`POST /accounting/customers`](/apis/accounting/reference/customers#customersAdd) with the standard headers:

```http
POST /accounting/customers HTTP/1.1
Host: unify.apideck.com
Authorization: Bearer ${APIDECK_API_KEY}
x-apideck-app-id: ${APIDECK_APP_ID}
x-apideck-consumer-id: {CONSUMER_ID}
x-apideck-service-id: quickbooks
Content-Type: application/json
```

Or through the SDK, looking the customer up first so you do not create duplicates:

```ts
const companyName = 'Beachside Surf Co. LLC'
const matches = new Set<string>()
for await (const page of await apideck.accounting.customers.list({
  serviceId: 'quickbooks',
  filter: { companyName },
  limit: 100
})) {
  for (const customer of page.getCustomersResponse?.data ?? []) {
    if (customer.companyName === companyName && customer.id) matches.add(customer.id)
  }
}
if (matches.size > 1) throw new Error('Multiple customers match; resolve the customer explicitly')
let customerId = [...matches][0]
if (!customerId) {
  const created = await apideck.accounting.customers.create({
    serviceId: 'quickbooks',
    customer: {
      displayName: 'Beachside Surf Co.', companyName,
      emails: [{ email: 'ap@beachsidesurf.com', type: 'primary' }], currency: 'USD'
    }
  })
  customerId = created.createCustomerResponse?.data?.id ?? ''
}
if (!customerId) throw new Error('Could not resolve a customer to invoice')
```

Persist the chosen ID against your own customer record and serialize concurrent creates for that customer. Name matching is a discovery aid, not a unique identity guarantee.

The response includes the connector-issued `id` you will reference on the invoice. On connectors where customers are read-only (see the table below), skip this step and look up the existing customer with [`GET /accounting/customers`](/apis/accounting/reference/customers#customersAll).

### 2. Push the invoice

Create the invoice against the resolved customer. The unified model expects line items with `description`, `quantity`, and `unit_price`. The downstream system will compute totals and tax based on the `tax_rate` you supply.

```json
{
  "number": "INV-2025-00184",
  "customer": {
    "id": "acct_01H8X9Y2A3K4M5N6P7Q8R9S0T1"
  },
  "invoice_date": "2025-03-04",
  "due_date": "2025-04-03",
  "currency": "USD",
  "reference": "PO-44219",
  "line_items": [
    {
      "type": "sales_item",
      "description": "Pro plan subscription, March 2025",
      "quantity": 1,
      "unit_price": 199.0,
      "total_amount": 199.0,
      "tax_rate": {
        "id": "TAX_NON"
      }
    },
    {
      "type": "sales_item",
      "description": "Onboarding hours",
      "quantity": 4,
      "unit_price": 150.0,
      "total_amount": 600.0,
      "tax_rate": {
        "id": "TAX_NON"
      }
    }
  ],
  "sub_total": 799.0,
  "total": 799.0,
  "status": "authorised",
  "payment_method": "credit_card"
}
```

Send this to [`POST /accounting/invoices`](/apis/accounting/reference/invoices#invoicesAdd). Capture the returned invoice `id`. That value is what you allocate the payment to in the next step.

The same call through the SDK, with a tax rate already resolved to its downstream `id` (`TAX_NON` below stands in for whatever ID you resolved). Pull the chart of accounts and tax rates once and cache them (see the ledger-account lookup in [Accounts Payable Automation](/guides/accounts-payable-automation#2-fetch-the-chart-of-accounts-for-line-coding) for the pattern) rather than looking them up per invoice:

```ts
const { createInvoiceResponse } = await apideck.accounting.invoices.create({
  serviceId: 'quickbooks',
  invoice: {
    number: 'INV-2025-00184',
    customer: { id: customerId },
    invoiceDate: new Date('2025-03-04'),
    dueDate: new Date('2025-04-03'),
    currency: 'USD',
    reference: 'PO-44219',
    lineItems: [
      {
        type: 'sales_item',
        description: 'Pro plan subscription, March 2025',
        quantity: 1,
        unitPrice: 199,
        totalAmount: 199,
        taxRate: { id: 'TAX_NON' }
      },
      {
        type: 'sales_item',
        description: 'Onboarding hours',
        quantity: 4,
        unitPrice: 150,
        totalAmount: 600,
        taxRate: { id: 'TAX_NON' }
      }
    ],
    subTotal: 799,
    total: 799,
    status: 'authorised',
    paymentMethod: 'credit_card'
  }
})

const invoiceId = createInvoiceResponse?.data?.id
if (!invoiceId) throw new Error('Invoice creation did not return an ID')
```

Two field names differ from what you might expect: the invoice number is `number`, not `invoiceNumber`, and the invoice-level figure is `total`, not `totalAmount`. On an invoice, `totalAmount` exists only on line items. The payment object in the next step is a different resource, and does take a top-level `totalAmount`. Sending the wrong one on an invoice is accepted and then silently dropped.

QuickBooks does not carry `status` or `subTotal` in its supported field set for invoices. It derives both itself, so the values you send are dropped there. Keep them in the payload anyway: they are load-bearing on Xero and NetSuite, so the same body stays portable even though this snippet is pinned to QuickBooks.

### 3. Record the payment with an allocation

This records an accounting payment; it does not charge a card or transfer funds. Trigger it only after your payment provider confirms settlement. Persist the provider's payment ID and the accounting payment ID to prevent duplicate recording after retries or repeated events.

In most accounting systems, invoice status is computed from outstanding balance, not set directly. Trying to `PATCH` the invoice with `status: "paid"` will fail or be ignored. Allocate a payment instead, and the connector flips the status as part of the post.

```json
{
  "transaction_date": "2025-03-12T15:04:00.000Z",
  "currency": "USD",
  "total_amount": 799.0,
  "customer": {
    "id": "acct_01H8X9Y2A3K4M5N6P7Q8R9S0T1"
  },
  "payment_method": "Credit Card",
  "payment_method_reference": "ch_3OxPq2KJ8mZ4T1Lk0aN9bV2X",
  "accounts_receivable_account_type": "Account",
  "accounts_receivable_account_id": "1200",
  "type": "accounts_receivable",
  "reconciled": true,
  "status": "authorised",
  "allocations": [
    {
      "id": "inv_01H8YBZ3C4D5E6F7G8H9J0K1L2",
      "type": "invoice",
      "amount": 799.0
    }
  ],
  "note": "Stripe charge ch_3OxPq2KJ8mZ4T1Lk0aN9bV2X"
}
```

Or through the SDK:

```ts
await apideck.accounting.payments.create({
  serviceId: 'quickbooks',
  payment: {
    transactionDate: new Date('2025-03-12T15:04:00.000Z'),
    totalAmount: 799,
    currency: 'USD',
    type: 'accounts_receivable',
    customer: { id: customerId },
    paymentMethod: 'Credit Card',
    paymentMethodReference: 'ch_3OxPq2KJ8mZ4T1Lk0aN9bV2X',
    accountsReceivableAccountType: 'Account',
    accountsReceivableAccountId: '1200',
    reconciled: true,
    status: 'authorised',
    allocations: [{ id: invoiceId, type: 'invoice', amount: 799 }],
    note: 'Stripe charge ch_3OxPq2KJ8mZ4T1Lk0aN9bV2X'
  }
})
```

Send this to [`POST /accounting/payments`](/apis/accounting/reference/payments#paymentsAdd). The `allocations[].id` is the invoice ID returned in step 2, and `allocations[].amount` can be partial if you are recording a deposit. Omit `allocations` entirely and the cash posts against the customer while the invoice stays open, which is the single most common AR bug. For a deeper breakdown of the allocation pattern, see the companion guide on [marking invoices and bills as paid](/guides/mark-invoices-as-paid).

### 4. Reconcile state via webhooks

Where the connector supports them, subscribe to `accounting.invoice.updated` and `accounting.payment.created`. Treat events as a reason to fetch the current record, deduplicate deliveries, and retain periodic reconciliation for gaps. See [Webhooks](/guides/webhooks).

### Optional: start from a sales order

Where the ERP records orders before invoices, [Sales Orders](/apis/accounting/reference/sales-orders) (beta; Acumatica, MYOB Acumatica, Sage Intacct and Fortnox) hold the customer's committed order with `line_items`, `status` and, where the system links them, `invoice_id`. Create the order first, then the invoice, and use `invoice_id` and `unbilled_amount` to track what is still to bill. Before posting the invoice, [Fiscal Periods](/apis/accounting/reference/fiscal-periods) (beta) tells you whether its date falls in an open period.

## Verify the result

```ts
const { getInvoiceResponse } = await apideck.accounting.invoices.get({
  serviceId: 'quickbooks', id: invoiceId
})
const savedInvoice = getInvoiceResponse?.data
if (!savedInvoice) throw new Error('Could not read back the invoice')
console.log({ id: savedInvoice.id, total: savedInvoice.total, balance: savedInvoice.balance })
```

Confirm the customer, currency, line coding, and total after creation. After payment recording, compare the balance with the expected remaining amount, allowing for connector processing delays and partial payments. Do not assume every connector returns the same status field.

If a write times out, inspect [Logs](/logs) and reconcile the downstream record before retrying. If a payment exists but the invoice is still open, check the allocation's invoice ID, amount, customer, and currency. Avoid retrying the payment blindly.

## Production checklist

- Persist connector-issued IDs within the correct consumer and company.
- Gate invoice writes on approval and payment recording on confirmed settlement.
- Resolve ambiguous customer matches and guard concurrent creates.
- Validate connector-specific required references before sending writes.
- Check pagination, expired authorization, duplicate events, timeouts, and partial payments.
- Make the first success observable through read-back and request logs.

## What actually bites people

Three constraints on the connectors most AR products start with, each verified against the live coverage matrix rather than the unified schema.

**QuickBooks Online.** Neither `status` nor `sub_total` is a supported invoice field, so `invoice.status` comes back empty and QuickBooks derives the subtotal itself. Drive your paid, part paid, and overdue states off `balance` against `total` instead. It is also the only one of the three that lets you filter invoices by `customerId`.

**Xero.** Invoices support only the `updatedSince` and `number` filters, so you cannot ask Xero for one customer's invoices. Sync the whole window and index by customer on your side. Xero payments also have no update operation: to correct one you delete it and post a new one.

**NetSuite.** On a OneWorld account every AR record needs subsidiary context. Resolve subsidiaries with `apideck.accounting.subsidiaries.list()`, which NetSuite supports, and note that the unified `subsidiary` object is **not** writable on NetSuite customers, invoices, or payments: `companyId` is the supported scope field. NetSuite invoice line items also carry no `description`, so the line is defined by the `item` you reference and free text silently vanishes.

## Connector-specific behavior

Coverage below reflects whether each connector supports creating the resource through Apideck. Read-only means the resource lists through `GET` but cannot be written, so you must reference an existing record from the downstream system. Verify any limited or partial coverage in the live coverage matrix before depending on it.

| Connector                               | Notes                                                                                                                                                     |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Access Financials                       | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Acumatica                               | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Banqup                                  | Customers and invoices are read-only and payments are not in coverage. Use this connector for ingesting AR data, not pushing it.                          |
| Campfire                                | Customers and invoices are writable. Payment writes are limited; verify allocations land before treating an invoice as settled.                           |
| Clear Books (UK)                        | Customers and invoices are read-only. Payments are not in coverage. Read-only AR sync only.                                                               |
| Digits                                  | Customers are read-only. Invoices and payments are not in coverage. Reporting use only.                                                                   |
| DualEntry                               | Customers, invoices, and payments are writable. Standard AR flow.                                                                                         |
| Exact Online                            | Customers are read-only. Look up the customer ID with `GET /accounting/customers` before invoicing. Invoices and payments are writable.                   |
| Exact Online (NL)                       | Same as Exact Online. Customers read-only, invoices and payments writable.                                                                                |
| Exact Online (UK)                       | Customers are read-only. Invoices and payments are writable.                                                                                              |
| FreeAgent                               | Customers and invoices are writable. Payments are not in coverage; mark invoices as paid through the FreeAgent UI or by other means.                      |
| FreshBooks                              | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Intuit Enterprise Suite                 | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| KashFlow                                | Customers and invoices are read-only. Payments are read-only. Use for reporting only.                                                                     |
| Microsoft Dynamics 365 Business Central | Customers and invoices are writable. Payments are read-only and must be posted natively.                                                                  |
| Moneybird                               | Customers and invoices are writable. Payment writes are limited; confirm allocations land before treating an invoice as settled.                          |
| MRI Software                            | Customers are read-only. Invoices and payments are not in coverage.                                                                                       |
| MYOB                                    | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| MYOB Acumatica                          | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| NetSuite                                | Customers, invoices, and payments are all writable. Subsidiary is required on every record in OneWorld accounts.                                          |
| Odoo                                    | Customers, invoices, and payments are all writable. Invoices map to `account.move` with type `out_invoice`.                                               |
| Pennylane                               | Customers and invoices are writable. Payments are not in coverage.                                                                                        |
| Procountor (FI)                         | Customers and invoices are writable. Payments are read-only.                                                                                              |
| QuickBooks Online                       | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Rillet                                  | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Sage Business Cloud Accounting          | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Sage Intacct                            | Customers, invoices, and payments are all writable. Dimensions and locations may be required depending on the entity setup.                               |
| Sage Intacct (REST)                     | The REST connector does not yet expose customers, invoices, or payments. Use the legacy Sage Intacct connector for AR automation.                         |
| Stripe                                  | Customers and invoices are writable. Payments are read-only because Stripe collects them itself. Treat the Stripe-emitted payment as the source of truth. |
| Visma Netvisor                          | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Wave                                    | Customers and invoices are writable. Payments are not in coverage. Apply payments inside Wave or via journal entries.                                     |
| Workday                                 | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |
| Xero                                    | Customers, invoices, and payments are all writable. Send invoices with `status: "AUTHORISED"` to make them payable.                                       |
| Yuki                                    | Customers and invoices are writable. Payments are not in coverage.                                                                                        |
| Zoho Books                              | Customers, invoices, and payments are all writable. Standard AR flow.                                                                                     |

### Stripe

Stripe is not a general ledger. Apideck exposes Stripe customers and invoices as writable so you can push billable items, but payment objects are read-only because Stripe charges the card itself and emits the resulting payment. For an AR automation product, the typical pattern is to create the invoice through Apideck, let Stripe collect, then mirror the resulting payment into your other accounting connector via [`POST /accounting/payments`](/apis/accounting/reference/payments#paymentsAdd).

### NetSuite

In a OneWorld account, every customer, invoice, and payment must reference a subsidiary. Resolve subsidiaries with [`GET /accounting/subsidiaries`](/apis/accounting/reference/ledger-accounts#subsidiariesAll) and persist the chosen subsidiary against the Apideck consumer so you do not have to look it up per request.

### Exact Online

Customers are read-only across the Exact Online connectors (`exact-online`, `exact-online-nl`, `exact-online-uk`). Build a customer picker in your UI that calls [`GET /accounting/customers`](/apis/accounting/reference/customers#customersAll) and have the user select the matching record before you push an invoice.

### QuickBooks Online and Xero

Both flip invoice status to paid automatically when a payment with an `allocations[]` entry is posted. Do not attempt to update the invoice status directly. The connector will reject status writes against computed fields.

## Reconcile a partial payment end to end

In a test company, use an invoice for 500.00 and a settled payment of 200.00 in the same currency. This illustrative example excludes taxes, credits, fees and exchange differences; it is a business checkpoint sequence, not a connector-independent payload.

1. Resolve the customer and required revenue, item and tax references. Create the invoice, store its returned ID, and read it back before starting collection.
2. Collect payment through your payment provider. Creating an accounting payment records a settlement; it does not charge the customer.
3. Once 200.00 has settled, record the payment using the supported invoice allocation fields for that connector. Store the returned payment ID alongside your settlement reference.
4. Read the invoice and payment back. The expected remaining balance in this example is 300.00. If the API omits the balance, keep it unavailable until verified; do not convert a missing value to zero.
5. Reconcile the next settlement against the remaining balance. Refunds, credits and currency conversion require their own accounting treatment and supported operations.

If invoice creation times out, resolve its outcome before sending another create. If payment recording fails, retain the existing invoice and retry only the failed recording step after checking the error. Use an application-level uniqueness rule for the tenant, connection and settlement reference, and reconcile ambiguous responses before retrying writes.

Before launch, demonstrate a partial payment, a duplicate settlement notification and an invalid customer reference. The same settlement must not produce two payments; a rejected invoice must not be marked as exported. Trace provider validation with [Logs](/logs#follow-parent-and-child-requests).

## Frequently asked questions

### Does creating an invoice or payment charge the customer?

No. These API records represent the receivable and its settlement in the accounting system. Your application and payment provider own collection. Record an accounting payment after settlement and verify its allocation to the invoice.

### How do I represent a partial invoice payment?

Record the settled amount using the connector’s supported payment-allocation fields, then read back the invoice and payment. Verify the remaining balance rather than marking the invoice paid locally. Follow [Reconcile a partial payment end to end](#reconcile-a-partial-payment-end-to-end).

### Can I create customers on every accounting connector?

No. Customer write support varies; this guide describes read-only customer behavior for Exact Online. Check [customer coverage](/apis/accounting/coverage?resources=customers) and use an existing source customer ID when creation is unavailable.

### What should I do if payment recording fails after money has settled?

Retain the settlement reference and existing invoice ID. Diagnose and retry the failed accounting-recording step only after checking whether it was accepted. Do not collect payment again to repair the ledger. Use [Logs](/logs) and verify the final invoice allocation.

## Next steps

- [Mark invoices and bills as paid](/guides/mark-invoices-as-paid) for the deeper allocation pattern, including partial payments and credit notes.
- [Handling bills and expenses](/guides/expenses-bills) for the accounts payable counterpart of this flow.
- [Accounting API reference](/apis/accounting/reference) for the full list of operations and fields.

## Related reading

From Apideck.

- [Accounting & ERP Partnership Guide](https://www.apideck.com/blog/accounting-and-erp-partnership-guide): Understand the partnership and production-access requirements to investigate when planning an accounting connector launch.
- [From Accounts Receivable to Lending Automation: Integration Use Cases for Vertical SaaS](https://www.apideck.com/blog/from-accounts-receivable-to-lending-automation-integration-use-cases-for-vertical-saas): Connect receivables and lending use cases to the accounting data your vertical SaaS product needs.
- [Apideck and Dolfin AI: the connectivity layer for embedded finance agents](https://www.apideck.com/blog/apideck-dolfin-ai-embedded-finance-agents): See how Dolfin approaches embedded accounts payable and receivable agents with connected accounting systems.
