# QuickBooks — Accounting API: gotchas

> Canonical URL: https://developers.apideck.com/apis/accounting/quickbooks/gotchas

## GET /accounting/tax-rates

In quickbooks, the tax rates resource does not support pagination. QuickBooks returns tax codes rather than complete tax rate data. `effective_tax_rate` is populated (as the sum of the code's component rates) but `total_tax_rate` is **not** returned — key on `effective_tax_rate`, not `total_tax_rate`, when reading a single percentage. When `effective_tax_rate` is unavailable, use `code` to reference the tax in invoice line items.

## GET /accounting/tax-rates

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/tax-rates

To create a tax rate, `effective_tax_rate`, `tax_payable_account_id`, `name` and a `component` is required. If `tax_payable_account_id` is unknown, make a proxy call to get list of TaxAgency. [TaxAgency doc](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/taxagency#query-a-taxagency)

## GET /accounting/bills

On bill line items, QuickBooks returns `ClassRef.name` as the `FullyQualifiedName`
(e.g. `"Fruit:Apple"`) rather than the leaf class name. Apideck normalises this by
extracting the last `:` segment, so `tracking_categories[].name` returns `"Apple"` and
`parent_name` returns `"Fruit"`. Expense line items are unaffected — QuickBooks returns
only the short name there.

## GET /accounting/bills

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/bills

When creating a bill in QuickBooks, each line item must be accompanied by a type designation. If the type is set to "expense_item," an item ID is required. Conversely, if the type is "expense_account," a ledger account ID must be provided.

`reference` and `notes` both map to the single `PrivateNote` field — QuickBooks' one reference-number slot (`DocNumber`) is taken by `bill_number` on bills, so `reference` shares the memo instead. Only one of the two survives a write (the other is dropped with no error), and on read both are populated from `PrivateNote` and are always identical. Send only one. Which of the two wins on write is currently under review — do not rely on it.

`bill_number` maps to `DocNumber`, limited to 21 characters; a longer value is rejected with `400 ValidationFault` (code `6000`), so a 36-character UUID does not fit.

Additionally, multicurrency needs to be enabled in your QBO account. If creating a bill in your default currency, you do not need to send the currency value as long as the supplier uses the same currency. For bills with suppliers using a different currency than your default, you must include the currency value, matching the supplier's configured currency. To change the currency of a customer or vendor, a new one must be created with the desired currency. Foreign currencies can only be sent to suppliers configured with that currency.

## GET /accounting/bills/{id}

On bill line items, QuickBooks returns `ClassRef.name` as the `FullyQualifiedName`
(e.g. `"Fruit:Apple"`) rather than the leaf class name. Apideck normalises this by
extracting the last `:` segment, so `tracking_categories[].name` returns `"Apple"` and
`parent_name` returns `"Fruit"`. Expense line items are unaffected — QuickBooks returns
only the short name there.

## POST /accounting/bills/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 bills per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/invoices

QuickBooks uses tax codes rather than tax rates. The `tax_rate` on line items references a tax code, which may not include the actual percentage value. To get the actual rate, retrieve the full tax rate details via the tax-rates endpoint.

## GET /accounting/invoices

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/invoices

`quotes` lists the estimates linked to the invoice with their `id` only; `quotes[].number`
is not returned on the list. Read it from `GET /accounting/invoices/{id}` or
`GET /accounting/quotes/{id}`.

## POST /accounting/invoices

Quickbooks offers limited (regional) support for applying discount on a Line Item. Including discount_percentage or discount_amount applies to entire invoice. The default type for a Line Item is a sales item if no type has been provided.
Send an `Idempotency-Key` to make retrying a create safe: it reaches QuickBooks as its `requestid`. QuickBooks keeps a `requestid` for at least 24 hours and can keep it longer, so after the 24-hour window a key reused for a different invoice may get the original invoice back, with the new body ignored. Use a new key for every invoice. Your key can be 1-255 characters: Unify sends QuickBooks a 32-character `requestid` derived from it, so QuickBooks' 50-character `requestid` limit never applies.

## POST /accounting/invoices

`quotes` links the invoice to existing QuickBooks estimates: send each quote's `id`
(`number` is read-only and is not sent). Only Pending or Accepted estimates are linked. An
estimate that is closed, rejected or already linked to an invoice is ignored without an
error: the request succeeds and that quote is missing from the invoice's `quotes` when it is
read back. A quote `id` that does not exist fails the request with a `400`. An estimate can
be linked to only one invoice. Linking does not copy the estimate's lines onto the invoice;
send `line_items` yourself. Linking changes the estimate's status: it reads back from
`GET /accounting/quotes/{id}` as `void`.

## GET /accounting/invoices/{id}

`quotes` lists the estimates linked to the invoice.
`quotes[].number` is read from the linked estimates with one extra request. When that
request fails, or the invoice links more quotes than fit in one request, `quotes[].number`
is omitted and `meta.warnings` has an entry with `operation: quotesAll`.

## PATCH /accounting/invoices/{id}

Quickbooks offers limited (regional) support for applying discount on a Line Item. Including discount_percentage or discount_amount applies to entire invoice. The default type for a Line Item is a sales item if no type has been provided. When setting status to `void`, do not include any other fields in the request body. Send an `Idempotency-Key` to make retrying an update (including a void) safe: it reaches QuickBooks as its `requestid`. QuickBooks keeps a `requestid` for at least 24 hours and can keep it longer, so after the 24-hour window a key reused for a different update may get the original update's result back, with the new body ignored. Use a new key for every update. Your key can be 1-255 characters: Unify sends QuickBooks a 32-character `requestid` derived from it, so QuickBooks' 50-character `requestid` limit never applies.

## PATCH /accounting/invoices/{id}

When setting tax rates on line items, QuickBooks requires valid tax codes. The `tax_rate.id` must reference an existing tax code from the tax-rates endpoint. QuickBooks automatically calculates tax amounts based on the tax code's rate.

## PATCH /accounting/invoices/{id}

`quotes` only adds quote links. Each quote `id` you send is linked to the invoice, and
quotes that are already linked stay linked. Sending `quotes: []`, `null` or a shorter list
removes no quote link: quote links cannot be removed by updating the invoice. The invoice's
other linked transactions (payments, time activities and billable charges) are kept. The
linking rules of invoice create apply: only Pending or Accepted estimates are linked, others
are ignored without an error, and a quote `id` that does not exist fails with a `400`.

## POST /accounting/invoices/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 invoices per request. A request carrying more is rejected before any
invoice is created.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have created some or all of its invoices. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list invoices to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/ledger-accounts

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.
Fixed assets: `fixed_asset.cost` and `fixed_asset.accumulated_depreciation` are set on top-level accounts of type Fixed Asset. QuickBooks keeps a tracked asset as such an account with sub-accounts for its cost and its accumulated depreciation. `fixed_asset.cost` is the account's own balance plus its sub-accounts other than accumulated depreciation, amortization or depletion. `fixed_asset.accumulated_depreciation` is the total of those contra sub-accounts as a positive amount, or `null` when the account has none. Sub-accounts and other accounts have no `fixed_asset`: their balances roll up into the top-level account, whose own `current_balance` excludes them and is often `0`. The net book value is `fixed_asset.cost` minus `fixed_asset.accumulated_depreciation`. One extra QuickBooks request per page reads the fixed-asset sub-accounts, and only when the page holds a top-level fixed-asset account; with 1,000 or more fixed-asset sub-accounts the figures are not set. QuickBooks has no acquisition date, depreciation method or useful life, so the other `fixed_asset` sub-fields are not returned.

## GET /accounting/ledger-accounts/{id}

`fixed_asset.cost` and `fixed_asset.accumulated_depreciation` are set on a top-level account
of type Fixed Asset, from the account and its sub-accounts, as described on the list. Other
accounts have no `fixed_asset`. QuickBooks has no acquisition date, depreciation method or
useful life.

## DELETE /accounting/ledger-accounts/{id}

This connector does not support deleting ledger accounts. Instead, it will update the ledger account to inactive.

## POST /accounting/ledger-accounts/batch

Records are written one at a time, so the request counts as **one request per
record** against your plan — batching saves you round trips, not quota.

Records are written one at a time rather than in a single batched call, so that a
QuickBooks plan usage-limit error is reported on the account it applies to. A request
of N records therefore takes about N times as long as a single write: a full request
of 10 takes about 16 seconds.

At most 10 records per request.

Each record succeeds or fails on its own; read the per-item `status` to see which
were written. If the request times out, the items it could not confirm come back
with `status: uncertain` rather than `failed` — some of them may well have been
written, and no ids are returned for those. Verify before retrying an `uncertain`
item: list the resource to find what exists, or a retry will duplicate it.

Results come back in the order the items were sent. Supply `_ref` on every item if
you need to tie an outcome back to your own record — it is echoed on every result,
including failures, and is never sent to the connector.

## GET /accounting/invoice-items

QuickBooks does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/invoice-items

The `taxable` field is applicable to US companies only and should be sent in the request body if your QuickBooks account is in the US region.

## PATCH /accounting/invoice-items/{id}

The `taxable` field is applicable to US companies only and should be sent in the request body if your QuickBooks account is in the US region.

## GET /accounting/credit-notes

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

`filter[updated_since]` and `filter[created_since]` are exclusive (`>`) and `Metadata.LastUpdatedTime` / `Metadata.CreateTime` have second granularity, so a record whose timestamp equals the cursor to the exact second is skipped. For incremental sync, set the cursor one second behind the highest `updated_at` seen in the previous run and de-duplicate by `id`.

## POST /accounting/credit-notes/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 credit notes per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## POST /accounting/bill-credit-notes

QuickBooks vendor credits have no draft/posted lifecycle: `status` is derived from the unapplied balance and is only ever `posted` (fully unapplied), `partially_paid`, or `paid` (fully applied), and `balance`/`remaining_credit` both report the unapplied amount.
Set tax with `line_items[].tax_rate.code`; the top-level `tax_code` is not honoured on automated-tax companies (e.g. UK), where QuickBooks computes tax from the line rates.
QuickBooks derives `total_amount` from the line items, so the value read back can differ from the one sent (a tax-exclusive line adds tax on top of the supplied amount).

## POST /accounting/bill-credit-notes/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 bill credit notes per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/customers

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/customers

`balance` reflects only the customer's own outstanding balance — it excludes the balances
of any sub-customers or jobs, which are returned as separate customer records linked
through `parent.id`. The value is denominated in the customer's own `currency`, which may
differ from the company's home currency. It can be negative when the customer holds
unapplied credits or overpayments.

## POST /accounting/customers

QuickBooks supports only billing and shipping addresses. Therefore, when creating addresses, it is required to specify the type as 'billing' or 'shipping' in the address object within the addresses array.
## PATCH /accounting/customers/{id}

QuickBooks supports only billing and shipping addresses. Therefore, when creating or updating addresses, it is required to specify the type as 'billing' or 'shipping' in the address object within the addresses array.
## POST /accounting/customers/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 customers per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost,
so set client timeouts for the size of request you send.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/suppliers

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/suppliers

QuickBooks only supports a single billing address. Therefore, the first item in the addresses array will always be mapped as the billing address, regardless of its original type.
## PATCH /accounting/suppliers/{id}

QuickBooks only supports a single billing address. Therefore, the first item in the addresses array will always be mapped as the billing address, regardless of its original type.
## POST /accounting/suppliers/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 suppliers per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost,
so set client timeouts for the size of request you send.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/payments

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/payments

Payment allocations in QuickBooks are processed asynchronously. While the allocations are included in the request, they may not appear in the immediate response. The allocations will be applied and become visible in subsequent list or get calls.

## PATCH /accounting/payments/{id}

To clear the payment method on an existing payment, send `payment_method_id: null` explicitly
in the PATCH body. Omitting the field preserves the existing payment method (sparse update
semantics). Sending `null` triggers `PaymentMethodRef: { value: null }` in the QuickBooks
sparse POST, which removes the payment method reference from the payment.

## POST /accounting/payments/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 payments per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/refunds

tax_inclusive only applies to UK, AU, IN, and CA editions. US companies ignore this field silently.

## POST /accounting/refunds

tax_inclusive only applies to UK, AU, IN, and CA editions. US companies ignore this field silently. payment_method_id is required to set the payment method. Provide the ID of a payment method record configured in QuickBooks. total_amount is required on each line item. If not provided, it is computed from quantity * unit_price. tracking_categories behavior depends on company settings. "One to entire transaction" uses top-level tracking_categories. "One to each row" requires tracking_categories on each line item.

## GET /accounting/refunds/{id}

tax_inclusive only applies to UK, AU, IN, and CA editions. US companies will not have this field in responses. payment_method returns the name of the payment method record, not the payment type category.

## PATCH /accounting/refunds/{id}

row_version is required for updates. tracking_categories behavior depends on company settings. "One to each row" requires tracking_categories on each line item.

## GET /accounting/balance-sheet

When using start and end date filters, QuickBooks requires both to be sent. However, the report only reflects the balance sheet situation as of the end date.
## GET /accounting/journal-entries

QuickBooks only returns journal entries that were explicitly created as `JournalEntry` records.
System-generated GL postings from invoices, bills, payments, deposits, transfers, and other
transaction types post to the general ledger but are not exposed by QuickBooks as journal entries
and will not appear in this response. To reconstruct full GL activity, fetch the underlying
transaction resources (invoices, bills, payments, etc.) in addition to journal entries.

Look up a specific journal entry by its number with `filter[number]` (for example
`?filter[number]=JE-0001`). The number is limited to 21 characters.

## GET /accounting/journal-entries

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/journal-entries

Quickbooks only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
Line-level tax fields (tax_rate, tax_amount, tax_type) on journal entries are only supported for non-US QuickBooks companies (UK, CA, AU, etc.). US-based QuickBooks companies silently discard all tax fields — the request succeeds but no tax data is persisted.
For US companies that need to reflect tax in journal entries, add a separate line item posting the tax amount to a tax liability ledger account (e.g., "Sales Tax Payable"). This keeps the journal entry balanced in the general ledger, but will not populate QuickBooks Sales Tax reports — for that, use invoices or sales receipts instead.
When line_items[].tax_type is not set, QuickBooks requires knowing whether the tax applies to a sales or purchase transaction. Unify infers this from line_items[].type: credit lines are treated as sales (TaxApplicableOn: Sales) and debit lines as purchases (TaxApplicableOn: Purchase). This covers the common case of sales summary journal entries. For atypical entries (e.g. a credit to an AP account, or a debit for a sales return), set tax_type explicitly on each line to override the default.
When a line item has `tax_rate` but no `tax_amount`, Unify calculates `tax_amount` as the line's `total_amount` multiplied by the tax rate's percentage, rounded to the cent. For tax rates made up of several components (for example Québec GST + QST), send the combined tax for the line in `tax_amount`, or omit it; Unify splits the line's tax across the components.
Creating a journal entry with a `number` that already exists is accepted and produces a second, separate entry — the number is not enforced as unique. Use a unique `number`, and if a create's result never arrives (a timeout or a 5xx), look the entry up with `filter[number]` before retrying so you do not create a duplicate.
Send an `Idempotency-Key` to make retrying a create safe: it reaches QuickBooks as its `requestid`. QuickBooks keeps a `requestid` for at least 24 hours and can keep it longer, so after the 24-hour window a key reused for a different journal entry may get the original journal entry back, with the new body ignored. Use a new key for every journal entry. Your key can be 1-255 characters: Unify sends QuickBooks a 32-character `requestid` derived from it, so QuickBooks' 50-character `requestid` limit never applies.

## PATCH /accounting/journal-entries/{id}

Quickbooks only supports one tracking category at a time so only the first item in the tracking_categories list will be used.
When line_items[].tax_type is not set, QuickBooks requires knowing whether the tax applies to a sales or purchase transaction. Unify infers this from line_items[].type: credit lines are treated as sales (TaxApplicableOn: Sales) and debit lines as purchases (TaxApplicableOn: Purchase). This covers the common case of sales summary journal entries. For atypical entries (e.g. a credit to an AP account, or a debit for a sales return), set tax_type explicitly on each line to override the default.

## POST /accounting/journal-entries/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 journal entries per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost,
so set client timeouts for the size of request you send.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## POST /accounting/sales-receipts

tax_inclusive only applies to UK, AU, IN, and CA editions. US companies ignore this field
silently.

payment_method_id is required to set the payment method. Provide the ID of a payment
method record configured in QuickBooks.

total_amount is required on each line item. If not provided, it is computed from
quantity * unit_price.

tracking_categories behavior depends on company settings. "One to entire transaction"
uses top-level tracking_categories. "One to each row" requires tracking_categories on
each line item.

The deposit destination is controlled via account. Omit account to deposit to
Undeposited Funds (QuickBooks' default).

Tax handling depends on the company's country edition. US companies with Automated Sales
Tax calculate tax automatically by location: a submitted tax_code or total_tax overrides
the automatic calculation, so omit both to accept it. Line item tax_rate values must be
the pseudo-codes TAX or NON for US companies (other values are rejected with a validation
error).

Non-US editions require real QuickBooks tax code IDs for tax_rate instead of the US
pseudo-codes, and total_tax is recalculated from the line tax codes and cannot be
overridden.

With multicurrency enabled, currency must match the customer's currency, and
currency_rate is required when it differs from the company's home currency.

## PATCH /accounting/sales-receipts/{id}

row_version submitted in the request body is ignored. The connector always fetches the
latest version immediately before writing, so there is no optimistic-concurrency
protection — concurrent updates are last-write-wins.

tax_inclusive only applies to UK, AU, IN, and CA editions. US companies ignore this field
silently.

payment_method_id is required to set the payment method. Provide the ID of a payment
method record configured in QuickBooks.

total_amount is required on each line item. If not provided, it is computed from
quantity * unit_price.

tracking_categories behavior depends on company settings. "One to each row" requires
tracking_categories on each line item.

The deposit destination is controlled via account. Omit account to deposit to
Undeposited Funds (QuickBooks' default).

Tax handling depends on the company's country edition. US companies with Automated Sales
Tax calculate tax automatically by location: a submitted tax_code or total_tax overrides
the automatic calculation, so omit both to accept it. Line item tax_rate values must be
the pseudo-codes TAX or NON for US companies (other values are rejected with a validation
error).

Non-US editions require real QuickBooks tax code IDs for tax_rate instead of the US
pseudo-codes, and total_tax is recalculated from the line tax codes and cannot be
overridden.

With multicurrency enabled, currency must match the customer's currency, and
currency_rate is required when it differs from the company's home currency.

## GET /accounting/purchase-orders

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/locations

QuickBooks Online does not expose a reliable cross-page total record count. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/departments

QuickBooks Online does not expose a reliable cross-page total record count. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/attachments/{reference_type}/{reference_id}

QuickBooks Online does not expose a reliable cross-page total record count. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/attachments/{reference_type}/{reference_id}

Quickbooks limits attachment uploads to 100MB.
## GET /accounting/fiscal-periods

QuickBooks has no fiscal-year list. Years are generated from the company's fiscal-year start month: the current fiscal year and the 9 before it, listed newest first, never a year that ends before the company start date. If the company ever changed its fiscal year-end, earlier years are reported on the current year-end, not the one in force at the time. Every record has `type: year`; `id` is the year's start date (`YYYY-MM-DD`) and `name` and `fiscal_year` are the year in which it ends. `status` is `locked` when the year ends on or before the books closing date, otherwise `open`: QuickBooks only asks for a warning confirmation or the closing-date password, so users who have the password can still post and `closed` is never returned. Preferences does not say which of the two settings is on, so a closing date set to warning-only is also reported `locked`. A closing date inside a year leaves it `open`. `locked_through` is the closing date capped at the year's end, when it is on or after the year's start, otherwise null. Each request reads CompanyInfo and Preferences and fails if either cannot be read.

## GET /accounting/fiscal-periods/{id}

An id outside the generated 10-year window answers 404.

## GET /accounting/trial-balance

Built from QuickBooks' `TrialBalance` report. QuickBooks reports income and expense accounts for the report period only, so Apideck sets the period start to the first day of the fiscal year that contains `end_date` (from the company's fiscal year start month, one extra CompanyInfo call); balance sheet accounts are cumulative to `end_date`. `end_date` defaults to today (UTC). Accounts with a zero balance are left out, so a company with no data returns an empty list. `account_id` is the QuickBooks account id, the id ledger accounts use; a balance the report shows without an account id is returned with `line_type` `unmapped` and no `account_id`. Account numbers are not in the report, so `code` is not returned. Amounts are in the company's home currency. A report whose debits and credits do not balance returns a `502`.
## GET /accounting/tracking-categories

QuickBooks Online does not expose a reliable cross-page total record count. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/tracking-categories/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 tracking categories per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost,
so set client timeouts for the size of request you send.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/bill-payments

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/bill-payments

Payment method is required and must be `Check` or `CreditCard`.

## POST /accounting/bill-payments/batch

The whole request is one call to the provider, so it counts as **one request**
against your plan however many records it carries.

At most 20 bill payments per request. A request carrying more is rejected before any
record is written.

Response time scales with the number of records rather than being a fixed cost:
a full request of 20 takes roughly 27 seconds. Set client timeouts accordingly.

A request that times out may still have written some or all of its records. When
that happens no ids are returned for them, so treat a timed-out batch as uncertain
rather than failed: list the records to find what was written before retrying, or a
retry will duplicate them.

## GET /accounting/expenses

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## POST /accounting/expenses

The top-level `account` must be a bank or credit-card account — the payment source the money left from — not an expense account; the expense account goes on `line_items[].account`. Sending an expense account at the top level fails with `400 "Invalid account type used"` (code `6430`), an error that names neither the field nor the type it expected.

`reference` and `memo` both map to the single `PrivateNote` field: only one survives a write (the other is dropped with no error), and on read both are populated from it and are always identical. Send only one. Which of the two wins on write is currently under review — do not rely on it.

## GET /accounting/categories

QuickBooks Online does not expose a reliable cross-page total record count. `meta.total_count` will not appear. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/quotes

QuickBooks Online does not expose a reliable cross-page total record count. The `QueryResponse.totalCount` field in list responses reflects the count for that page only, not the full dataset. `meta.total_count` will not appear in list responses. Surfacing the true total requires a separate `SELECT COUNT(*) FROM {Entity}` query — an extra API call that is not currently implemented.

## GET /accounting/projects

The `priority` field is mapped from QuickBooks integer values (0-9) to unified enum values using ranges: 0-2 → "low", 3-5 → "medium", 6-7 → "high", 8-9 → "critical".

## POST /accounting/projects

The `priority` field accepts enum values ("low", "medium", "high", "critical") which are converted to QuickBooks integer values: "low" → 2, "medium" → 5, "high" → 7, "critical" → 9.

## GET /accounting/projects/{id}

The `priority` field is mapped from QuickBooks integer values (0-9) to unified enum values using ranges: 0-2 → "low", 3-5 → "medium", 6-7 → "high", 8-9 → "critical".

## PATCH /accounting/projects/{id}

The `priority` field accepts enum values ("low", "medium", "high", "critical") which are converted to QuickBooks integer values: "low" → 2, "medium" → 5, "high" → 7, "critical" → 9.


[API reference](https://developers.apideck.com/apis/accounting/reference.md) · [Coverage matrix](https://developers.apideck.com/apis/accounting/coverage)
