# Sage Intacct — Accounting API: gotchas

> Canonical URL: https://developers.apideck.com/apis/accounting/sage-intacct/gotchas

## GET /accounting/tax-rates

Sage Intacct supports multi-component taxes through the `components` array. Each component can have its own rate and tax authority. Use compound taxes for jurisdictions with multiple tax layers (e.g., GST + PST in Canada).

## POST /accounting/bills

`line_items[].item` must reference the item by its Sage item code (`ITEMID`), not by the
numeric record number. Pass the value Invoice Items returns in `code` — either as
`item.code` (preferred; it wins when both are sent) or as `item.id`. The numeric `id`
returned by Invoice Items is rejected by Sage with "Item 'N' specified is not valid"
(BL01001973). No automatic translation is applied: Sage item codes may themselves be
numeric, so a record number cannot be told apart from a code.

## POST /accounting/bills

`bill_number` and `reference` both map to the single `DOCNUMBER` field, and `bill_number` wins — when both are sent, `reference` is silently discarded. On read both are populated from `DOCNUMBER` and are always identical. Send `reference` without `bill_number` if you rely on it as a back-reference. `bill_number` is additionally written to `RECORDID`.

`notes` is unaffected — it maps to `DESCRIPTION` and round-trips independently.

## PATCH /accounting/bills/{id}

`line_items[].item` must reference the item by its Sage item code (`ITEMID`), not by the
numeric record number. Pass the value Invoice Items returns in `code` — either as
`item.code` (preferred; it wins when both are sent) or as `item.id`. The numeric `id`
returned by Invoice Items is rejected by Sage with "Item 'N' specified is not valid"
(BL01001973). No automatic translation is applied: Sage item codes may themselves be
numeric, so a record number cannot be told apart from a code.

## POST /accounting/invoices

`line_items[].item` must reference the item by its Sage item code (`ITEMID`), not by the
numeric record number. Pass the value Invoice Items returns in `code` — either as
`item.code` (preferred; it wins when both are sent) or as `item.id`. The numeric `id`
returned by Invoice Items is rejected by Sage with "Item 'N' specified is not valid"
(BL01001973). No automatic translation is applied: Sage item codes may themselves be
numeric, so a record number cannot be told apart from a code.

Sage Intacct accepts a second invoice whose `number` already exists for the same customer.
To have it rejected instead, set **If duplicate numbers** to **Do not allow transactions to
be created** in the company's Accounts Receivable configuration (Document sequencing
section). The check is per customer, covers invoices and credit notes, and matters when you
send `number`; invoices numbered by a document sequence do not collide. It does not apply
to journal entries.

Send an `Idempotency-Key` to make retrying a create safe: Unify sends Sage Intacct a
32-character control id derived from it, and Intacct rejects a second create with the same
control id as already recorded instead of creating a second invoice. When you sent
`number` (or `code`), Unify then finds the invoice by that number and its customer and
returns the original invoice's id. Without either there is nothing to find it by, and
with document sequencing on Intacct replaces the number you sent with its own, so in both
cases the retry returns Sage Intacct's "already recorded" error: no second invoice is
created, and the invoice can be found by listing the customer's invoices. If several
invoices for that customer share the number, the error is
returned as well. A create Sage Intacct rejected can be retried with the same key and the
same body; a corrected body needs a new key. Use a new key for every invoice.

## PATCH /accounting/invoices/{id}

`line_items[].item` must reference the item by its Sage item code (`ITEMID`), not by the
numeric record number. Pass the value Invoice Items returns in `code` — either as
`item.code` (preferred; it wins when both are sent) or as `item.id`. The numeric `id`
returned by Invoice Items is rejected by Sage with "Item 'N' specified is not valid"
(BL01001973). No automatic translation is applied: Sage item codes may themselves be
numeric, so a record number cannot be told apart from a code.

## POST /accounting/credit-notes

`allocations[]` is not applied on create — the credit is created as a standalone `ARADJUSTMENT`. To apply it to invoices, follow up via `/vault/proxy` with `create_arpymt` drawing from the new credit's `RECORDNO` against the target `ARINVOICE`. `allocations` is not returned on read.

`reference` cannot be stored — Sage Intacct's `create_aradjustment` rejects a `docnumber` element, and while it accepts an `externalid` element on create, that value is silently dropped and never returned on read (verified live). The `reference` field is only returned on reads when `DOCNUMBER` was set through another channel; it cannot be set on create or update via this API.

Sage Intacct accepts a second credit note whose `number` already exists for the same customer. To have it rejected instead, set **If duplicate numbers** to **Do not allow transactions to be created** in the company's Accounts Receivable configuration (Document sequencing section). The check is per customer, covers invoices and credit notes, and matters when you send `number`; credit notes numbered by a document sequence do not collide. It does not apply to journal entries.

Send an `Idempotency-Key` to make retrying a create safe: Unify sends Sage Intacct a 32-character control id derived from it, and Intacct rejects a second create with the same control id as already recorded instead of creating a second credit note. When you sent `number`, Unify then finds the credit note by its number and customer and returns the original credit note's id. `filter[number]` finds a credit note by its number (exact match). Without `number` there is nothing to find it by, and with document sequencing on Intacct replaces the number you sent with its own, so in both cases the retry returns Sage Intacct's "already recorded" error: no second credit note is created. If several credit notes for that customer share the number, the error is returned as well. A create Sage Intacct rejected can be retried with the same key and the same body; a corrected body needs a new key. Use a new key for every credit note.

## PATCH /accounting/credit-notes/{id}

`reference` cannot be stored — Sage Intacct's `update_aradjustment` rejects a `docnumber` element, and the `externalid` element it accepts is a create-only integration key that is never returned on read (verified live), so it cannot round-trip. The `reference` field cannot be set on create or update via this API.

## POST /accounting/bill-credit-notes

Bill credit notes are Sage Intacct AP Adjustments. `total_amount`, `balance` and `remaining_credit` are reported as positive magnitudes (Sage stores an AP credit as a negative amount internally). On a multi-entity company a `line_items[].location` is required on create. `supplier.id`/`line_items[].ledger_account.id` must be the Sage vendor id / account number (the values returned as `supplier.display_id` / `ledger_account.code`), not the internal record numbers.
`number` is assigned by Sage and read-only; `reference` maps to the vendor document number. Line-level tax is not written — the same limitation as the AR `credit-notes` resource, since both are Sage adjustment objects: per-line tax is captured only under an Advanced Tax solution with multi-line tax enabled, and is otherwise discarded silently despite the write returning 201.
A posted AP adjustment reads back as `status: posted` (consistent with how other connectors report a posted bill credit note). Note this differs from the AR `credit-notes` resource on the same connector, which reports `authorised` for the equivalent posted Sage state — the underlying Sage state is the same, only the unified value reported per resource differs.

## GET /accounting/profit-and-loss

Sage Intacct's Profit and Loss report is structured around account groups.  To retrieve accurate data, all income statement accounts must be mapped to one  of the following categories: Revenue, Cost of Revenue, Operating Expense, or Other Expenses.  Any income statement accounts that are not mapped to one of these categories  will appear under the `uncategorized_accounts` property.

## GET /accounting/journal-entries

Sage Intacct returns journal entry headers only for this endpoint — Intacct's `GLBATCH`
query does not include line items. The `line_items` array is therefore omitted from each
entry in this list. To read line-level detail (accounts, amounts, departments, locations,
tracking categories), fetch each entry individually with the Get Journal Entry endpoint.

`number` is the batch number Intacct assigns to the entry, and `source_id` is the reference
you supplied when creating it. Both can be looked up with an exact-match, case-sensitive
filter: `?filter[number]=432` or `?filter[source_id]=your-reference`. Intacct numbers batches
per journal, so `filter[number]` can return one entry per journal (for example a general,
a sales and a receipts journal entry all numbered 435) — check `journal_symbol` on the
results, or combine it with `filter[source_id]`. `source_id` is not enforced as unique — if
several entries were created with the same value, `filter[source_id]` returns all of them — so
use a unique value per entry. A non-numeric `filter[number]` is rejected by Intacct with a 400.

## POST /accounting/journal-entries

Sage Intacct only supports `draft` and `posted` status fields when updating a journal entry.
If no status is provided then the default status is "draft".
Only Journal Entries with `draft` status can be updated.
Only the first tracking category is used (`tracking_categories[0]`). On both create and
update, the connector sends both `CLASSID` (from `tracking_categories[0].code`) and
`CLASSDIMKEY` (from `tracking_categories[0].id`), so a class may be referenced by its `id`
(as returned by `GET /accounting/tracking-categories`) or by its `code` on either verb;
supplying just one of the two is sufficient.
Each line's `location_id` falls back to the connection's configured default entity. When
the connection operates at the top level (no specific entity), set `location_id` explicitly
on lines whose ledger account requires a location dimension.

Send an `Idempotency-Key` to make retrying a create safe: Unify sends Sage Intacct a
32-character control id derived from it, and Intacct rejects a second create with the same
control id as already recorded. When you sent a `source_id`, Unify then finds the journal
entry by it and returns the original journal entry's id. Without a `source_id` there is
nothing to find it by, so the retry returns Sage Intacct's "already recorded" error: no
second journal entry is created. If several journal entries share your `source_id`, the
original cannot be identified and the error is returned as well, so use a unique
`source_id` per journal entry. A create Sage Intacct rejected can be retried with the same
key and the same body; a corrected body needs a new key. Use a new key for every journal
entry. A `pass_through` that changes the control id replaces the key, and a repeat of that
value stays an error.

`number` cannot be set on create: Intacct assigns the batch number itself (a sequence per
journal, so it is only unique together with `journal_symbol`) and any value sent is ignored.
`memo` is not written — put the text in `title`, which is returned as both
`title` and `memo`. `source_id` is stored and returned, and is the recommended recovery key
after a create that timed out or returned a 5xx: look the entry up with `filter[source_id]`
on the list endpoint before retrying. It is not a duplicate guard — Intacct accepts the same
`source_id` on several entries — so use a unique value per entry.

## PATCH /accounting/journal-entries/{id}

Sage Intacct only supports `draft` and `posted` status fields when updating a journal entry.
If no status is provided then the default status is "draft".
Only Journal Entries with `draft` status can be updated.

## GET /accounting/locations

Returns the full set of Intacct `LOCATION` records — every location, whether or not it
is flagged as a legal entity. For only entity-flagged locations (legal sub-companies under
the top-level company), use `companies` or `subsidiaries`, which query Intacct's
`LOCATIONENTITY` view.

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

Only `invoice` and `bill` are supported as a `reference_type`. Listing attachments for
`expense`, `expense-report` or `quote` returns an empty list rather than an error, even
when the record has a file attached in Sage.

Use the `sage-intacct-rest` connector if you need attachments on those record types — it
supports all five.

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

Only `invoice` and `bill` are supported as a `reference_type`. An upload against
`expense`, `expense-report` or `quote` still returns `201` with an attachment `id`, but
the file is NOT linked to the record: it is created in Sage under
Company > Supporting Documents and left unattached. Listing that record's attachments
then returns an empty array, and deleting the returned `id` answers `200` without
removing anything.

Treat a `201` on those three reference types as "file stored, not attached" — no status
code on any of the three calls reports the problem. Use the `sage-intacct-rest` connector
if you need attachments on those record types; it supports all five.

The `name` you send in `x-apideck-metadata` becomes Sage's supporting-document ID, which
Sage limits to **20 characters**. A longer name is rejected with `400`
(`PL03000018 — Attachments ID is too long`).

## GET /accounting/bank-accounts

Pagination is not supported when listing all bank accounts without an account_type filter.

## GET /accounting/journals

Use `code` as `journal_symbol` when creating a journal entry — it is the same symbol (e.g. "GJ") already used there. Sage Intacct journals carry no category of their own, so `type` is always `general`; `allow_vat` and `currency` are not exposed and are always absent.

## GET /accounting/trial-balance

Built from Sage Intacct's `get_trialbalance` on the reporting book that matches `filter[accounting_method]`: the accrual book by default and for `accrual`, the cash book for `cash`. If Intacct rejects the cash book, for example because the company has no cash book, the request returns a `502` with Intacct's message. The report is requested from the first day of the fiscal year that contains `end_date` to `end_date`, which defaults to today. Balance sheet accounts are cumulative to `end_date`; income and expense accounts are year-to-date. The fiscal year start is the selected entity's own first fiscal month, else the company's; if neither can be read the request returns a `502` rather than assuming January. Intacct closes earlier years into retained earnings itself, so debits equal credits without an extra line. Each account's balance is netted into one debit or credit, and accounts with a zero balance are left out. Statistical accounts are excluded; any other balance without a matching ledger account is returned with `line_type` `unmapped`, `code` set to its account number and null `account_id` and `name`. For every other line `line_type` is `account`, `account_id` is the ledger account's record number (the id `GET /accounting/ledger-accounts/{id}` takes), `code` its account number and `name` its title (extra requests read the chart of accounts and the fiscal year setting). The report covers the entity selected by `x-apideck-company-id`, else the connection's entity. `x-apideck-company-id: top-level` on a connection whose Default Entity is a specific entity returns a `422`, because the connection's session is fixed to that entity; use a top-level connection instead. A report that spans several currencies returns a `422`, so select an entity. A date before the ledger starts returns an empty report. A failure reading Intacct returns a `502`, or `429` when Intacct throttles. A report whose debits and credits differ by half a cent or more returns a `502`.
## GET /accounting/tracking-categories

The Parent ID field refers to the parent tracking category code.
## POST /accounting/tracking-categories

When creating a category with parent ID please use the tracking category code.
## GET /accounting/tracking-categories/{id}

The Parent ID field refers to the parent tracking category code.
## PATCH /accounting/tracking-categories/{id}

When updating the Parent ID field please use the tracking category code.
## POST /accounting/bill-payments

`payment_method` is required. A bill payment created without one is rejected with the generic downstream error `BL34000094`, which does not name the missing field. The value must match one of Sage Intacct's system AP payment methods — e.g. `Cash`, `Printed Check`, `EFT`, `Charge Card`, or `Record Transfer`. These methods are company-wide (not per-vendor or per-invoice), so a single value can be reused across all bill payments. There is no endpoint that lists payment methods; the values configured on a company can be discovered by reading existing bill payments or payments and inspecting `payment_method`. Recording an `EFT` payment does not require ACH/EFT bank setup on the vendor or account.
`account` (the bank/GL account the payment is drawn from) is also required — a bill payment without it is rejected with the generic code `BL01001973` ("Enter a valid bank account"). Like `BL34000094`, `BL01001973` is a *generic* Sage business-logic code that does not name the cause (it is also returned for closed periods, invalid dates, etc.); always read the specific reason in the error `detail` (`description2`) rather than the code alone. When routing a payment to a specific entity (`x-apideck-company-id` / `subsidiary.id`), the `account` must be one that is valid in that entity.

## POST /accounting/expenses

When creating an expense for Sage Intacct, the account_id must refer to a credit card account, not a ledger account. You can find the correct credit card account ID in Sage Intacct under the Credit Cards section.
## DELETE /accounting/expenses/{id}

The delete operation for credit card transactions in Sage Intacct is not a true deletion. Instead, it performs a reversal using the reverse_cctransaction function.
## GET /accounting/quotes

Quotes are Order Entry transactions (`SODOCUMENT`). The list endpoint returns header fields only — `line_items` are omitted on list results; use the GET one endpoint to retrieve full line-item detail for a quote.
Quotes are scoped by their transaction-definition class rather than by name, so every quote document type on the tenant is returned — including custom or renamed quote definitions. Sage Intacct lets a tenant define multiple quote document types with arbitrary names; all of them are matched.

## PATCH /accounting/quotes/{id}

When updating line items, each line item must include its `line_number`. Sage Intacct matches existing lines by `line_number`, not by `id`.

## GET /accounting/sales-orders

Sales orders are Order Entry documents (`SODOCUMENT`) of the Order class. System-generated fulfilment documents (transaction definitions starting with `SYS-`) are not returned. The list returns order headers only — `line_items` are returned when you retrieve a single sales order. `delivery_date` is the order's need-by date and is null when none was entered. `po_number` is the customer PO number and `reference` the document's reference number. `status` is `back_ordered` when the order is flagged as back-ordered and still open; `invoiced` means the order is fully converted. Sage Intacct does not record the invoice created from an order, so `invoice_id` is not returned. The document the order was converted from is exposed by its record number as the `source_document_key` custom field rather than as `quote_id`, because it is not necessarily a quote.

## POST /accounting/sales-orders

Sage Intacct creates a sales order against a named Order transaction definition.
Send it as `order_type` (for example `Sales Order-Inventory`). If you omit it, the
connection setting **Sales Order Transaction Definition** (`sales_order_document_type`)
is used, and if that is blank the company's single active Order definition is used.
If the company has several active Order definitions the request fails and lists
them; set `order_type` or the connection setting.

Every line needs `item.id` (or `item.code`) and `unit_of_measure`, and the customer,
item and location must exist; on a multi-entity company each line also needs
`location_id`. A sales order needs at least one line. `status` accepts `draft` or
`open`; other values are ignored. `delivery_date` is written as the need-by date.
`currency` is written with `currency_rate` when given, otherwise Intacct's daily rate
is used. Address `contact_name` must match an existing contact; other address fields
are not written. `po_number` is written as the customer PO number, `reference` as
the document reference number; `notes` is not written. `warehouse_id` and
`discount_memo` are accepted as line `custom_fields`.

## GET /accounting/sales-orders/{id}

Returns the full sales order including `line_items`. Line `line_number` is 0-based, as Sage Intacct numbers Order Entry lines; use it to address lines on update.

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

The customer, `order_type`, `status` and totals cannot be changed on update. Lines are matched by `line_number`; lines without a `line_number` are appended. Orders that have been converted to an invoice (status `invoiced`, or partially converted) are rejected by Sage Intacct and cannot be updated or deleted.


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