Dualentry – Gotchas
DualEntry is an AI-native ERP platform built from the ground up for finance teams scaling from mid-market to IPO.
21 gotchas across 13 resources
These are connector-specific behaviors and limitations to be aware of when integrating.
Bill Payments1 gotcha
billPaymentsAllDualEntry scopes bill payments by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
Bills2 gotchas
billsAllupdated_since is inclusive and is applied at whole-second granularity, so a poll that
reuses the newest updated_at it received will re-receive the records from that final
second. De-duplicate on id, or advance the cursor past the boundary second.
Two DualEntry behaviours combine to cause this. Its updated_after filter is a
>= comparison, and while DualEntry stores update times with sub-second precision it
returns them truncated to the second — so a returned timestamp is always slightly earlier
than the value the filter compares against. Any sub-second component supplied in
updated_since is therefore rounded down before the request is sent, which guarantees no
record is ever skipped at the boundary, at the cost of re-delivering that second.
billsAllDualEntry scopes bills by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
Credit Notes2 gotchas
creditNotesAllupdated_since is inclusive and is applied at whole-second granularity, so a poll that
reuses the newest updated_at it received will re-receive the records from that final
second. De-duplicate on id, or advance the cursor past the boundary second.
Two DualEntry behaviours combine to cause this. Its updated_after filter is a
>= comparison, and while DualEntry stores update times with sub-second precision it
returns them truncated to the second — so a returned timestamp is always slightly earlier
than the value the filter compares against. Any sub-second component supplied in
updated_since is therefore rounded down before the request is sent, which guarantees no
record is ever skipped at the boundary, at the cost of re-delivering that second.
filter[ids] and filter[number] address the same field on this connector: a
DualEntry credit note's unified id and number are the same value. If both are
supplied, filter[number] takes precedence and filter[ids] is ignored — so a
number outside the ids list still returns that credit note. Send one or the
other, not both.
filter[ids] returns at most 100 records per page. A list of more than 100 ids is not
truncated — the filter is preserved across pages, so page through the cursor to collect
them all.
creditNotesAllDualEntry scopes credit notes by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
Customers1 gotcha
customersAllDualEntry customers are org-level master data and are not scoped to any company. The x-apideck-company-id header has no effect on customer list or lookup results — all customers belong to the organisation, not to individual subsidiary companies.
filter[ids] returns at most 100 records per page. A list of more than 100 ids is not
truncated — the filter is preserved across pages, so page through the cursor to collect
them all.
Expenses3 gotchas
expensesAllDualEntry scopes expenses by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
expensesAddDualEntry requires company_id, supplier (vendor_id), account (account_number), and currency (currency_iso_4217_code) when creating a direct expense, even though these fields are optional in the unified schema.
expensesUpdateDualEntry requires company_id, supplier (vendor_id), account (account_number), and currency (currency_iso_4217_code) when updating a direct expense, even though these fields are optional in the unified schema.
Invoice Items2 gotchas
invoiceItemsAllInvoice items (the product catalog) are tenant-wide in DualEntry and cannot be scoped by company.
The x-apideck-company-id header is accepted but has no effect — all items across the tenant are
returned regardless of the company ID.
invoiceItemsAddCreating an item requires code (used as the DualEntry SKU) and an income account (income_account.id).
Omitting code returns 400 ... missing sku.
Invoices3 gotchas
invoicesAllupdated_since is inclusive and is applied at whole-second granularity, so a poll that
reuses the newest updated_at it received will re-receive the records from that final
second. De-duplicate on id, or advance the cursor past the boundary second.
Two DualEntry behaviours combine to cause this. Its updated_after filter is a
>= comparison, and while DualEntry stores update times with sub-second precision it
returns them truncated to the second — so a returned timestamp is always slightly earlier
than the value the filter compares against. Any sub-second component supplied in
updated_since is therefore rounded down before the request is sent, which guarantees no
record is ever skipped at the boundary, at the cost of re-delivering that second.
filter[ids] and filter[number] address the same field on this connector: a
DualEntry invoice's unified id, display_id and number are all the same value.
If both are supplied, filter[number] takes precedence and filter[ids] is ignored
— so a number outside the ids list still returns that invoice. Send one or the
other, not both.
filter[ids] returns at most 100 records per page. A list of more than 100 ids is not
truncated — the filter is preserved across pages, so page through the cursor to collect
them all.
invoicesAllDualEntry scopes invoices by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
invoicesAddPosted invoices (status: posted) require due_date, an invoice-level memo (customer_memo, or reference
as a fallback), and a description on every line item. Omitting any of these returns
400 ... Required for posted records. A company must also be resolved — supplied by the connection's
Default Company setting, the x-apideck-company-id header, or company_id in the body (any one suffices).
Draft invoices (status: draft) have none of these requirements.
Journal Entries1 gotcha
journalEntriesAllDualEntry scopes journal entries by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
Ledger Accounts1 gotcha
ledgerAccountsAllLedger accounts (the chart of accounts) are tenant-wide in DualEntry and cannot be scoped by company.
The x-apideck-company-id header is accepted but has no effect — all accounts across the tenant are
returned regardless of the company ID.
Payments2 gotchas
paymentsAllDualEntry scopes payments by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
paymentsAddApply a payment to an invoice via allocations[]: each allocations[].id is the target invoice's id
(its DualEntry invoice number) and allocations[].amount the amount applied. This is what drives the
invoice's derived status (partially_paid / paid). A memo (note) is required. Use
status: authorised to post the payment — posted is not an accepted value (allowed: draft,
authorised, rejected, paid, voided, deleted). Payments created with status: draft succeed
but do not affect the invoice balance or status; only posted (authorised) payments move the balance.
Purchase Orders1 gotcha
purchaseOrdersAllDualEntry scopes purchase orders by company. Pass the x-apideck-company-id header (or set a Default
Company on the connection) to return only records belonging to that company within the tenant.
Without it, records across all companies in the tenant are returned.
Suppliers1 gotcha
suppliersAllDualEntry suppliers are org-level master data and are not scoped to any company. The x-apideck-company-id header has no effect on supplier list or lookup results — all suppliers belong to the organisation, not to individual subsidiary companies.
Tax Rates1 gotcha
taxRatesAllDualEntry returns the Product Tax Code catalog (e.g. TAXABLE - System, NON_TAXABLE - System, plus Avalara codes) from /public/v2/product-tax-codes. These are categorical tax codes, not rate-based — effective_tax_rate and total_tax_rate are always 0. Use the returned id and code to reference the tax on invoice-items (sales_details.tax_rate.id).
Read-only catalog: add, update, delete are not exposed in this release. Global system-managed codes are list-only and return 403 on GET /tax-rates/{id}; org-scoped codes (when present) support get-by-id.
The product tax code catalog is tenant-wide and cannot be scoped by company. The x-apideck-company-id header is accepted but has no effect on the returned catalog.