Invoices
Append invoice rows
/a/InvoiceRow_appendMany
Append a normalized batch of line items to an unpaid invoice under one row-storage lock.
When to use this endpoint
Use this when copying/importing multiple rows into an existing invoice. It avoids repeated lock and total-recalculation work.
Authentication and permissions
The request must use a logged-in CRM session or valid signed API credentials and is scoped to that tenant.
Users whose legacy crm_access mask contains bit 64 receive {"error":"no permission to access api"}. No record-owner check is made.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
invoice_id / id | Positive integer-compatible | Yes | Active invoice ID. |
rows | Array or JSON array string | Yes | Rows appended after the current maximum position. Empty line structures are discarded; an empty array is accepted and returns no row IDs. |
Example request
$result = FM_api('InvoiceRow_appendMany', [
'invoice_id' => 981,
'rows' => json_encode([
['description' => 'Part A', 'qty' => 1, 'price' => 20],
['description' => 'Part B', 'qty' => 2, 'price' => 15],
]),
]);
Example response
{
"ok": 1,
"total_net": 50,
"total": 50,
"row_ids": [441, 442]
}
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error":"cannot edit invoices that are marked Paid"} | Invoice status is Paid. |
{"error":"missing or invalid parameter: rows"} | Rows do not decode to an array. |
{"error":"...refer to different accounts..."} | A row has conflicting nominal ID/code aliases. |
{"error":"invoice row job is not linked to invoice; reload before editing"} | A surviving row names an unrelated job; the batch is rejected. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- The operation shares the same normalization, lazy hydration, 15-second lock wait, total recalculation, event, and linked-job updates as single-row add. Returned
row_idsfollow the surviving normalized row order. - Every positive row
job_idmust identify a directly/legacy linked job, as forInvoiceRow_add. Unassigned rows are allowed. All surviving rows are checked before hydration or batch inserts, so an unrelated owner rejects the entire batch without adding its valid prefix or changing totals.
Side effects
- Appends row-table records, recalculates invoice totals/edit metadata, fires
Invoice_edit, and recalculates candidate linked-job totals.
See it in Fieldmotion
CRM1
The invoice table widget uses this route when copying a group of prepared rows into a job invoice.
CRM2
Quote-to-invoice line-item copying and invoice-table workflows call this batch route.
Related endpoints
InvoiceRow_listsupplies stable row IDs and positions before a targeted edit.Invoice_setRowsreplaces the complete row collection for compatibility callers.