# Exact Online — Accounting API: gotchas

> Canonical URL: https://developers.apideck.com/apis/accounting/exact-online/gotchas

## GET /accounting/tax-rates

`status` is derived from Exact's `IsBlocked` flag (`false` -> `active`, `true` -> `inactive`). Live-verified against a real division: the raw VATCode object has no `Active`-equivalent field of its own, only `IsBlocked`. Exact has no third state, so `archived` is never returned.

## GET /accounting/bills

Same as `exact-online-nl`.

## POST /accounting/bills

`line_items[].ledger_account` is not supported on create — Exact's purchase-invoice line
object rejects an unknown `GLAccount` property outright (400, "The property name
'GLAccount' ... is not valid"), so the connector does not send it: a `ledger_account` on
a create request is silently dropped rather than rejected, and the created bill line will
not be coded to that ledger account. Code the expense via `line_items[].item` instead, or
use `journal-entries` for a GL-coded posting.

## GET /accounting/invoices

Same as `exact-online-nl`.

## POST /accounting/invoices

Exact Online assigns invoice status automatically (`open` by default) and ignores the `status` field on create — invoices are finalized/processed in Exact Online, not via the API.
Set VAT via `line_items[].tax_rate.code`; Exact derives the percentage from the code, so `line_items[].tax_rate.rate` is ignored on create.
`shipping_address` creates a CRM address record against the customer, but **does not
attach it to the invoice**: the invoice is saved with whatever delivery address the
customer already had, so only the first address ever sent for a given customer takes
effect. A later, different address is stored on the customer and then ignored by the
invoice. Set the delivery address on the customer record when it needs to change.

On read, `shipping_address.string` returns that address record's identifier rather than a
formatted address, and the individual lines are not returned.

## GET /accounting/invoices/{id}

Same as `exact-online-nl`.

## GET /accounting/bill-credit-notes

Exact Online does not track settlement on a purchase entry, so `balance`, `remaining_credit`, `date_paid` and `allocations` are never returned. `tax_inclusive`, `account`, `tax_code`, `subsidiary`, `location`, `department`, `tracking_categories`, `custom_fields` and `row_version` are likewise not populated.
`status` reflects Exact's entry status: an open entry reads as `authorised`, a processed one as `paid`, and a draft as `draft`. Any other status Exact may return falls back to `authorised`.

## POST /accounting/bill-credit-notes

Requires a purchase journal code on the connection setting `purchase_journal_code`; without it Exact rejects the create. Vault presents that setting under the Bills resource, and the same value applies here. Line item `total_amount` must be a positive amount of at least one cent — Exact derives the document type from the amount sign and rounds to two decimals, so a zero, negative or sub-cent line would record a bill instead of a credit note and is rejected.
Exact refuses a duplicate: a credit note whose supplier, `reference`, `date_issued` and total all match an existing one is rejected with `Already exists`, reported as a `500` rather than a conflict. Retrying a create that already succeeded therefore fails instead of producing a second record — vary `reference` when a repeat is genuinely intended.
`line_items[].total_amount` is always booked as the amount excluding tax and `tax_inclusive` is ignored, so tax from the line's `tax_rate.code` is added on top: a line of `100` with a 21% code produces a credit note whose `total_amount` is `121`. This also applies to a tax code Exact presents as tax-inclusive, so sending a gross amount on such a code overstates the credit note by the tax.

## POST /accounting/suppliers

Exact does not enforce uniqueness on the supplier name — posting the same `display_name`/
`company_name` twice creates two separate suppliers, both with a `201`. This connector
passes the name through as sent rather than matching on write; the caller owns
de-duplication (for example, checking `suppliers.list` for an existing name, or storing
the returned `id` and reusing it) before creating a supplier that may already exist.

## GET /accounting/payments

Payments are read from Exact Online's cash-flow area, so the Exact Online app registration
needs read access to it (the `financial.cashflow` scope, set in the app's Data & Security
Review). Without it, every call returns a 403 `AppScopeViolated` error while the connection
still shows as Connected and other resources keep working. Invoice `balance` is derived from
the same area and comes back `null` in that case.

## POST /accounting/payments

Creating a payment records the bank transaction but does not automatically reconcile it
with the linked invoice — the invoice status will not change to paid until the payment is
matched manually in Exact Online. A valid bank journal (Type=12) must be supplied via the
`bank_journal_code` connection setting or `pass_through[journal_code]` per request;
without one the API returns 'Invalid journal'.

## GET /accounting/journal-entries

Limit parameter for pagination is not supported for this resource.

The list returns the same `line_items` as the single-record read, so no fetch by id is
needed for line-level data.

Exact's journal entry header has no date field, so `posted_at` is derived from the lines:
it is the earliest `line_items[].date`. Only an entry with no dated line falls back to the
first day of its financial period (`FinancialYear`/`FinancialPeriod`), which is month
precision. `created_at` is when the entry was created in Exact, not its accounting date.

`title` is the journal's own description. `memo` is the first non-empty
`line_items[].description`, or null when no line has one. `updated_by`/`created_by` are
not populated: Exact's header records when it was created and modified, but not by whom.

Exact does not distinguish a customer account from a supplier account at the line level,
so each line's account is returned under both `line_items[].supplier` and
`line_items[].customer`. To tell which it is, match the id against the suppliers or
customers list; an account that is both appears in both.

## POST /accounting/journal-entries

A validation refusal — an unbalanced entry, an unknown or inactive reference (ledger
account, journal, VAT code, currency), or a missing required field — returns `422` with
the specific reason named. Any other `500` is a genuine unexpected downstream failure,
not a refusal the request itself caused.

VAT on a line is selected by `tax_rate.code`, not `tax_rate.rate` — the rate is not sent
and has no effect. Omitting the code does not mean "no VAT": Exact substitutes the ledger
account's own default VAT code, which changes the line's tax amount and can then reject
the whole entry as unbalanced with an error naming the balance mismatch rather than VAT.
Always send `tax_rate.code` explicitly rather than relying on a specific ledger account's
default.

`line_items[].date` maps to Exact's per-line `Date` (a real field, distinct from the
header, which has no date field at all — see the list-operation gotcha above). Historical
or backdated bookings should set this per line rather than relying on `posted_at`.
`line_items[].source_id` maps to Exact's per-line `Notes`, useful for carrying your own
reconciliation identifier since `description` is typically a human-readable label.

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

`reference_id` is the invoice, bill, or journal entry's own unified id — not Exact's
internal Document envelope GUID. The connector resolves between the two internally
(Exact's `FinancialTransactionEntryID`, live-verified to equal the unified id for all
three reference types), so no manual lookup or pass-through is needed.

`file_url` is Exact's own document viewer page
(`https://start.exactonline.{tld}/docs/SysAttachment.aspx?...`), not a direct file
download link — it requires an active, logged-in Exact browser session and redirects to
a login page for an unauthenticated request (live-verified: a plain GET returns a 302 to
the Exact login page, not the file). There is no other download-capable URL on this
connector's attachment records.

This list has no server-side total count, so pagination metadata never includes a count.

## GET /accounting/journals

Use code as journal_symbol when creating a journal entry. The id is Exact's journal GUID. allow_vat applies to general journals (type general). Select the division with x-apideck-company-id, or the connection's default division. This list has no server-side total count, so pagination metadata never includes a count.

## POST /accounting/journals

`type: bank` requires `iban` — Exact needs a bank account reference to create a bank journal. `type: payment_service` requires a payment service provider reference that has no equivalent in this unified field set — creating a journal with that type will be rejected by Exact. Use `pass_through` to supply the required vendor field if you need `payment_service`, or use `sales`, `sales_credit_note`, `purchase`, `purchase_credit_note`, `cash` or `general` instead.
`type: other` has no Exact equivalent and will be rejected — `other` is a read-side fallback for values Exact might introduce in the future, not a valid input.
`clearing_account` is the unallocated account a bank journal parks incoming movements in. Bank feed statements posted to a journal without one never reach the reconciliation queue. Exact assigns a default on create; set it explicitly to pin a specific account. Only meaningful on `type: bank` and `type: payment_service`.

## GET /accounting/fiscal-periods

Periods of the selected division, in fiscal year and period order. `name` is generated as `{fiscal_year}-P{period_number}` (for example `2026-P01`) because Exact has no period name. `fiscal_year` is the calendar year in which the period's fiscal year ends, taken from the latest period end of that fiscal year (one extra read of the division's periods; null if it cannot be read); Exact's own financial year label, which can be the start year, is the first part of `name`. `status`, `locked_through` and `is_adjustment` are not exposed by Exact and are always null.

## GET /accounting/trial-balance

Built from `financial/ReportingBalance`, which is kept per fiscal period. `filter[end_date]` is resolved to the fiscal period that contains it, and the report is as of the end of that period (`end_date` in the response); `start_date` is the first day of that fiscal year. Income and expense accounts cover that fiscal year up to and including that period. For the current and previous fiscal year, balance sheet accounts are Exact's opening balance for that year (after the result appropriation, the same source as the balance sheet) plus that year's movements up to and including the period; for older years they are summed over every earlier fiscal year, which reads the ledger's full history and can take many requests or fail with a `502` on a long-lived administration. Accounts with a zero balance are left out. A result of earlier years that has not been appropriated to equity is returned as one extra line, "Result of previous fiscal years (not yet appropriated)", with `line_type` `unappropriated_result` and null `account_id` and `code`. On the opening-balance path that line is the opening balance's own net, negated, never derived from the requested year's movements, so a report whose debits and credits do not balance returns a `502` instead of being absorbed into it; an empty opening balance is not trusted and the earlier years are read instead. `account_id` is the G/L account's id, the same id the ledger accounts take; a balance without an account would be `unmapped`. Exact is accrual-only: `filter[accounting_method]=cash` returns a `400`. Without `filter[end_date]` the current period is used, today in UTC (the latest period if today is past the last one); a date outside every fiscal period returns a `400`, and a division without any financial period returns a `422`. `currency` is null.
## GET /accounting/bill-payments

Bill payments are read from Exact Online's cash-flow area, so the Exact Online app registration
needs read access to it (the `financial.cashflow` scope, set in the app's Data & Security
Review). Without it, every call returns a 403 `AppScopeViolated` error while the connection
still shows as Connected and other resources keep working. Invoice `balance` is derived from
the same area and comes back `null` in that case.

## POST /accounting/bill-payments

Creating a bill payment records the bank transaction but does not automatically reconcile
it with the linked bill — the bill's status will not change to paid until the payment is
matched manually in Exact Online. A valid bank journal (Type=12) must be supplied via the
`bank_journal_code` connection setting or `pass_through[journal_code]` per request;
without one the API returns 'Invalid journal'.

## POST /accounting/bank-feed-accounts

Same as `exact-online-nl`.

## GET /accounting/bank-feed-statements

Same as `exact-online-nl`.

## POST /accounting/bank-feed-statements

Same as `exact-online-nl`.


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