# Odoo — Accounting API: gotchas

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

## GET /accounting/tax-rates

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/bills

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

Line-level tax is written from `line_items[].tax_rate.id` (an Odoo tax id) and read back on `line_items[].tax_rate`/`tax_amount`. `tax_rate` is present only on a line with exactly one tax; it is absent on both untaxed lines and lines with multiple taxes (which still report the aggregate `tax_amount`), so its absence does not by itself mean a line is untaxed.

## GET /accounting/invoices

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

Line-level tax is written from `line_items[].tax_rate.id` (an Odoo tax id) and read back on `line_items[].tax_rate`/`tax_amount`. `tax_rate` is present only on a line with exactly one tax; it is absent on both untaxed lines and lines with multiple taxes (which still report the aggregate `tax_amount`), so its absence does not by itself mean a line is untaxed.

## GET /accounting/ledger-accounts

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/invoice-items

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/credit-notes

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

Line-level tax is written from `line_items[].tax_rate.id` (an Odoo tax id) and read back on `line_items[].tax_rate`/`tax_amount`. `tax_rate` is present only on a line with exactly one tax; it is absent on both untaxed lines and lines with multiple taxes (which still report the aggregate `tax_amount`), so its absence does not by itself mean a line is untaxed.

## POST /accounting/bill-credit-notes

Bill credit notes are Odoo vendor credit notes (`account.move` with `move_type=in_refund`). A created credit note stays in `draft`: this connector cannot post it, so `status` always reads back `draft` and `number` is `null` (Odoo assigns the document number only on posting).
`currency` cannot be set on create — the credit note takes the company's default currency. `note` is stored in Odoo's rich-text field, so it reads back wrapped in `<p>…</p>`.
`total_amount` is derived from the line items — a create must send `line_items` that sum to the intended amount; a `total_amount` sent without matching lines is ignored (a create with no lines stores a zero-value credit note). Line-level tax is written from `line_items[].tax_rate.id` (an Odoo tax id) and read back as `line_items[].tax_rate` and `tax_amount`. `tax_rate` is present only on a line with exactly one tax; it is absent on both untaxed lines and lines with multiple taxes (which still report the aggregate `tax_amount`), so the absence of `tax_rate` does not by itself mean a line is untaxed. Line-level discount is not written.
Updating `line_items` replaces the whole collection: a PATCH carrying `line_items` deletes the existing lines and recreates the sent set (line `id` is ignored, omitted lines are removed), so send the full set of lines on every update that touches them.

## GET /accounting/customers

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/suppliers

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/payments

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/companies

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/journal-entries

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/purchase-orders

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## PATCH /accounting/purchase-orders/{id}

Delete operation cancels the purchase order (sets status to 'cancel') instead of hard-deleting it. Odoo does not allow hard deletion of purchase orders — they must be cancelled first. This follows standard accounting practice where financial documents are voided rather than deleted.
Requires the Purchase module (purchase) to be installed on the Odoo instance.

## GET /accounting/goods-receipts

A receipt is generated per purchase order when the order is confirmed, and a split
delivery produces one receipt per delivery, so a single purchase order line can appear on
several receipts, each with its own `quantity_received`. Sum `quantity_received` across
receipts sharing the same `line_items[].purchase_order.line_id` to get the total received
against a purchase order line. A purchase order that contains only service products
produces no receipt.

When a tenant merges deliveries from several purchase orders into one receipt,
`purchase_order_id` and every line's `purchase_order.id` name the order the receipt was
generated from, while each line's `purchase_order.line_id` is exact. Use `line_id` for
line-level matching.

A receipt in `draft` status has not been validated yet: `quantity_received` reflects the
quantity recorded so far and `received_date` falls back to the scheduled date. Receipt lines
are read from a field introduced in Odoo 17; on Odoo 16 and earlier `line_items` is returned
empty. `line_items` is also returned empty, with a `meta.warnings` entry describing the
failure, if the line-item lookup itself fails. `quantity_ordered`, `unit_of_measure`,
`currency`, `posted_date` and `total_amount` are not available.

`filter[updated_since]`, `filter[supplier_id]` and `filter[purchase_order_id]` are
supported. `filter[purchase_order_id]` matches the order the receipt was generated from
(the header `purchase_order_id`); a receipt that also carries lines merged in from a second
order is not returned for that second order. Match on `line_items[].purchase_order.line_id`
for line-level lookups.

## GET /accounting/goods-receipts/{id}

Only receipts created from a purchase order are addressable. Requesting the id of any other
inbound or outbound stock movement returns a 404.

A receipt that has not been validated yet (`status` `draft`) reports the scheduled date as
`received_date`, because the actual date is only recorded once the receipt is validated.
Check `status` before treating `received_date` as an actual arrival date.

## GET /accounting/subsidiaries

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/locations

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/departments

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

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

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/bank-accounts

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## POST /accounting/journals

`type` is required — Odoo's underlying account.journal model has no default and rejects a create with it omitted. Use one of `sales`, `purchase`, `cash`, `bank` or `general`.
`currency` cannot be set on create — it is returned when reading a journal but is not accepted as an input field. New journals use the company's default currency.

## PATCH /accounting/journals/{id}

`currency` cannot be changed on update — it is returned when reading a journal but is not accepted as an input field.

## GET /accounting/fiscal-periods

Odoo has no fiscal-year list. Years are generated from the company's fiscal year-end setting: the current fiscal year and the 9 before it, never one ending before the company's accounting opening date (or creation date), listed newest first. 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 `closed` when the year ends on or before the fiscal-year lock date or the hard lock date (Odoo 18+), which stop every user; `locked` when it ends on or before the period lock date (Odoo 17 and earlier, stops everyone except Advisers) or a sales, purchase or tax lock date, which block only some users or transaction types; otherwise `open`. A lock date inside a year leaves it `open`. `locked_through` is the latest of those lock dates capped at the year's end, when it is on or after the year's start, otherwise null. Odoo 18 per-user lock exceptions and parent-company locks are not considered, and a lock date field the Odoo version does not have is ignored. Send `x-apideck-company-id` (or set a default company) for a multi-company database, otherwise the request returns 422. Odoo Enterprise fiscal-year records are not used. An invalid API key returns 401 and a user without access to the company returns 403.

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

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

## GET /accounting/trial-balance

Built from Odoo's posted journal items (`account.move.line`, posted entries only), summed per account for the company selected by `x-apideck-company-id` or the connection's company; a database with several companies and no selection returns a `422`. Balance sheet accounts are cumulative to `end_date`; income and expense accounts (account types income, income_other, expense, expense_depreciation, expense_direct_cost; Odoo 16 and later) run from the start of the fiscal year that contains `end_date`, from the company's fiscal year-end setting. Odoo does not close income and expense into equity entry by entry, so the result of earlier fiscal years is returned as one line named "Result of previous fiscal years (not yet appropriated)" with `line_type: unappropriated_result` and null `account_id` and `code`; it is summed from its own read, not computed as the difference, so a report whose debits and credits differ by half a cent or more returns a `502`. Off-balance accounts are excluded and accounts with a zero balance are left out. `account_id` is the account's Odoo id, the id `GET /accounting/ledger-accounts/{id}` takes; a balance whose account cannot be read has `line_type: unmapped` and null `account_id`, `code` and `name`. `code` and `name` come from the company's chart of accounts, archived accounts included. The fiscal year start comes from the company's fiscal year-end setting: Enterprise fiscal-year records with other dates are not read, and postings of branch companies (Odoo 17+) are not included, only those of the selected company. An invalid API key returns `401` and a user without access to the company `403`. `currency` is the company currency. `filter[accounting_method]=cash` returns a `400`: Odoo's cash basis is a tax-reporting setting, not a ledger mode. Each request makes four Odoo calls.
## GET /accounting/tracking-categories

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/bill-payments

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/expenses

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/bank-feed-accounts

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/bank-feed-statements

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.

## GET /accounting/categories

Odoo does not include a total record count in list responses. `meta.total_count` will not appear. Surfacing the true total requires a separate `search_count` call to the Odoo XML-RPC API — an extra API call that is not currently implemented.


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