Connect QuickBooks Online

Connect a QuickBooks Online company to a Wingspan Account, map vendors, customers, and accounts, and audit what syncs, using the V3 API.

This page walks through connecting a QuickBooks Online (QBO) company to a Wingspan Account through the V3 API, setting the default mappings, linking payees and payers to QBO vendors and customers, and checking what synced. Once connected, Wingspan writes your payables to QBO as Bills and your invoices as QBO Invoices, and records their payments.

You can also connect QuickBooks from Settings > Integrations in the Wingspan app. The steps below are the API equivalent.

How it works

An AccountingConnection links one Wingspan Account to one QBO company. Each Account can have one active QuickBooks connection. The connection has two separate status fields:

FieldValuesMeaning
statusCreated, Connecting, Connected, RetryingConnection, DisconnectedThe OAuth link to QuickBooks.
syncStateNeverSynced, Syncing, Synced, OutOfSyncHow fresh the books are. Derived by Wingspan; you never set it.

Wingspan keeps a normalized, read-only copy of your QBO reference data (vendors, customers, items, classes, and chart of accounts). You map Wingspan records to those copies by their Wingspan IDs, not by QBO's native IDs.

sequenceDiagram
  participant App as Your app
  participant WS as Wingspan API
  participant QBO as QuickBooks
  App->>WS: POST /accounting-connections
  App->>WS: POST /{id}/connect
  WS-->>App: authorizeUrl, state
  App->>QBO: Send the user to authorizeUrl
  QBO-->>App: Redirect with code, state, realmId
  App->>WS: POST /{id}/complete-connection
  App->>WS: POST /{id}/refresh-reference-data
  App->>WS: GET vendors, items, classes, ledger-accounts
  App->>WS: PATCH /{id} mappingDefaults
  Note over WS,QBO: Payables and invoices sync to QBO

Prerequisites

  • An Account you can act on. Accounting connections are Account-scoped: send X-Wingspan-Account to connect a child Account.
  • A QuickBooks Online user who can authorize the company.

1. Create the connection

Create an accounting connection:

curl -X POST https://api.wingspan.app/v3/platform/accounting-connections \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "partner": "Quickbooks", "externalId": "qbo-main" }'
// 201 Created (trimmed)
{
  "id": "Qc4mTn7wKp2xRb8dLs5vHa",
  "partner": "Quickbooks",
  "accountId": "Nw7kQ2pLx9RtVb3mHc5dZa",
  "status": "Created",
  "syncState": "NeverSynced"
}

A second active QuickBooks connection on the same Account returns 409 with detailCode: integrations.PartnerAlreadyConnected.

2. Authorize with QuickBooks

Call Connect to get the QuickBooks authorization URL:

curl -X POST https://api.wingspan.app/v3/platform/accounting-connections/Qc4mTn7wKp2xRb8dLs5vHa/connect \
  -H "Authorization: Bearer $WINGSPAN_TOKEN"
{
  "authorizeUrl": "<QuickBooks authorization URL>",
  "state": "<opaque>",
  "expiresAt": "2026-09-24T15:20:00Z"
}

Send the user to authorizeUrl. They sign in to Intuit and pick the QBO company. When QuickBooks redirects back, pass the code, state, and realmId it returned to Complete the connection:

curl -X POST https://api.wingspan.app/v3/platform/accounting-connections/Qc4mTn7wKp2xRb8dLs5vHa/complete-connection \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "<from redirect>", "state": "<from redirect>", "realmId": "<from redirect>" }'

The response shows status: Connected and partnerCompanyName, and Wingspan fires AccountingConnection.Connected. The state is single-use and expires: an invalid or expired one returns 422 with detailCode: integrations.OAuthStateInvalid. A realmId that doesn't match the company that started the flow returns 422 with integrations.RealmMismatch. In either case, call connect again.

3. Pull QuickBooks reference data

Refresh reference data copies the collections you name into Wingspan:

curl -X POST https://api.wingspan.app/v3/platform/accounting-connections/Qc4mTn7wKp2xRb8dLs5vHa/refresh-reference-data \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "collections": ["LedgerAccounts", "Customers", "Vendors", "Items", "Classes"] }'

The response is 202 with an async operation (operationType: ReferenceDataRefresh). Today the work finishes before the response returns, so the operation is usually already Completed, PartiallyCompleted, or Failed. One collection failing doesn't block the others; check progress and error, and refresh the failed collection again.

Then read the copies:

CollectionCall
Chart of accountsGET .../ledger-accounts
Items (products and services)GET .../items
ClassesGET .../classes
VendorsGET .../vendors
CustomersGET .../customers

Each record has a Wingspan id, the QBO displayName, and a health of Healthy, Stale, or Error.

4. Set the default mappings

Update the connection with mappingDefaults. Every ID must come from step 3 for this same connection, or the request returns 422. This replaces V1's "default line item and default expense account" setup screen.

curl -X PATCH https://api.wingspan.app/v3/platform/accounting-connections/Qc4mTn7wKp2xRb8dLs5vHa \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mappingDefaults": {
      "defaultItemId": "Op6rTw1kQn4xLm8dVs2bHc",
      "expenseLedgerAccountId": "Au9dKp3wQx7tLm2nVr5sHb",
      "payablePaymentLedgerAccountId": "Ro4kQw8nLx2tPm6dVb3sHa"
    }
  }'
FieldUsed for
defaultItemIdThe QBO item on invoice lines Wingspan writes
expenseLedgerAccountIdThe expense account on bill lines
payablePaymentLedgerAccountIdThe bank account a bill payment is recorded against
receivablePaymentLedgerAccountIdThe account an invoice payment is recorded against
payableClassId, receivableClassIdThe QBO class for bills and invoices

5. Map payees and payers to QBO vendors and customers

Link each Wingspan payee to its QBO vendor, and each payer to its QBO customer, using the Wingspan IDs from step 3:

curl -X PATCH https://api.wingspan.app/v3/payments/payees/Py3mQw7kLx2tRb9nVd4sHa \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "quickbooksVendorId": "Wh7pQs2nKx5tLm9dVb3rHa" }'
RecordFields
Payee (Update a payee)quickbooksVendorId (an AccountingVendor.id), quickbooksExpenseAccountId (a ledger account id)
Payer (Update a payer)quickbooksCustomerId (an AccountingCustomer.id), quickbooksItemId (an AccountingItem.id for that payer's invoice lines)

Match by email or name from the vendor and customer lists. A payee with no mapping gets a new QBO vendor when its first payable syncs.

What Wingspan writes to QuickBooks

These mappings describe what the sync writes today. They are carried over from V1 with V3 names.

Payable to QBO Bill

QBO fieldWingspan source
TotalAmtPayable total
DueDatePayable dueDate
LinePayable line items (description, amount, expense account)
VendorRefThe payee's mapped QBO vendor
LinkedTxnThe QBO BillPayment, once the payable is paid

Paid payable to QBO BillPayment: VendorRef (the payee's vendor), TotalAmt (payable total), PayType Check, CheckPayment.BankAccountRef (your mapped payment account), and one line linking the Bill.

New payee to QBO Vendor: PrimaryEmailAddr and DisplayName from the payee's email.

Invoice to QBO Invoice

QBO fieldWingspan source
TotalAmtInvoice total
DocNumberInvoice number
DueDateInvoice dueDate
LineInvoice line items, using SalesItemLineDetail with the mapped item
CustomerRefThe payer's mapped QBO customer
LinkedTxnThe QBO Payment, once the invoice is paid

Paid invoice to QBO Payment: TotalAmt, CustomerRef, ProcessPayment: true.

New payer to QBO Customer: PrimaryEmailAddr, CompanyName, DisplayName, and FullyQualifiedName from the payer's details.

Line items also accept accounting dimensions (class, glAccountCode, item, location, project, taxCode), which Wingspan stores and returns as you sent them.

Link or skip individual documents

Every invoice and payable has a quickbooks field. Use it when your own accounting system already created the QBO object, or when Wingspan shouldn't touch it:

{
  "quickbooks": {
    "billSync": { "id": "1841", "syncToken": "0" },
    "syncControl": { "shouldCreate": false, "shouldUpdate": true, "shouldDelete": false }
  }
}
  • billSync (payables) or invoiceSync (invoices) links the document to an existing QBO object. Send the QBO id and its current syncToken together.
  • syncControl flags set to false stop Wingspan from creating, updating, or deleting the QBO object for that document.

Send it on create or on PATCH of the payable or invoice.

6. Check what synced

Every write is recorded as a sync activity. List sync activities and filter by entityType, entityId, action, or isError:

curl -G https://api.wingspan.app/v3/platform/accounting-connections/Qc4mTn7wKp2xRb8dLs5vHa/sync-activities \
  -H "Authorization: Bearer $WINGSPAN_TOKEN" \
  --data-urlencode "filter[isError][eq]=true"
// (trimmed)
{
  "data": [
    {
      "id": "En5tRk2wQp8xLm3nVb6dHs",
      "entityType": "Payable",
      "entityId": "Pb8nVx3kQm6tLw2rHd9sYc",
      "action": "Create",
      "partnerEntityType": "Bill",
      "isError": true,
      "message": "<reason reported by QuickBooks>"
    }
  ],
  "pagination": { "nextPageToken": "" }
}

Fix the cause (for example, map the vendor), then retry that one row with Resync a sync activity. The connection must be Connected, or you get 409 with integrations.ConnectionNotConnected. To hide a row you've dealt with, PATCH it with { "isHidden": true }.

Keep the connection healthy

  • If QuickBooks is unreachable, calls return 503 with detailCode: integrations.PartnerUnavailable. Retry later.
  • POST .../refresh-token forces an OAuth token refresh.
  • Refresh reference data after you add vendors, customers, items, or accounts in QBO.
  • If status becomes Disconnected, run connect and complete-connection again.

Webhooks

EventWhen
AccountingConnection.Created, .Connected, .Disconnected, .TokenRefreshedConnection lifecycle
AccountingVendor.Created, .Refreshed (and the same for AccountingCustomer, AccountingItem, AccountingClass)Reference data copied or refreshed

Not available yet

  • Starting a full sync on demand (POST .../sync) and reading sync-run history. Use sync activities instead.
  • Disconnecting or deleting a connection through the V3 API. Contact support.
  • Accounting partners other than QuickBooks Online.

Related pages


Did this page help you?