---
title: "How to Create and Sync Invoices with Unified's Accounting API"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_create_and_sync_invoices_with_unified_accounting_api-icon.png
date: 2026-01-19T21:53:00.000Z
tag: Product
description: "Invoicing is one of the most opinionated parts of accounting systems—and one of the easiest places for integrations to fail."
url: "https://unified.to/blog/how_to_create_and_sync_invoices_with_unified_accounting_api"
---

# How to Create and Sync Invoices with Unified's Accounting API
------
_January 19, 2026_

Invoicing is one of the most opinionated parts of accounting systems—and one of the easiest places for integrations to fail.


Across providers, invoices differ in how they're classified, when they can be edited, how they're delivered, and what 'paid' actually means. Some systems allow drafts to be modified freely. Others lock invoices once authorized. Payment collection, tax handling, and delivery behavior vary widely, even when the surface concepts look identical.


For a PM or backend team, this creates immediate risk:

- Can invoices be created and updated safely across providers without branching logic?
- When is an invoice immutable, and how should failures be surfaced to users?
- How do you control delivery and payment behavior without relying on undocumented defaults?

Many teams discover too late that invoice logic baked for one accounting system doesn't generalize. The result is partial writes, rejected updates, or invoices that don't behave the way users expect.


Unified's [Accounting API](/accounting) is designed to make invoice automation explicit and defensible. Invoices are represented through a normalized `AccountingInvoice` model, with classification handled at the line-item level and lifecycle rules enforced by the provider rather than guessed upstream. Core behaviors—listing, syncing, creating, updating, delivering, and removing invoices—are exposed through documented parameters with consistent [pagination](/docs/reference/pagination) and error semantics.


This guide shows how to build a production-grade invoice sync and write flow using Unified's Accounting API—resolving dependencies, creating classified invoices, controlling delivery and payment behavior, and handling provider constraints without vendor-specific code paths or assumptions.


## What this guide covers

- Listing invoices with pagination and incremental sync
- Resolving required invoice dependencies (contacts, accounts, categories, tax rates)
- Creating invoices with classified line items
- Controlling invoice delivery and payment behavior
- Updating and removing invoices
- Handling pagination, [rate limits](/docs/reference/rate_limits#rate-limits), and provider constraints

## Prerequisites

- Node.js 18+
- A Unified workspace
- An Accounting integration authorized for a customer
- A valid `connectionId`
- Unified API key

## Step 1: Initialize the SDK


```typescript
import { UnifiedTo } from '@unified-api/typescript-sdk';

const sdk = new UnifiedTo({
  security: {
    jwt: process.env.UNIFIED_API_KEY!,
  },
});
```


All Accounting API calls are scoped by `connectionId`, which represents a single authorized customer integration.


## Step 2: Understand the Invoice data model (critical)


Unified represents invoices using the normalized `AccountingInvoice` object.


Key characteristics:

- Invoices are **customer-facing** and reference a `contact_id`
- **Classification happens at the line-item level**
- Line items may reference:
    - `account_id` → `AccountingAccount`
    - `category_ids[]` → `AccountingCategory`
    - `taxrate_id` → `AccountingTaxrate`
- Invoice lifecycle is expressed via `status`
- Payment and delivery behavior is controlled via `payment_collection_method` and `send`
- Provider rules (required fields, allowed transitions) are enforced downstream

### Invoice status values


```typescript
'DRAFT'
'VOIDED'
'AUTHORIZED'
'PAID'
'PARTIALLY_PAID'
'PARTIALLY_REFUNDED'
'REFUNDED'
'SUBMITTED'
'DELETED'
'OVERDUE'
```


These values are normalized, but providers may return additional states. Treat this as an open set.


## Step 3: List invoices (sync in)


To sync invoices into your system, use `listAccountingInvoices`.


```typescript
const results = await sdk.accounting.listAccountingInvoices({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-22T22:41:23.849Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  contact_id: '',
  org_id: '',
  type: '',
  fields: '',
  raw: '',
});
```


### Pagination rules

- Default page size: **100**
- Maximum page size: **100** on most endpoints
- Pagination uses **offset**, not cursors
- You've reached the end when `results.length < limit`

### Incremental sync


Use `updated_gte` to fetch only invoices updated since your last sync timestamp. This is the recommended polling strategy.


## Step 4: Retrieve a single invoice


Use this to verify creates/updates or reconcile provider-side changes.


```typescript
const invoice = await sdk.accounting.getAccountingInvoice({
  connectionId,
  id: invoiceId,
  fields: '',
  raw: '',
});
```


## Step 5: Resolve invoice dependencies


Before creating an invoice, resolve the objects it references.


### Resolve the customer (contact)


Invoices require a `contact_id`.


```typescript
const contacts = await sdk.accounting.listAccountingContacts({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-22T22:41:23.849Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  type: 'CUSTOMER',
  org_id: '',
  fields: '',
  raw: '',
});
```


Select the appropriate `AccountingContact.id`.


### Resolve accounts (optional but common)


```typescript
const accounts = await sdk.accounting.listAccountingAccounts({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-20T00:59:41.301Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  org_id: '',
  fields: '',
  raw: '',
});
```


Use an account ID when your provider supports line-item account classification.


### Resolve categories (optional)


```typescript
const categories = await sdk.accounting.listAccountingCategories({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-20T00:59:41.301Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  parent_id: '',
  fields: '',
  raw: '',
});
```


### Resolve tax rates (optional)


```typescript
const taxrates = await sdk.accounting.listAccountingTaxrates({
  connectionId,
  limit: 50,
  offset: 0,
  updated_gte: '2026-01-20T00:59:41.301Z',
  sort: 'updated_at',
  order: 'asc',
  query: '',
  org_id: '',
  fields: '',
  raw: '',
});
```


## Step 6: Build a provider-safe invoice payload


A minimal, classification-aware invoice payload:


```typescript
const invoice = {
  contact_id: customerId,
  currency: 'USD',
  invoice_number: 'INV-10042',
  notes: 'January subscription',
  payment_collection_method: 'send_invoice',
  send: true,
  lineitems: [
    {
      unit_quantity: 1,
      unit_amount: 500.0,
      tax_amount: 50.0,
      account_id: revenueAccountId,
      category_ids: [subscriptionCategoryId],
      taxrate_id: salesTaxId,
      item_name: 'Pro Plan – January',
    },
  ],
};
```


Important notes:

- Totals are typically derived as

    `unit_quantity * unit_amount + tax_amount`

- Providers may recompute totals or reject mismatches
- Leave optional fields unset unless you are certain the provider supports them

## Step 7: Create the invoice


```typescript
const created = await sdk.accounting.createAccountingInvoice({
  connectionId,
  accountingInvoice: invoice,
  fields: '',
  raw: '',
});
```


The response is a single `AccountingInvoice` object reflecting the provider's current state.


## Step 8: Control invoice delivery and payment behavior


### Send invoice (email / link)


```typescript
payment_collection_method: 'send_invoice'
send: true
```


This typically emails the invoice or makes a payment link available via `invoice.url`.


### Charge automatically


```typescript
payment_collection_method: 'charge_automatically'
send: true
```


This attempts to charge the customer's default payment method.


**Important:** Provider behavior varies. Some platforms require additional configuration or a default payment method on the contact. Some may ignore `send` depending on provider capabilities.


## Step 9: Update an invoice


To update an invoice, supply its `id`.


```typescript
const updated = await sdk.accounting.updateAccountingInvoice({
  connectionId,
  id: invoiceId,
  accountingInvoice: {
    notes: 'Updated invoice notes',
  },
  fields: '',
  raw: '',
});
```


Invoice updates may be restricted by provider lifecycle rules. For example, some providers do not allow updates once an invoice is submitted or paid.


## Step 10: Remove an invoice (optional)


```typescript
await sdk.accounting.removeAccountingInvoice({
  connectionId,
  id: invoiceId,
});
```


Use this cautiously. Some providers treat removal as a soft delete or disallow deletion in certain states.


## Pagination, filtering, and querying


All list endpoints share the same semantics:

- `limit` / `offset` pagination
- `updated_gte` for incremental sync
- Sorting via `sort` (`name`, `updated_at`, `created_at`)
- Ordering via `order` (`asc`, `desc`)

### Query limitations

- `query` filters by **name or email only**
- Behavior is integration-specific
- For complex filtering, Unified strongly recommends syncing data into your own database

## Rate limits and retries


Unified enforces:

1. **Upstream provider rate limits**
2. **Unified-level fairness limits**

Rate limit errors return **HTTP 429**.


### Recommended handling

- Exponential backoff (1s → 2s → 4s → 8s)
- Add jitter
- Cap retry attempts
- Queue requests for high-volume operations

### Strong recommendation


For continuous invoice sync or lifecycle tracking, use **webhooks** instead of polling. Unified manages retries, backoff, and initial sync for you.


## Error handling


Unified uses standard HTTP status codes:

- `400` – invalid parameters
- `401` – broken or revoked connection
- `403` – missing scopes
- `404` – resource not found
- `429` – rate limit exceeded
- `501` – feature not supported by this integration
- `500` – internal error (retryable)

Errors are often provider-specific and should be surfaced clearly to users.


## Summary


Using Unified's Accounting API, you can:

- List, create, update, and remove invoices across accounting platforms
- Classify invoice line items using normalized accounts, categories, and tax rates
- Control invoice delivery and payment behavior through a single API
- Handle pagination, rate limits, and provider constraints safely

The key design principle is **defensive integration**: rely on normalized models, expect provider variance, and avoid assuming required fields or lifecycle transitions.


If you need real-time invoice updates or high-volume sync, pair this workflow with Unified webhooks.


→ [Start your 30-day free trial](https://app.unified.to/login)


→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)