Last year, a developer on our team spent three days debugging why invoice totals in QuickBooks Online didn't match what our connector was sending. The amounts were correct. The tax rates were correct. The line items were correct. The problem? The chart of accounts mapping defaulted to an expense account instead of a revenue account. Every invoice landed on the wrong side of the books.
This is the kind of bug that accounting software integrations produce. Not a 500 error. Not a timeout. A silent, structurally valid write to the wrong place, discovered six weeks later during a month-end close.
If you're building software that connects to accounting systems, you need a working understanding of how accounting actually works. Not because you're going to become a CPA. Because the data model of every accounting API is shaped by accounting rules, and if you don't know those rules, you will build things that look right and are wrong.
This guide covers the accounting concepts that matter for integration developers. Not all of accounting. The parts that affect how you read, write, and sync financial data through APIs.
What accounting concepts do developers need for API integrations?
The table below maps core accounting concepts to their practical API integration implications. Each concept directly affects how you read, write, or sync financial data through accounting APIs like QuickBooks, Xero, and NetSuite.
| Concept | What It Is | API Impact | Standard Reference |
|---|---|---|---|
| Double-entry bookkeeping | Every transaction requires balanced debit and credit entries summing to zero | Journal entry and invoice APIs reject unbalanced payloads | GAAP, IFRS (IAS 1) |
| Chart of accounts | Numbered taxonomy of all accounts organized by classification (assets, liabilities, equity, revenue, expenses) | Every line item must reference a valid account ID; mappings differ per platform | GAAP, IFRS (IAS 1) |
| Accounts receivable | Money owed to a company by its customers for goods or services delivered | Invoice creation APIs; aging reports; payment application | GAAP (ASC 310), IFRS (IFRS 9) |
| Accounts payable | Money a company owes to its suppliers for goods or services received | Bill creation APIs; vendor management; payment processing | GAAP (ASC 405), IFRS (IFRS 9) |
| Revenue recognition | Rules governing when earned revenue can be recorded in the books | Affects invoice timing, deferred revenue, and journal entry automation | ASC 606, IFRS 15 |
| Tax handling | Tax rates, jurisdictions, inclusive/exclusive pricing, and liability posting | Tax rate IDs on line items; tax-inclusive vs tax-exclusive flag per invoice | Jurisdiction-specific (VAT, GST, sales tax) |
| Multi-currency | Transactions in foreign currencies require exchange rates and gain/loss tracking | Currency code and rate fields required on every foreign-currency transaction | GAAP (ASC 830), IFRS (IAS 21) |
| Reconciliation | Comparing two sets of records (your platform vs accounting system) to verify agreement | Requires external IDs, timestamps, and idempotent sync logic on every write | Operational practice (no single governing standard) |
Double-entry is a constraint, not a concept
Double-entry bookkeeping is the foundational accounting rule that every financial transaction must have at least two entries, a debit and a credit, that balance to zero. Every major accounting API (QuickBooks, Xero, NetSuite, Sage, FreshBooks) enforces this constraint at the API level. Unbalanced payloads are rejected. This rule is codified under both GAAP and IFRS (IAS 1).
Every accounting system you'll integrate with enforces double-entry bookkeeping. This is the single most important thing to internalize because it shapes every API call you'll make.
The rule is simple: every financial transaction must have at least two entries that balance to zero. Money comes from somewhere and goes somewhere. If you debit one account for $500, you must credit another account (or combination of accounts) for $500. No exceptions. The system won't accept an unbalanced entry.
For developers used to working with CRMs or HRIS systems, this is a different world. In a CRM, you can create a contact with whatever fields you want and the system accepts it. In an accounting system, creating a transaction means satisfying a mathematical constraint. If your payload doesn't balance, the API rejects it.
This has practical consequences. When you're syncing invoices from your platform into a customer's accounting system, you're not just pushing a document. You're creating a set of balanced entries: a debit to accounts receivable (the customer owes money) and a credit to a revenue account (income was earned). If your integration doesn't specify the right accounts for both sides, the system either rejects the request or, worse, picks defaults that put the data somewhere unhelpful.
Modern Treasury published a solid primer on double-entry fundamentals in their "Accounting for Developers" series, covering the mechanics of debits and credits in detail. That's worth reading if you want the theoretical foundation. What follows here focuses on how those mechanics translate into integration code.
The chart of accounts: your most important mapping problem
A chart of accounts (COA) is the numbered list of every account in a company's books, organized into five classifications: assets, liabilities, equity, revenue, and expenses. It functions as the schema of the accounting database. Every API transaction references accounts from this list, and writing to the wrong account classification means the customer's financial statements are wrong.
The chart of accounts (COA) is a numbered list of every account in a company's books. It's the taxonomy that organizes all financial data, the schema of the accounting database. Every transaction references accounts from this list.
Accounts fall into five classifications:
Assets (what the company owns: cash, receivables, inventory), Liabilities (what the company owes: loans, payables, credit cards), Equity (ownership value: retained earnings, owner investment), Revenue (income earned from operations), and Expenses (costs incurred to operate).
The first three appear on the balance sheet. The last two appear on the income statement. This distinction matters for integrations because writing data to the wrong classification means the customer's financial statements are wrong.
Here's where it gets complicated for developers. Every company's chart of accounts is different. A construction company and a SaaS company both use QuickBooks, but their account structures look nothing alike. Your integration can't hardcode account IDs. You need to either let users map accounts during setup, or use the accounting platform's API to look up accounts by type and classification at runtime.
QuickBooks Online, for example, exposes account objects with a Classification field and an AccountType field that's more specific. A revenue account in QBO looks something like this:
{
"Id": "1",
"Name": "Sales of Product Income",
"Classification": "Revenue",
"AccountType": "Income",
"AccountSubType": "SalesOfProductIncome",
"CurrentBalance": 0,
"Active": true
}
The same account in Xero comes back with a different structure:
{
"AccountID": "ab72e8e0-7b21-4b92-a248-0cf4ee38c6f7",
"Name": "Sales",
"Type": "REVENUE",
"Class": "REVENUE",
"Status": "ACTIVE",
"TaxType": "OUTPUT"
}
Notice the differences. QBO gives you AccountType and AccountSubType as separate fields. Xero attaches a TaxType directly to the account. A unified API like Apideck normalizes these into a single schema:
{
"id": "1",
"nominal_code": "4000",
"name": "Sales",
"type": "income",
"classification": "revenue",
"status": "active",
"currency": "USD"
}
If you're building a multi-platform integration without a normalization layer, you're maintaining separate mapping logic for every platform. This is one reason unified accounting APIs exist.
One more thing about the COA: it's not static. Customers add accounts, rename them, deactivate them. Your integration needs to handle stale account references gracefully. A mapping that worked during onboarding might break three months later because the customer's bookkeeper reorganized their chart of accounts.
Invoices, bills, and the AR/AP lifecycle
An invoice is a request for payment sent by a seller, creating an accounts receivable entry. A bill is a request for payment received by a buyer, creating an accounts payable entry. In most accounting APIs, both use the same object with a type flag (accounts_receivable or accounts_payable). Revenue recognition rules under ASC 606 and IFRS 15 govern when invoice revenue can be recorded.
Most accounting integrations start with invoices. If your platform generates charges, subscriptions, orders, or fees, the most common integration request is "sync this into my accounting system."
Two terms to keep straight:
Invoices vs bills
An invoice is a request for payment sent by a seller. It creates an accounts receivable entry: the customer owes you money. A bill is a request for payment received by a buyer. It creates an accounts payable entry: you owe someone money. In many accounting APIs, invoices and bills are the same object with a type flag. Merge's unified API, for instance, uses an Invoice object with a type field set to either accounts_receivable or accounts_payable.
Each invoice or bill contains line items. A line item connects the transaction to a specific account in the chart of accounts (usually a revenue or expense account) and includes quantity, unit price, tax, and description. The line items must reconcile to the invoice total, and the journal entry the system generates behind the scenes must balance.
Here's what creating an invoice looks like through the Apideck Accounting API:
POST /accounting/invoices
{
"type": "accounts_receivable",
"number": "INV-0042",
"customer": {
"id": "cust_8a3b1c"
},
"invoice_date": "2026-04-01",
"due_date": "2026-05-01",
"line_items": [
{
"description": "Monthly platform subscription",
"quantity": 1,
"unit_price": 299.00,
"ledger_account": {
"id": "acc_revenue_4000",
"nominal_code": "4000"
},
"tax_rate": {
"id": "tax_standard_10"
}
}
],
"currency": "USD"
}
That ledger_account on the line item is where most integration bugs live. If it points to the wrong account, the invoice gets created successfully but the revenue shows up in the wrong place in the customer's financial statements. The API won't stop you because the payload is structurally valid.
The lifecycle matters for your integration. An invoice moves through states: draft, submitted, authorized, paid, voided. Not every accounting platform uses the same state names, but the pattern is consistent. Your integration needs to decide: at what state do I create the invoice in the accounting system? Do I update it when the status changes? What happens if the invoice is voided after it's been synced?
Syncing payments
Payment objects connect to invoices. When a customer pays an invoice, a payment record is created that references the invoice and records which bank or cash account received the funds. The accounting system then marks the invoice as paid and creates the corresponding journal entry (debit cash, credit accounts receivable).
A payment sync looks like this:
POST /accounting/payments
{
"total_amount": 328.90,
"transaction_date": "2026-04-15",
"customer": { "id": "cust_8a3b1c" },
"account": { "id": "acc_checking_1000" },
"invoices": [
{
"id": "inv_0042",
"amount": 328.90
}
],
"reference": "pmt_stripe_pi_3abc123"
}
The account field specifies which bank account the money landed in. The invoices array links this payment to one or more open invoices. That reference field (a Stripe payment intent ID in this case) is what makes reconciliation possible later. If your platform processes payments, syncing both the invoice and the payment is what gives the customer a complete picture. Syncing invoices without payments leaves open receivables that the customer's bookkeeper has to close manually.
Journal entries: the escape hatch
A journal entry is the most primitive write operation in accounting: a set of lines, each referencing an account, each marked as debit or credit, summing to zero. Journal entries are the fallback for any transaction that doesn't map to higher-level objects like invoices or payments. Use them for accruals, reclassifications, and adjustments.
When the transaction you need to record doesn't fit neatly into invoices, bills, or payments, journal entries are the fallback. A journal entry is the most primitive write operation in accounting: a set of lines, each referencing an account, each marked as debit or credit, summing to zero.
Here's a journal entry that records a $500 year-end accrual (recognizing revenue that was earned but not yet invoiced):
POST /accounting/journal-entries
{
"title": "Revenue accrual - December 2025",
"journal_date": "2025-12-31",
"line_items": [
{
"description": "Accrued consulting revenue",
"type": "debit",
"amount": 500.00,
"ledger_account": { "id": "acc_1200" }
},
{
"description": "Accrued consulting revenue",
"type": "credit",
"amount": 500.00,
"ledger_account": { "id": "acc_4000" }
}
]
}
The debit to account 1200 (accrued receivables, an asset) and the credit to account 4000 (revenue) must sum to zero. If you remove one line or change an amount so they don't balance, the API rejects the entry.
Journal entries are useful for adjustments (correcting a prior entry), accruals (recording revenue or expense before cash moves), reclassifications (moving an amount from one account to another), and any complex transaction your platform needs to represent that doesn't map cleanly to the accounting system's higher-level objects.
The tradeoff is that journal entries are powerful but opaque. An invoice carries semantic meaning: who the customer is, what was sold, payment terms, due date. A journal entry is just numbers and account codes. If your integration relies heavily on journal entries instead of using the appropriate transaction objects, customers will struggle to understand what the entries represent when they review their books.
Use the highest-level object that fits your use case. Fall back to journal entries when you have to.
Authentication: OAuth is the gatekeeper
Before your integration can read or write anything, it needs to authenticate with the customer's accounting system. Nearly every major accounting platform uses OAuth 2.0 for third-party access, and the implementation details vary enough to cause problems.
The OAuth flow for accounting APIs
The general pattern: your application redirects the user to the accounting platform's authorization page, the user grants permission, the platform returns an authorization code, and your application exchanges that code for an access token and a refresh token. The access token is what you attach to every API call. The refresh token is what you use to get a new access token when the current one expires.
QBO access tokens expire after one hour. Xero tokens expire after 30 minutes. Sage Intacct uses session-based auth with a different pattern entirely. If your integration doesn't handle token refresh correctly, it will work during testing and break in production the first time a token expires mid-sync.
Scopes and permissions
Most platforms let users control what your integration can access. Xero moved to granular OAuth scopes in 2024, meaning you now request specific permissions like accounting.transactions.read or accounting.contacts.write instead of getting access to everything. QBO uses similar scoping. Your onboarding flow needs to request only the scopes you need, and your integration needs to handle the case where a user grants partial access.
A unified API like Apideck handles the OAuth flow and token refresh for you across all connected platforms. But understanding what happens underneath matters for debugging.
Webhooks and sync strategies
Once authenticated, you need to decide how your integration keeps data in sync between your platform and the accounting system.
Push vs poll
There are two approaches. Polling means your integration periodically calls the accounting API to check for changes (new invoices, updated payments, modified accounts). Webhook-based sync means the accounting platform notifies your integration when something changes.
QBO supports webhooks that notify you of entity changes. Xero has webhooks for key events. But not every platform supports webhooks, and those that do often have limitations on what events they cover. Many integrations use a hybrid: webhooks for real-time awareness, with periodic polling as a fallback to catch anything the webhooks missed.
Idempotency and deduplication
Whichever approach you use, your sync logic must be idempotent. Webhooks can fire more than once for the same event. Polling can encounter the same record on consecutive runs. If your integration creates a duplicate invoice because it processed the same event twice, the customer now has a reconciliation problem. Store the external ID of every synced object and check for it before creating new records.
Reconciliation: where integrations actually fail
Reconciliation is the process of comparing two sets of financial records to verify they agree. In API integrations, this means comparing what your platform says happened with what the accounting system shows. Timing differences, rounding errors, partial payments, and voided transactions are the primary causes of drift between systems.
Reconciliation is the process of comparing two sets of records to ensure they agree. In practice, it means comparing what your platform says happened with what the accounting system shows.
This is the hard problem. Not conceptually hard. Operationally hard.
Here's why. Your platform and the accounting system will drift. Timing differences are the most common cause: your system records a transaction on March 31 but the accounting system receives it on April 1, which might put it in a different reporting period. Rounding differences in tax calculations produce one-cent discrepancies that accumulate. Partial payments create mismatches between expected and actual amounts. Voided or refunded transactions that aren't synced back create orphaned entries.
Bank reconciliation adds another layer. The customer's bank statement shows actual cash movement. The accounting system shows recorded transactions. Matching these two is something accountants do monthly (or daily, for larger companies). If your integration introduces data that doesn't match bank records, you've created a reconciliation problem for the customer.
The practical takeaway: your integration needs to be idempotent, handle retries without creating duplicates, timestamp everything, and provide a way for customers (or their accountants) to trace any entry in the accounting system back to the source event in your platform. Unique external IDs on every synced object are not optional.
Multi-entity and multi-currency
In 2026, SaaS platforms building accounting integrations increasingly serve customers who operate across multiple legal entities and currencies. A property management company with LLCs in three states. A marketplace with sellers across a dozen countries. A travel tech platform processing bookings in local currencies across dozens of markets.
Multi-entity accounting means the same parent company maintains separate books for each subsidiary or legal entity, plus a consolidated view. Sage Intacct is built for this natively. QuickBooks Online handles it through separate company files or through the Advanced tier's multi-entity features. Xero uses a multi-org structure. Each has different API implications for how you scope your integration.
The key question for your integration: does your data need to land in one entity or be split across several? If a single transaction in your platform spans multiple entities (a transfer between subsidiaries, an intercompany charge), you may need to create entries in multiple company books and matching elimination entries for consolidation.
Multi-currency adds its own complexity. Accounting systems require a base currency (also called functional currency or home currency). Foreign currency transactions must be recorded at the exchange rate on the transaction date, and the system tracks unrealized gains and losses as exchange rates fluctuate. Your integration needs to pass currency codes and exchange rates, not just amounts. If you send an amount without specifying the currency, most systems will assume the base currency, and if that's wrong, the books are wrong. The rules for foreign currency translation are defined under GAAP (ASC 830) and IFRS (IAS 21).
Compare these two invoice payloads. The first is what most developers send initially:
{
"line_items": [
{ "unit_price": 250.00, "quantity": 1 }
]
}
The system assumes the company's base currency (say, USD). If the actual transaction was in EUR, the books are now wrong by whatever the exchange rate difference is. The correct version includes currency and rate:
{
"currency": "EUR",
"currency_rate": 1.08,
"line_items": [
{ "unit_price": 250.00, "quantity": 1 }
]
}
That currency_rate of 1.08 means 1 EUR = 1.08 USD. The accounting system uses this to compute the base currency equivalent ($270.00) and records both amounts. When the exchange rate changes before the invoice is paid, the system records the difference as a foreign exchange gain or loss automatically.
Tax: the field that breaks everything
Tax handling varies so dramatically across accounting platforms, jurisdictions, and business types that it deserves its own warning.
At minimum, your integration needs to handle: tax rates that differ by product type and customer location, tax-exempt customers, tax-inclusive versus tax-exclusive pricing, and the accounts to which tax amounts are posted. A line item total of $100 with 10% tax-exclusive means $110 hits the accounting system ($100 to revenue, $10 to a tax liability account). The same $100 with tax-inclusive pricing means $90.91 to revenue and $9.09 to tax. If your integration doesn't distinguish between these two models, the revenue figures and tax liabilities will both be wrong.
Tax-inclusive vs tax-exclusive
In API terms, the difference looks like this. Tax-exclusive (common in the US):
{
"line_items": [
{
"unit_price": 100.00,
"tax_rate": { "id": "tax_10_percent" },
"tax_amount": 10.00,
"total_amount": 110.00
}
],
"total": 110.00
}
Tax-inclusive (common in the EU, UK, Australia):
{
"line_items": [
{
"unit_price": 100.00,
"tax_rate": { "id": "vat_20_percent" },
"tax_amount": 16.67,
"total_amount": 100.00
}
],
"total": 100.00
}
Notice the tax-inclusive line item has a total_amount equal to the unit_price because the tax is already inside that number. The accounting system needs to unbundle it: $83.33 to revenue, $16.67 to a VAT liability account. If your integration sends tax-inclusive amounts as if they were tax-exclusive, the customer's revenue is overstated and their tax liability is understated. Accountants catch this. Usually not quickly.
QuickBooks Online has its own automated sales tax engine for US transactions. Xero handles tax through tax rates attached to line items with region-specific behavior. International platforms must contend with VAT reverse charges, withholding tax, and GST/HST rules that change which party records the tax liability. If your platform operates across both cash-based and accrual accounting regimes, the tax timing gets even more complex.
The safest pattern is to pass tax amounts explicitly as computed by your platform, rather than relying on the accounting system's tax engine to recalculate. This avoids discrepancies between what your platform charged the customer and what the accounting system records. But some accounting platforms prefer to own tax calculation. Understand your target platforms and offer configuration options.
Building with accounting APIs in 2026
If you're building accounting integrations in 2026, here's the compressed version of what to get right. (For a deeper operational checklist, see our best practices for accounting integrations in vertical SaaS.)
Map to the chart of accounts dynamically. Never hardcode account IDs. Use the highest-level transaction object available (invoices over journal entries) for readability. Sync payments alongside invoices to close open receivables. Assign unique external IDs to every object so you can reconcile and deduplicate. Handle tax explicitly, including the distinction between tax-inclusive and tax-exclusive amounts. Support multi-currency by always passing currency codes and exchange rates. Build for multi-entity customers even if most of your current users are single-entity.
QuickBooks still commands over 60% of the US small business accounting market, but your integration will encounter dozens of other platforms. Each has its own data model, its own API idioms, its own rules about required fields. A unified accounting API (like Apideck's, which normalizes across these platforms through a single integration point) reduces the surface area of this problem, but understanding the accounting concepts underneath is what prevents the subtle, silent bugs that erode trust. If you're weighing whether to build or buy your accounting integrations, the concepts in this guide are exactly what you'd need to maintain yourself.
The best accounting integrations are the ones the customer's accountant never has to think about. That only happens when the developer who built them understood what the accountant expects to see.
If you want to see where AI is reshaping the accounting stack on top of these same integration patterns, that's the next layer of the story.
Frequently asked questions
What is the difference between an invoice and a bill in accounting APIs?
An invoice and a bill represent the same transaction from two different perspectives. An invoice is created by the seller and records accounts receivable (money owed to you). A bill is created by the buyer and records accounts payable (money you owe someone). In most accounting APIs, both use the same object with a type field set to accounts_receivable or accounts_payable.
Do all accounting APIs enforce double-entry bookkeeping?
Yes. Every major accounting platform (QuickBooks, Xero, Sage, NetSuite, FreshBooks) enforces double-entry at the API level. If you submit a journal entry where debits and credits don't balance to zero, the API will reject it. Higher-level objects like invoices handle the balancing for you by generating the underlying journal entries automatically.
How do I handle multiple accounting platforms in one integration?
You can either build and maintain separate integrations for each platform, or use a unified accounting API that normalizes data across platforms into a single schema. Building individually gives you full control but requires maintaining separate authentication, data mapping, and sync logic for every platform. A unified API trades some flexibility for speed and lower maintenance cost.
What accounting standards affect API integration design?
Two frameworks govern how financial data is structured: GAAP (used in the US) and IFRS (used in most other countries). For API integrations, the most relevant standards are ASC 606 / IFRS 15 (revenue recognition, which affects invoice timing), ASC 830 / IAS 21 (foreign currency translation, which requires exchange rates on every multi-currency transaction), and ASC 310 / IFRS 9 (accounts receivable measurement, which affects how aging and impairment are calculated).
What is a chart of accounts and why does it matter for API integrations?
A chart of accounts (COA) is the numbered list of every account in a company's books, organized into five classifications: assets, liabilities, equity, revenue, and expenses. It matters for integrations because every line item in an invoice, bill, or journal entry must reference a valid account from the COA. Mapping to the wrong account is the single most common integration bug: the API accepts the payload, but the data lands in the wrong place in the customer's financial statements.
Ready to get started?
Scale your integration strategy and deliver the integrations your customers need in record time.








