Skip to content

Contacts, customers and suppliers

Is a customer who is also a supplier one record or two? Xero says one. QuickBooks says two, with no link between them. NetSuite says two, related through a shared entity abstraction. This is the first modelling decision an integration has to make, and it is irreversible in practice.

Concepts
10
Divergences
3
Structural
1

Endpoints

Xero

GET /Contacts

One object, both roles

QBO

SELECT * FROM Customer / Vendor

Two entities, two queries

NetSuite

SuiteQL: FROM customer / vendor

Two record types over a shared entity table

The same concept in three vocabularies

Read across a row. Where a cell is absent, the concept has nowhere to live in that system — which is a design constraint, not a gap to paper over.

NativeNative
— A field or object exists for it
PartialPartial
— Exists with a caveat that changes what you can build
DerivedDerived
— Not stored — you compute it
AbsentAbsent
— Nothing in the API
Contacts, customers and suppliers fields compared across Xero, QuickBooks Online and NetSuite
ConceptXeroQBONetSuite
Role modelThe whole page in one row.
Native

One Contact

IsCustomer / IsSupplier are read-only, derived from transaction history

Partial

Customer and Vendor

Separate entities, no link, independent balances

Partial

customer and vendor

Separate records over a shared entity abstraction

Uniqueness
Native

Name must be unique

Org-wide. A duplicate write fails

Native

DisplayName must be unique

Across Customer, Vendor, Employee and Other Name — the same party is often "Acme Ltd" and "Acme Ltd (V)"

Native

entityid

Configurable auto-numbering

Hierarchy
Partial

ContactGroups

Flat grouping, not parenting

Native

ParentRef + Job

Sub-customers double as jobs; BillWithParent rolls billing up

Native

parent

True hierarchy on both customer and vendor

Addresses
Native

Addresses[]

Typed STREET / POBOX

Partial

BillAddr, ShipAddr, OtherAddr

Fixed slots, not a list

Native

addressbook sublist

Many, with defaults per purpose

People at the party
Native

ContactPersons[]

Partial

GivenName / FamilyName / PrimaryEmailAddr

One person only

Native

contact records

Linked, many

Posting defaults
Native

SalesDefaultAccountCode, PurchasesDefaultAccountCode, PaymentTerms

Partial

SalesTermRef, PaymentMethodRef, CurrencyRef

Native

terms, receivablesaccount, currency

Tax identity
Native

TaxNumber

Native

PrimaryTaxIdentifier / TaxIdentifier

Native

vatregnumber, taxidnum

1099 / contractor flag
Absent

—

Native

Vendor1099

Native

is1099eligible

Balance on the record
Absent

—

Partial

Balance, BalanceWithJobs

Customer only

Absent

—

Query it

Archive
Native

ContactStatus: ACTIVE / ARCHIVED

Native

Active

Native

isinactive

What actually differs, and what you do about it

A difference stated without a response is trivia. Each of these carries the handling.

Structural

The same real party is two records in QuickBooks

QBO · NetSuite

A client who is also a subcontractor is one Contact in Xero and two unlinked records in QBO, each with its own id and balance. DisplayName is unique across customers, vendors and employees, so the two cannot even share a name — the vendor is typically 'Acme Ltd (V)'. Nothing in the API says they are the same party, so any total-exposure figure has to reunite them.

Do — Model a Party with role flags and a list of source refs. Merge is an explicit action showing both balances; auto-merge on a matching tax identifier, never on a name.

Sharp edge

Xero's role flags are read-only and historical

Xero

IsCustomer and IsSupplier are not settable. They become true when the contact first appears on a sales or purchase document. A brand-new contact you intend to bill is neither, until it is.

Do — Treat roles as a hint for filtering, never as a constraint on what a user may do. A contact picker that hides suppliers from an invoice form will hide the new contact the user just created.

Sharp edge

Sub-customers are jobs, projects and departments all at once

QBO

QBO users routinely encode project structure in the customer hierarchy, because there is nowhere else to put it. The same tree is simultaneously a list of parties and a list of engagements, and BillWithParent decides which level carries the receivable.

Do — Import the hierarchy, and expose it as both a party tree and a dimension. Do not flatten it — the parent-level balance and the child-level balance are both real numbers people reconcile.

The type the components consume

The plural `sources` is the entire point of this type. Singular would force a merge decision at import time, before anyone can review it.

contacts.ts
type Party = {
  id: string;
  name: string;
  roles: ("customer" | "supplier")[];   // Xero: one record, both roles. QBO: merged, or not.
  parentId?: string;                     // QBO jobs, NetSuite hierarchy; undefined on Xero
  taxNumber?: string;
  defaultCurrency?: string;
  defaultAccountIds?: { sales?: string; purchases?: string };
  addresses: Address[];                  // normalised from three different shapes
  active: boolean;
  /** Plural. A merged party carries the Customer id and the Vendor id. */
  sources: SourceRef[];
};

What this changes in the interface

The payoff. Every difference above that reaches the surface, and what the component does about it.

One party can hold two source records
The party detail header shows both source links, and any balance shown states which source it came from. A single unlabelled balance on a merged party is a lie of omission.
Roles are derived on Xero
ContactPicker never filters by role in a write context. It sorts by relevance and labels the role it knows about.
QBO customer hierarchies encode projects
The party tree is also offered as a grouping dimension in reports, because that is what the data means to the user who entered it.

Where the accounting is explained

What consumes this payload

The normalised object above is what the components read. These are the surfaces it feeds, the words for the concepts in it, and the document that explains the accounting.

Compared acrossXero docsQuickBooks Online docsNetSuite docs

Works in your QuickBooks Online or Xero file

Get your books closed every month

Lopifi is building AI bookkeeping that keeps your books closed on US GAAP as transactions happen, with a CPA review every quarter, for $200 a month. We’re not open yet.

$200/month when we open. No card now.