Twinfield – Gotchas
Twinfield (Wolters Kluwer) cloud accounting platform.
29 gotchas across 16 resources
These are connector-specific behaviors and limitations to be aware of when integrating.
Bank Accounts1 gotcha
bankAccountsAllOnly account code and name are available — account_type, currency, balance,
available_balance, bank_name, account_number, iban and status are not returned
by this connector.
name is read from a Dutch-language field key. Verified only against a Dutch-language
office; a non-Dutch office may return this field empty.
Bank Feed Statements1 gotcha
bankFeedStatementsAddOnly create is supported. update and delete are not implemented and are not possible —
once a bank statement is created in Twinfield, it cannot be changed.
There is no endpoint to list bank feed accounts, so bank_feed_account_id must be supplied
by the caller as the target account's IBAN, with no way to validate it beforehand.
The statement currency defaults to EUR when not set on the connection — the unified model
carries no per-statement or per-transaction currency field to source it from instead.
Statements for a non-EUR bank account must have the connection's currency setting configured
accordingly.
source_transaction_id on read is a synthesized composite value (not a Twinfield-native id),
since Twinfield's statement transaction lines carry no identifier of their own.
Reading a bank feed statement by id (bank-feed-statements.one) is currently non-functional —
every request fails with a generic server error regardless of request shape. Treat this
operation as unsupported until further notice.
Bill Payments2 gotchas
billPaymentsAllTwinfield returns every matching bill payment in a single call for this endpoint — there is no server-side pagination, so a large date range can be a large response.
account and allocations are not populated on the list. Fetch the individual bill
payment by id to get those fields.
billPaymentsAddDisabled as of 2026-08-18. Same underlying constraint as the payments resource: Twinfield's
bank transaction requires a reference to an existing, already-imported bank statement line,
which this connector has no way to discover (its bank-statement-import path is itself
disabled), and account.id needs a real ledger-account code rather than the id
/accounting/bank-accounts returns. See the payments resource's gotcha for details.
Bills3 gotchas
billsAllThis connector fetches every matching bill in a single call — there is no server-side
pagination, so a large date range can be a large response. The id returned matches what
bills.get expects, and other transaction types (bank, journal entries) are excluded.
sub_total, total_tax, line_items and updated_at are not available from this list
view — only one summary row per bill is returned, not the underlying line items. total
is the posted gross value (net + VAT once a bill carries tax). balance and paid_date,
notably, ARE available here even though they are always null on GET by id — this list
surfaces data the single-record read does not.
billsAddBoth supplier.id and bill_number are required — Twinfield rejects a bill without an invoice number, and every line must post against the accounts-payable control account, which requires knowing the supplier.
The accounts-payable control account itself is per-organisation configuration Twinfield exposes no way to look up. It defaults to 1600 (Twinfield's standard chart of accounts); if this office uses a different code, configure it on the connection.
reference is not sent — Twinfield's purchase transaction header has no free-text field
distinct from the invoice number (invoicenumber, already used for bill_number) to hold
it. Treat reference as write-unsupported for this connector.
line_items[].tax_rate.id is sent to Twinfield as a per-line vatcode; Twinfield computes
the VAT and posts it as its own line, so the balancing control-account line must equal net +
VAT, not just net. The connector computes that VAT total itself — using each line's
tax_amount when supplied, otherwise a live lookup of the tax_rate.id code's percentage —
and adds it to the control-account line. When that lookup cannot resolve a percentage (an
unknown code, or the lookup call itself failing), the connector defaults the line's
contribution to zero rather than failing the request outright; if Twinfield's own vatcode
computes a nonzero VAT for that line regardless, the resulting mismatch causes Twinfield to
reject the write with its own balance error rather than silently posting an unbalanced
amount.
billsOnetotal_tax, balance and paid_date are always null on this get-by-id operation —
Twinfield's response omits the balancing line where those values would come from.
sub_total is set equal to total rather than left null, since this connector does not
currently post VAT on bills (see the bill create gotcha), so there is no tax to subtract.
balance and paid_date are available on bills.list.
Customers1 gotcha
customersAllstatus is not returned by this list call — the underlying vendor list only returns code
and name per row. Fetch the customer by id to read it. The same applies to emails, phone_numbers, addresses,
websites, tax_number, bank_accounts, updated_at and account — all empty/null on
the list response, populated only when fetched by id.
Expenses2 gotchas
expensesAllThis connector fetches every matching expense in a single call — there is no server-side pagination, so a large date range can be a large response.
transaction_date is approximated as the first day of the expense's accounting month, not
the actual transaction date. sub_total, total_tax and updated_at are not available on
the list.
This list also returns records created through this connector's journal-entries resource
— there is no field to tell the two apart. Treat any entry whose lines look like a
multi-line journal posting as a possible journal-entries record.
expensesAddaccount_id (or account.id) is required for this connector even though the unified
schema only requires one of them via x-apideck-strict-any-of — Twinfield has no implicit
default for the credited side.
Not every ledger account works as account_id — Twinfield rejects some accounts with an
error naming a conflicting bank/cash book or control account rather than the field itself.
A plain, general-purpose ledger account works; a bank, cash-book-linked, or control
account does not. There is no way to predict which accounts qualify from the unified
request alone.
An expense posted through this connector is indistinguishable from a journal entry on the
Twinfield side; there is no payment_type/cash-vs-card distinction preserved.
The write always creates a draft transaction; this connector has no way to post it directly.
delete is disabled as of 2026-08-18. Transaction numbers are only unique within the
specific ledger they belong to, and this operation has no way to confirm the id it is given
actually belongs to an expense rather than a journal-entries record or a transaction
entered directly in Twinfield's own UI — deleting one by id risks voiding an unrelated
record that happens to share its number. get has the same collision on the read side —
it is non-destructive but can silently return a hand-entered journal entry reshaped as an
expense (dropping any line beyond the first debit/credit pair). Do not rely on an id
obtained from journal-entries or Twinfield's own UI being safe to pass to this resource,
or vice versa.
memo becomes the credited line's description, and Twinfield caps a transaction line's
description at 40 characters ("De omschrijving mag maximaal 40 tekens bevatten") — a
longer value is rejected outright rather than truncated. This connector does not truncate
memo or line_items[].description before sending; a value over 40 characters fails the
whole write.
update is not implemented, and not just for lack of time: Twinfield's transaction API has no
in-place update verb, only add (always assigns a new transaction number) and delete. Faking an
update as delete-then-recreate would return a different id than the one being updated, breaking
the update contract, so it is left unsupported rather than built as a lossy workaround.
General Ledger Transactions1 gotcha
generalLedgerTransactionsAllThis connector fetches every matching transaction in a single call — there is no
server-side pagination, so a large date range can be a large response. currency_rate and
reference are not available. Transactions created through this connector's expenses
resource are indistinguishable from manual journal entries and are returned as
source_type: journal_entry.
posted_at is approximated as the first day of the transaction's accounting period
(month), not the actual posting date, since the connector's data source for this endpoint
does not expose the real date.
Getting a specific transaction by id is not supported — Twinfield's transaction numbers
are not globally unique across transaction types, so there is no reliable way to look one
up directly.
Invoices3 gotchas
invoicesAllThis list only returns unpaid invoices — Twinfield does not expose a way to list paid or
historical invoices. status, line_items, invoice_date, due_date, currency,
sub_total, total_tax and balance are not available on the list; fetch the invoice by
id for those fields.
Because of this, webhook notifications only reliably cover new invoices — once an invoice is paid it drops out of this list entirely rather than appearing as an update, so a status change to paid will not reliably produce a webhook event.
Several fields are read from Dutch-language field keys. Verified only against a Dutch-language office; on a non-Dutch-language office this list may return empty.
meta.total_count may overstate the true number of invoices: the underlying list mixes
unpaid bills into the same count, and pagination needs the unfiltered total to reliably
fetch every page.
invoicesAddline_items[].ledger_account.id must reference a profit-and-loss (income or expense)
ledger account — Twinfield rejects an invoice line that posts to a balance-sheet account
(e.g. a bank or receivables account) with "Grootboekrekening bestaat niet (in de
PNL-groep)".
line_items[].description is not preserved on the created invoice line. Twinfield derives
the line's description from the ledger account it posts to, so the created invoice shows a
fixed label for that ledger account instead of the text you sent.
invoicesOneline_items[].total_amount is each line's net amount (excl. VAT), computed from
Twinfield's own unitspriceexcl × quantity. The VAT-inclusive gross total is total
(the invoice's header field, read from totals.valueinc) — Twinfield's response carries
no per-line gross figure to map instead.
Journal Entries2 gotchas
journalEntriesAllThis connector fetches every matching journal entry in a single call — there is no
server-side pagination, so a large date range can be a large response. An entry with
multiple lines is grouped back into one record with a nested line_items array.
journal-entries.list also returns records that were created through this connector's
expenses resource — there is no field to tell the two apart. Treat any entry whose lines
look like a two-line debit/credit posting as a possible expenses record.
memo, posted_at and updated_at are always null on the list. Fetch the entry by id
for those fields.
Twinfield transaction numbers are not globally unique — the same number can also identify
a bills/expenses record, or a transaction entered directly in Twinfield, and this
connector has no field to disambiguate. Getting a journal entry by an id sourced from
another resource (or from Twinfield directly) can silently return the wrong record.
journalEntriesAddTwinfield caps a line's description at 40 characters. A longer value is rejected outright
rather than truncated, and this connector does not pre-truncate
line_items[].description before sending.
Ledger Accounts2 gotchas
ledgerAccountsAllactive and status are not returned by this list call — fetch the ledger account by id
to read these fields.
This list includes both balance-sheet and profit & loss accounts, distinguished by type.
ledgerAccountsAddTwinfield requires the account's dimension type (BAS for type: "balancesheet", PNL for
type: "other") up front, and its own validation enforces the code format per type: balance
sheet codes must match [0-3][0-9][0-9][0-9]; anything else is rejected as Profit & Loss.
Send code in the matching range for the type you request — a type: "other" code inside
the 0–3 range is rejected by Twinfield's own format check before this connector's mapping
is even reached.
Fetching, updating and deleting an existing account by id derives its type from the same
leading-digit rule rather than a caller-supplied value, since Twinfield's read/update/delete
calls need the dimension type up front and the unified request has no type field to read it
from at that point. This is Twinfield's own enforced convention, not a per-tenant setting.
Payment Methods5 gotchas
paymentMethodsAllThe only source for payment types returns just code and name — there is no field for
the payment method's type (cash/check/bank_transfer/etc.) or its active/inactive status.
Both are always null.
name is read from a Dutch-language field key. Verified only against a Dutch-language
office; a non-Dutch office may return this field empty.
paymentMethodsAddPayment methods cannot be created through this connector — the vendor operation for maintaining payment types is not publicly documented in enough detail to implement reliably, unlike the read path this connector uses.
paymentMethodsOneThe only source for payment types returns just code and name — there is no field for
the payment method's type (cash/check/bank_transfer/etc.) or its active/inactive status.
Both are always null.
paymentMethodsUpdatePayment methods cannot be updated through this connector — the vendor operation for maintaining payment types is not publicly documented in enough detail to implement reliably, unlike the read path this connector uses.
paymentMethodsDeletePayment methods cannot be deleted through this connector — no documented delete mechanism exists for this data.
Payments2 gotchas
paymentsAllTwinfield returns every matching payment in a single call for this endpoint — there is no server-side pagination, so a large date range can be a large response.
account and allocations are not populated on the list. Fetch the individual payment by
id to get those fields.
paymentsAddDisabled as of 2026-08-18. Twinfield's bank transaction requires a reference to an existing,
already-imported bank statement line — a value the caller cannot supply directly, and this
connector has no way to discover one (its bank-statement-import path is itself disabled).
Separately, account.id must be a real ledger-account code (e.g. 1010); the id returned
by /accounting/bank-accounts is a different Twinfield concept and does not work here.
Projects1 gotcha
projectsAllstatus is not returned by this list call — the underlying vendor list only returns code
and name per row. Fetch the project by id to read it.
Suppliers1 gotcha
suppliersAllstatus is not returned by this list call — the underlying vendor list only returns code
and name per row. Fetch the supplier by id to read it. The same applies to emails, phone_numbers, addresses,
websites, tax_number, bank_accounts, updated_at and account — all empty/null on
the list response, populated only when fetched by id.
Tax Rates1 gotcha
taxRatesAllListing tax rates returns only the VAT code and its description; effective_tax_rate and
total_tax_rate are not populated on the list operation. Reading a single tax rate by its
known VAT code does return the rate.
name and description are read from a Dutch-language field key. Verified only against a
Dutch-language office; on a non-Dutch-language office these fields would come back empty.
Tracking Categories1 gotcha
trackingCategoriesAllstatus is not returned by this list call — the underlying vendor list only returns code
and name per row. Fetch the tracking category by id to read it.