When to use this endpoint

Call InvoiceRow_list first, then use this route when a persisted invoice editor creates a new row.

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

ParameterTypeRequiredDescription
invoice_id / idPositive integer-compatibleYesActive invoice ID. invoice_id takes precedence over the legacy id alias.
rowObject or JSON object stringYesLine item. Common fields include description, price, qty, net/netPrice, discountOverall, gross, taxpc, tax and nominal IDs/codes, itemcode, notes, source IDs, and optional job_id.
positionInteger-compatibleNoRequest value takes precedence over row.position. Blank/omitted appends; otherwise values below zero become 0 and existing rows at/after the position shift up.

Example request

$result = FM_api('InvoiceRow_add', [
	'invoice_id' => 981,
	'row' => json_encode(['description' => 'Service', 'qty' => 2, 'price' => 50, 'taxpc' => 20]),
]);

Example response

{
  "ok": 1,
  "total_net": 100,
  "total": 120,
  "row": {"description":"Service","qty":2,"price":50,"net":100,"gross":120,"invoice_row_id":441,"position":0}
}

Errors and unusual responses

ResponseCondition
{"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":"invoice row job is not linked to invoice; reload before editing"}A positive job_id is not directly/legacy linked to the invoice.
{"error":"...refer to different accounts..."}Nominal ID/code aliases conflict.

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 waits up to about 15 seconds for the invoice-row lock; contention returns invoice rows are being saved. please try again soon..
  • The first targeted row operation lazily copies legacy embedded rows to the row table and sets meta.rows_storage=table.
  • Empty rows are rejected. Missing net defaults from price × quantity, tax selection can override taxpc, and gross is derived when absent or tax changes.

Side effects

  • Inserts a row-table record, recalculates invoice net/gross totals and edit metadata, fires Invoice_edit, and updates price totals/edit metadata on linked jobs.
  • Once table storage is canonical, targeted mutations do not rewrite the legacy invoices.rows cache; readers use the storage marker.

See it in Fieldmotion

CRM1

Job Invoice, invoice line-item widgets, and the main Invoices editor call this route when a new saved row is edited.

CRM2

Configurable invoice and Job Costs table widgets use this route for a new persisted row and include the linked job ID where applicable.

Related endpoints

  • InvoiceRow_list supplies stable row IDs and positions before a targeted edit.
  • Invoice_setRows replaces the complete row collection for compatibility callers.