---
title: "How to Sync Customers or Suppliers with Unified's Accounting API"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_sync_customers_or_suppliers_with_unified_accounting_api-icon.png
date: 2026-01-19T21:54:00.000Z
tag: Product
description: "Syncing customers and suppliers sounds like a basic accounting task. It becomes a product problem the moment you support more than one accounting system."
url: "https://unified.to/blog/how_to_sync_customers_or_suppliers_with_unified_accounting_api"
---

# How to Sync Customers or Suppliers with Unified's Accounting API
------
_January 19, 2026_

Syncing customers and suppliers sounds like a basic accounting task. It becomes a product problem the moment you support more than one accounting system.


Many platforms treat customers and vendors as separate objects. Others merge them. Some allow a single entity to be both. Identifiers aren't stable, names change, emails aren't unique, and 'vendor vs customer' is often inferred rather than explicit. When teams integrate vendor-by-vendor, this usually leads to duplicate records, broken updates, or brittle reconciliation logic.


For a PM, this raises hard questions early:

- Is a customer and a supplier the same logical entity in our product?
- What happens if a contact switches roles or has multiple roles?
- How do we guarantee idempotent syncs without relying on provider-specific IDs?

Many products solve this by branching logic per accounting platform or maintaining separate customer and vendor pipelines. That increases complexity and makes long-term maintenance expensive.


Unified's [Accounting API](/accounting) takes a different approach. It provides **a single normalized contact model**—`AccountingContact`—and uses explicit boolean flags (`is_customer`, `is_supplier`) to represent role. Identity is stable, behavior is documented, and sync semantics are consistent across accounting providers.


This guide shows how to build a production-grade customer and supplier sync on top of that model—listing contacts, filtering by role, handling incremental updates, creating and updating records.


## Prerequisites


You'll need:

- A Unified workspace
- An API key
- A `connectionId` for an authorized accounting integration
- Node.js 18+ (or equivalent runtime)

Install the SDK:


```bash
npm install @unified-api/typescript-sdk
```


Initialize the client:


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

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


## The AccountingContact model (important)


Unified normalizes customers and suppliers into **one object**:


```typescript
type AccountingContact = {
  id?: string;
  name?: string;
  first_name?: string;
  last_name?: string;
  emails?: { email?: string; type?: "WORK" | "HOME" | "OTHER" }[];
  telephones?: { telephone?: string; type?: "WORK" | "HOME" | "OTHER" | "FAX" | "MOBILE" }[];
  billing_address?: { ... };
  shipping_address?: { ... };
  is_customer?: boolean;
  is_supplier?: boolean;
  is_active?: boolean;
  tax_number?: string;
  payment_methods?: { ... }[];
  company_name?: string;
  identification?: string;
  updated_at?: string;
};
```


**Key implications:**

- Customers and suppliers are not separate endpoints.
- Filtering is done using `is_customer` / `is_supplier`.
- The stable identifier is `(connectionId, id)`.

## Step 1: List accounting contacts


Use `listAccountingContacts` to fetch contacts for a connection.


```typescript
const contacts = await unified.accounting.listAccountingContacts({
  connectionId,
  limit: 100,
});
```


### Pagination rules

- `limit` defaults to 100 (maximum)
- Use `offset` to paginate
- When returned results `< limit`, there are no more records

Example paginated fetch:


```typescript
let offset = 0;
const allContacts: AccountingContact[] = [];

while (true) {
  const page = await unified.accounting.listAccountingContacts({
    connectionId,
    limit: 100,
    offset,
  });

  allContacts.push(...page);

  if (page.length < 100) break;
  offset += 100;
}
```


## Step 2: Filter customers or suppliers


Since Unified uses a shared model, filter in your application:


```typescript
const customers = allContacts.filter(c => c.is_customer);
const suppliers = allContacts.filter(c => c.is_supplier);
```


You can also filter out inactive records:


```typescript
const activeCustomers = customers.filter(c => c.is_active !== false);
```


## Step 3: Incremental sync using updated timestamps


To avoid full re-syncs, use `updated_gte`:


```typescript
const updatedSince = "2025-01-01T00:00:00Z";

const updatedContacts = await unified.accounting.listAccountingContacts({
  connectionId,
  updatedGte: updatedSince,
  limit: 100,
});
```


**Notes:**

- Dates must be ISO-8601
- Time and timezone are optional (defaults to UTC)
- This is the recommended polling strategy when [webhooks](/blog/replacing_polling_with_unified_webhooks_and_virtual_streams) aren't used

## Step 4: Store sync keys correctly


For every contact you store locally, persist:


```typescript
{
  connectionId: string;
  unifiedContactId: string; // AccountingContact.id
}
```


Do **not** rely on:

- name
- email
- `identification`
- raw provider IDs

Those are not guaranteed to be unique or stable.


## Step 5: Create a customer or supplier


To create a new contact, use `createAccountingContact`.


```typescript
const created = await unified.accounting.createAccountingContact({
  connectionId,
  accountingContact: {
    name: "Acme Corp",
    is_customer: true,
    emails: [{ email: "billing@acme.com", type: "WORK" }],
  },
});
```


**Important:**

- This is a full create (POST)
- You must supply the fields you want set
- The response returns the created `AccountingContact`

## Step 6: Update a contact (PUT)


Unified's **update method** is **PUT**, shown as:


```typescript
updateAccountingContact
```


This replaces the contact resource.


```typescript
const updated = await unified.accounting.updateAccountingContact({
  connectionId,
  id: existingUnifiedContactId,
  accountingContact: {
    name: "Acme Corporation",
    is_customer: true,
    is_supplier: false,
  },
});
```


## Step 7: Delete a contact (optional)


To remove a contact:


```typescript
await unified.accounting.removeAccountingContact({
  connectionId,
  id: unifiedContactId,
});
```


This maps to:


```plain text
DELETE /accounting/{connection_id}/contact/{id}
```


## Final notes


Customers and suppliers are one of the most failure-prone parts of accounting integrations, not because the data is complex, but because identity is often split across inconsistent models.


By treating customers and suppliers as a single normalized `AccountingContact` and relying on explicit role flags (`is_customer`, `is_supplier`), you avoid duplicate records, vendor-specific branching, and fragile reconciliation logic. The sync patterns in this guide are designed to be safe by default: stable identifiers, explicit [pagination](/docs/reference/pagination), timestamp-based incremental updates, and documented create/update/delete semantics.


Used this way, Unified's Accounting API gives you a predictable, maintainable foundation for customer and supplier syncs across accounting systems—without guessing, scraping, or vendor-specific code paths.


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


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