Twinfield – Gotchas

Service ID: twinfield

Twinfield (Wolters Kluwer) cloud accounting platform.

⚠️

29 gotchas across 16 resources

These are connector-specific behaviors and limitations to be aware of when integrating.

allbankAccountsAll

Only 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.

addbankFeedStatementsAdd

Only 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

allbillPaymentsAll

Twinfield 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.

addbillPaymentsAdd

Disabled 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

allbillsAll

This 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.

addbillsAdd

Both 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.

onebillsOne

total_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

allcustomersAll

status 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

allexpensesAll

This 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.

addexpensesAdd

account_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.

allgeneralLedgerTransactionsAll

This 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

allinvoicesAll

This 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.

addinvoicesAdd

line_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.

oneinvoicesOne

line_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.

alljournalEntriesAll

This 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.

addjournalEntriesAdd

Twinfield 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.

allledgerAccountsAll

active 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.

addledgerAccountsAdd

Twinfield 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 03 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.

allpaymentMethodsAll

The 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.

addpaymentMethodsAdd

Payment 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.

onepaymentMethodsOne

The 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.

updatepaymentMethodsUpdate

Payment 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.

deletepaymentMethodsDelete

Payment methods cannot be deleted through this connector — no documented delete mechanism exists for this data.

Payments2 gotchas

allpaymentsAll

Twinfield 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.

addpaymentsAdd

Disabled 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

allprojectsAll

status 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

allsuppliersAll

status 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

alltaxRatesAll

Listing 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.

alltrackingCategoriesAll

status 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.