QuickBooks Online Customer Management
Overview
Customers are the foundational entity in QuickBooks Online for MSP billing workflows. Each managed services client maps to a QBO Customer record. Customers hold billing addresses, payment terms, outstanding balances, and serve as the parent reference for invoices, payments, and estimates. MSPs commonly use sub-customers to break down billing by service line (e.g., "Acme Corp:Managed Services", "Acme Corp:Project Work").
Key Concepts
Customer Hierarchy
QuickBooks Online supports a parent/sub-customer hierarchy for organizing billing:
Parent Customer: Acme Corporation
+-- Sub-Customer: Acme Corp:Managed Services
+-- Sub-Customer: Acme Corp:Project Work
+-- Sub-Customer: Acme Corp:Hardware
Sub-customers allow MSPs to track revenue and outstanding balances per service line while rolling up to a single client.
Customer vs Job
In QBO, "Jobs" are implemented as sub-customers. A project or engagement for a client is represented as a sub-customer under the parent.
Payment Terms
Payment terms control when invoices are due:
| Term | Description | Common MSP Usage | |------|-------------|------------------| | Due on receipt | Due immediately | Break-fix work | | Net 15 | Due in 15 days | Small clients | | Net 30 | Due in 30 days | Standard managed services | | Net 45 | Due in 45 days | Enterprise clients | | Net 60 | Due in 60 days | Government/education |
Balance Tracking
QBO automatically tracks the customer balance (sum of all unpaid invoices minus unapplied payments). This is critical for MSP accounts receivable management.
Field Reference
Core Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | Id | string | System | Auto-generated unique identifier | | DisplayName | string | Yes | Unique display name (customer-facing) | | CompanyName | string | No | Legal company name | | GivenName | string | No | Contact first name | | FamilyName | string | No | Contact last name | | Active | boolean | No | Whether customer is active (default: true) | | Balance | decimal | Read-only | Outstanding balance | | BalanceWithJobs | decimal | Read-only | Balance including sub-customers | | SyncToken | string | Required for updates | Optimistic locking token |
Contact Fields
| Field | Type | Description | |-------|------|-------------| | PrimaryPhone.FreeFormNumber | string | Primary phone number | | AlternatePhone.FreeFormNumber | string | Alternate phone | | Mobile.FreeFormNumber | string | Mobile phone | | Fax.FreeFormNumber | string | Fax number | | PrimaryEmailAddr.Address | string | Primary email (used for invoice delivery) | | WebAddr.URI | string | Website URL |
Address Fields
| Field | Type | Description | |-------|------|-------------| | BillAddr.Line1 | string | Billing street address | | BillAddr.City | string | Billing city | | BillAddr.CountrySubDivisionCode | string | Billing state/province | | BillAddr.PostalCode | string | Billing postal code | | BillAddr.Country | string | Billing country | | ShipAddr | object | Shipping address (same structure as BillAddr) |
Billing Fields
| Field | Type | Description | |-------|------|-------------| | SalesTermRef.value | string | Payment terms ID (e.g., Net 30) | | PaymentMethodRef.value | string | Default payment method ID | | CurrencyRef.value | string | Currency code (e.g., "USD") | | PreferredDeliveryMethod | string | "Print", "Email", or "None" | | Taxable | boolean | Whether customer is taxable |
Hierarchy Fields
| Field | Type | Description | |-------|------|-------------| | ParentRef.value | string | Parent customer ID (for sub-customers) | | Job | boolean | Whether this is a job (sub-customer) | | Level | integer | Depth in hierarchy (0 = top-level) | | FullyQualifiedName | string | Full path (e.g., "Acme Corp:Managed Services") |
Metadata Fields
| Field | Type | Description | |-------|------|-------------| | MetaData.CreateTime | datetime | Creation timestamp | | MetaData.LastUpdatedTime | datetime | Last update timestamp |
API Patterns
Query Customers
GET /v3/company/{realmId}/query?query=SELECT * FROM Customer WHERE DisplayName LIKE '%Acme%'&minorversion=73
Authorization: Bearer {access_token}
Accept: application/json
curl example:
curl -s -H "Authorization: Bearer $QBO_ACCESS_TOKEN" \
-H "Accept: application/json" \
"https://quickbooks.api.intuit.com/v3/company/$QBO_REALM_ID/query?query=SELECT%20*%20FROM%20Customer%20WHERE%20DisplayName%20LIKE%20'%25Acme%25'&minorversion=73"
Common Queries:
-- All active customers
SELECT * FROM Customer WHERE Active = true ORDERBY DisplayName
-- Customers with outstanding balance
SELECT * FROM Customer WHERE Balance > '0' ORDERBY Balance DESC
-- Find by company name
SELECT * FROM Customer WHERE CompanyName LIKE '%Tech%'
-- Find by email
SELECT * FROM Customer WHERE PrimaryEmailAddr = 'billing@acmecorp.com'
-- Count all customers
SELECT COUNT(*) FROM Customer
Get Single Customer
GET /v3/company/{realmId}/customer/123?minorversion=73
Authorization: Bearer {access_token}
curl -s -H "Authorization: Bearer $QBO_ACCESS_TOKEN" \
-H "Accept: application/json" \
"https://quickbooks.api.intuit.com/v3/company/$QBO_REALM_ID/customer/123?minorversion=73"
Create Customer
POST /v3/company/{realmId}/customer?minorversion=73
Content-Type: application/json
Authorization: Bearer {access_token}
{
"DisplayName": "Acme Corporation",
"CompanyName": "Acme Corporation",
"GivenName": "John",
"FamilyName": "Smith",
"PrimaryPhone": {
"FreeFormNumber": "555-123-4567"
},
"PrimaryEmailAddr": {
"Address": "billing@acmecorp.com"
},
"BillAddr": {
"Line1": "123 Main Street",
"City": "Springfield",
"CountrySubDivisionCode": "IL",
"PostalCode": "62704"
},
"SalesTermRef": {
"value": "3"
},
"PreferredDeliveryMethod": "Email",
"Notes": "MSP managed services client. Contract: 36-month. Primary contact: John Smith."
}
curl -s -X POST \
-H "Authorization: Bearer $QBO_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
"https://quickbooks.api.intuit.com/v3/company/$QBO_REALM_ID/customer?minorversion=73" \
-d '{
"DisplayName": "Acme Corporation",
"CompanyName": "Acme Corporation",
"PrimaryEmailAddr": { "Address": "billing@acmecorp.com" },
"SalesTermRef": { "value": "3" }
}'
Update Customer (Sparse)
POST /v3/company/{realmId}/customer?minorversion=73
Content-Type: application/json
Authorization: Bearer {access_token}
{
"Id": "123",
"SyncToken": "2",
"sparse": true,
"PrimaryPhone": {
"FreeFormNumber": "555-999-8888"
},
"Notes": "Updated billing contact: Jane Doe (555-999-8888)"
}
Deactivate Customer
{
"Id": "123",
"SyncToken": "2",
"sparse": true,
"Active": false
}
Create Sub-Customer
{
"DisplayName": "Acme Corp:Managed Services",
"CompanyName": "Acme Corporation",
"ParentRef": {
"value": "123"
},
"Job": true,
"BillWithParent": true
}
Common Workflows
New MSP Client Onboarding
- Create parent customer with billing info and payment terms
- Create sub-customers for each service line (managed services, projects, hardware)
- Set payment terms based on contract (typically Net 30)
- Configure email delivery for automated invoice sending
- Link to PSA using Notes or custom fields for cross-reference
async function onboardMspClient(clientData) {
// Step 1: Create parent customer
const customer = await createCustomer({
DisplayName: clientData.companyName,
CompanyName: clientData.companyName,
GivenName: clientData.contactFirstName,
FamilyName: clientData.contactLastName,