Skip to main content
Your app knows a customer as ACME, Inc.. QuickBooks has Acme Inc, or Acme LLC, or both. Before you add the customer, or put an invoice on it, you need to know which record your app means. QuickBooks can’t tell you. A full_name lookup is exact: it ignores case and the spaces at either end, and nothing else. The only looser filter is “contains” (or “starts with”, “ends with”), on one list at a time, unranked, so ACME, Inc. doesn’t find Acme Inc, and Smith, John doesn’t find John Smith. Matching does. QuBe Sync keeps an index of the names in the company file, and a match ranks them against yours in milliseconds. It ignores case, accents, punctuation, company suffixes such as Inc and LLC, spacing and word order, finds near misses, keeps store and unit numbers apart, and can compare an account number, email, phone, city and postal code too.
Matching needs a realtime connection (or a simulated one in your sandbox). A standard connection is refused with matching_requires_realtime. Your sandbox app isn’t billed, so switching a sandbox connection to realtime costs nothing: do it from its page in the dashboard. A workflow’s match state is never refused: on a standard connection it falls back to QuickBooks’ own filters.

Match a name

Send the name in the JSON body, never the query string: The answer comes back at once, a 200. This file has both Acme LLC and Acme Corp. Both are the same name once punctuation and suffixes are set aside, so neither is certain, and the one with the email you specified comes first:
Response
string
required
Which lists to look in: customer, vendor, employee, other_name, or entity for all four (they share one name space in QuickBooks); item for every item type; or account, class, standard_terms, date_driven_terms, sales_tax_code, payment_method, ship_method, price_level, customer_type, vendor_type, job_type, sales_rep or customer_msg.
string
required
The name your app has for the record, at most 255 characters. With a parent, its own name only: Kitchen, not Smith:Kitchen. Its numbers must agree with a record’s: see numbered locations.
string
For a job, a sub-item, a subaccount and the like: the parent’s full name or its list_id, so only that parent’s children are matched. Without one, children match on their own name too, and say whose they are (a job under Smith). Taken for customer, entity, item, account, class, customer_type, vendor_type and job_type.
object
For customer, vendor, employee, other_name and entity: what else your app knows, as account_number, email, phone, postal_code, city and company_name. The same account number makes a strong match whatever the name. The same email, phone or postal code, with a name at least 0.4 alike (see score), makes a strong match: Bill Johnson with Bill’s email finds William Johnson. Each one in common is a reason, and ranks a candidate higher; a city or company name does only that. See distinguishing fields for how each is compared.
boolean
default:"false"
true answers only exact or none: the one record with this full name, as QuickBooks compares names, or nothing. Never a fuzzy match, so you can act on it without asking.
number
This call’s candidate threshold, from 0.3 to 1.0, in place of the default 0.44. Raise it to list only close names; lower it to list more. Not taken with exact_only.

What the answer means

match is the first of these that applies:
Only use an exact match without asking. Acme Inc and Acme LLC are a strong match for each other, and they can be two companies: putting an invoice on the wrong one is worse than asking. A phone that a household or an office shares can make another person a strong match, too.
Each record in record or candidates has:
string
QuickBooks’ id for the record: what a reference such as customer_ref or item_ref names it by.
string
Its full name. A job’s or a sub-item’s starts with its parent’s: Smith:Kitchen.
string
The list it’s in: customer, vendor, an item type such as item_service, and so on.
boolean
false for an inactive record. Inactive records are matched too: QuickBooks hides them, but they keep their names, and adding another with the same name fails with 3100.
number
How alike the names are, from 0 to 1, once case, accents, punctuation and suffixes are set aside: 1.0 for an exact match. A candidate has at least 0.44, unless it’s there for another reason, such as two letters swapped (Desing for Design), which scores low on a short name.
string[]
Why it matched, as phrases you can show: same name, same after removing punctuation and suffixes, same words in a different order, same apart from spacing, two letters swapped, similar name, or different name for a record found by its account number alone; then same number; then same account number, same email, same phone, same postal code, same city or same company name; and a job under Smith for a child matched without a parent.
string | null
Its email, lower-cased, for a person to tell candidates apart. null when it has none, or isn’t a customer, vendor, employee or other name.
string | null
Its phone, for a person to tell candidates apart: a 10-digit one as 512-555-0142. null when it has none.
And the answer says how many records qualified:
integer
For candidates: how many records qualified, of which candidates lists the first 10. 0 for any other tier.
boolean
false when candidates_total is a lower bound: a match reads at most 50 records, the most alike first, and it read that many, so more may qualify.
Before a change depends on a matched record, read it by its list_id (GET /customers?list_id=...). The index can be a few minutes behind QuickBooks for what someone did in QuickBooks itself: a record renamed there since comes back with its new name.

Numbered locations and crowded files

Some company files are full of names that differ only by a number: franchise locations (Subway #1234, Subway #1235, and so on), units, a customer’s jobs by year. On their letters alone those names look nearly the same: Subway 1234 and Subway 1235 are 0.71 alike. So matching treats the numbers as what tells records apart. Numbers must agree. A word with a digit in it is a number to a match: 1234 in Subway #1234, 4B in Unit 4B. Leading zeros don’t count, so #01234 is 1234. When your name and a record’s both have numbers, the record must have every number you sent. One with a different number instead is never offered, no matter the other similarities. When exactly one record has the same numbers and a name at least 0.4 alike, it’s a strong match, with the reason same number. A record with your numbers and more can be a candidate, with the reason the number asked, and more, but never a strong match. A name with a number, asked for without one, is matched on the rest of the name, so Subway lists the stores. Two things outrank a different number: the exact full name, and the account_number you sent.

When the list is crowded

When candidates_total is more than the 10 in candidates, or candidates_total_complete is false, the name alone doesn’t pick out a record. Don’t show your user a pick list of near-identical names. Ask for what tells them apart: the store number, the city, the postal code or your account number for it. Then match again with that: The one store with that postal code is a strong match. If several share it, the ones in Austin come first.

Distinguishing fields

Send what your app knows in fields. Each one in common is a reason and ranks a candidate higher, and some make a match strong on their own: The city and postal_code are compared with a customer’s billing address, and with a vendor’s, an employee’s or an other name’s own address.

Tighter or looser, for one call

  • exact_only: true answers exact or none, never a fuzzy match. Use it when your app should act only on a certain match, as when it’s about to create the record if it isn’t there.
  • min_score moves the candidate threshold from 0.44, anywhere from 0.3 to 1.0. Raise it to see only close names, or lower it to cast wider. exact and strong don’t change, and a record two letters swapped stays a candidate.

Match once, then keep the list_id

Matching is for the records your app has no QuickBooks id for yet. Once your user confirms a match, store its list_id on your own record, and from then on refer to the QuickBooks record by it, such as customer_ref: {list_id} on an invoice. A list_id survives renames; a name doesn’t. It’s unique only within that list family (customers, vendors, employees and other names share one) of that company file, so keep it with the connection it came from (see Making requests).
  • Match again only when the list_id stops working. That happens when the record is deleted, or merged into another, in QuickBooks: a lookup by the list_id answers status code 500 with no record, and a request that references it is refused with 3120 (see Error handling). If your app keeps in step with the pull_changes workflow, a merge comes through as a deletion of the record merged away.
  • Give the records you create an id you can match on. An app that creates customers can set their account_number to its own id for them, and match on that later, even after someone renames the customer in QuickBooks. Check first that the company doesn’t already use account numbers for something else.

In a workflow

A workflow does the same match as one of its steps: a match state. It takes the endpoint’s body as templates (kind, name, parent, fields, exact_only and min_score), so one chart serves any record, and it always raises success, with the endpoint’s answer in response.data. The chart branches on response.data.match:
look_up
Here an exact match is used at once, none ends the run, a crowded answer asks your app for a city or postal code and matches again, and a strong match or a few candidates go to a wait state that asks your app which one is meant. The same rule as everywhere: only use exact without asking. The fallback. On a connection downgraded to standard mid-run, or if the index’s build gives up, the step sends QuickBooks’ own filters instead, as ordinary requests of the run: the exact full name, and the “contains” name filter, for each list the kind covers. The answer has the same shape, with index.state unavailable. It is exact, or candidates without scores (score is null, with the reason contains the name), or none, and never strong. exact_only skips the “contains” query, and min_score and fields have nothing to apply to, so they are ignored. So the run finishes, without the ranking, punctuation, suffixes, word order, typos and other fields the index matches on. Or let a template do it. The shipped resolve template finds a list record of any kind (a customer, a job under its parent, a vendor, an item, an account) by the list_id you stored, then by name. It asks your app about near matches, or for a detail when too many are alike, and reads the record whole before it’s used. ensure creates what resolve doesn’t find, asking when the name is held elsewhere in its name space. add_transaction makes sure of every record a transaction names before adding it. See Pick a template. The state’s keys, its events and what the run records are under Match states, and Matching names in a chart has this chart whole.

Turning it on

Each connection’s index is built the first time something needs it. QuBe Sync reads the names in the company file’s lists from QuickBooks itself, a step at a time, as the Web Connector checks in. Your own requests always go first, and those reads never appear among your requests. It takes a few Web Connector sessions, more on a large company file.
1

Opt in, with a webhook_url

Opting in starts the build ahead of your first match:It answers 202 with the connection’s name_index while the index builds, or 200 if it’s already ready, and then nothing is sent. Opting in again while it builds replaces the webhook_url, so it’s safe to repeat. A first match does the same as opting in, without the webhook: it’s refused with name_index_building, and starts the build.
2

Get name_index.ready

When every list is in, QuBe Sync sends your webhook_url this, signed like every other webhook:
name_index.ready
If QuickBooks’ answers to one step are lost three times in a row (the session ended, or the Web Connector reported an error), the build gives up and you get name_index.failed instead. It starts again on its own a day later while the connection is matching, or at your next match or opt-in.
3

Match

Every match from then on is answered from the index.
You can also read where an index stands on the connection itself (GET /connections/{connection_id}):
object
state is ready, building, not_built (nothing has needed matching yet) or unavailable (a standard connection). as_of is when it last caught up with QuickBooks, and records how many records it holds.

How current it is

index.as_of in every answer says when the index last caught up with QuickBooks. A record added, renamed or deleted in QuickBooks itself after that isn’t reflected yet. What you add, rename or delete through QuBe Sync is. When QuickBooks answers one of your requests, or a workflow run’s, that adds or changes a record in a list the index holds (POST /customers, an ItemServiceAddRq, a ListDelRq), the record goes into the index before the answer reaches your webhook or the run’s next step. So a run that creates an item, followed seconds later by one that names it, finds it as exact. A request that asks for some fields only (include_ret_element) isn’t taken this way; the next match refreshes instead.

Errors

See Error handling for the shapes, and the API reference for both endpoints.