Record Payment
Records a payment against the invoice — a gesture the server resolves into
per-line settlement MARKS, executing one invoice.payment_recorded action
per mark in one transaction and returning the CREATED MARK ROWS
(paymentIds + createdPayments), each mark the handle for
removing it.
Two modes, exactly one per request. Send either payBalanceDue: true
or an allocations array — both together, or neither, is a 400.
payBalanceDue: truesettles the whole document: one mark per OPEN line item at that line’s full remaining, in the document’s line-item order, BOTH directions at once. The invoice comes backpaidwithbalanceDueCents: 0, and the gesture’s net cash equals thebalanceDueCentsyou asked to pay, by construction. Only the literaltrueis accepted (payBalanceDue: falseis a400) — to pay less, name the lines.allocationsmarks exactly the line items you name, for exactly the cents you name. Nothing is spread, split, or inferred: a line you do not name is untouched, and a line you name for less than its remaining stays open.
Allocation amounts are in the LINE’s frame. allocations[].amountCents
settles its own line’s remaining toward zero, so it carries the sign of
that remaining — this is NOT the oriented net-cash frame balanceDueCents
reads in. A payable-direction line with 8,000 remaining takes a mark of
+8000; a receivable-direction line with 3,000 remaining takes a mark of
+3000 too (money in, but the line’s own remaining is positive). Each
amount must be a nonzero integer, and a lineItemId may appear at most
once per request (both 400s).
Per-mark guards (422). Every mark must name a line item on the
document (422 UNKNOWN_ID), be nonzero and carry its line’s remaining’s
sign — a mark against a settled line, or one running opposite its line’s
remaining, is 422 INVALID_AMOUNT, as is a payBalanceDue against a
document with nothing open — and its magnitude may not exceed its line’s
remaining’s (422 AMOUNT_EXCEEDS_BALANCE_DUE). The bounds are per line;
there is no document-scalar bound. The payment date must not be in the
future (422 FUTURE_DATE).
Reserve erosion. On an invoice in a reserved category,
erodeReserves is REQUIRED (true = the marks consume the linked
event’s reserves; eroding requires an event link —
422 ERODE_REQUIRES_EVENT). On an operating-category invoice the field
must be omitted (422 ERODE_RESERVES_MISMATCH either way it is
misused). There is no headroom bound: the marks’ oriented reserve effect
applies to the remaining reserve unbounded — the remaining reserve is a
SIGNED balance, and eroding past the estimate takes it below zero (the
books’ statement that payments have outrun an estimate that still
stands), never a 422.
Approval gate (422 NOT_APPROVED). Money may only post against an
APPROVED invoice (approved: true on the invoice row) — paid ⇒ approved. On an UNAPPROVED invoice the outcome depends on the calling
key’s permissions, mirroring the in-app behaviour where a sufficiently
authorized user’s payment auto-approves:
- the key holds
company.payment:approve→ the invoice is APPROVED automatically as part of this request, in the same transaction and before the payment marks, and the payment posts. The response’s invoice row comes backapproved: true; the auto-approval is deliberately NOT listed injournalIds(that array is the payment gesture’s marks, so an idempotent replay returns an identical body). - the key does NOT hold it →
422 NOT_APPROVED. Approve the invoice first — via approve with a key that holds the permission, or in the app.
A draft is rejected as 422 INVOICE_DRAFT regardless of approval: no
money may post against an unposted document.
Concurrency + idempotency. Requires If-Match (the invoice’s current
headJournalId) and a client-minted actionId. The actionId anchors
the FIRST mark’s journal action; sibling marks take server-minted ids in
the same transaction. An identical retry replays the original outcome —
the full paymentIds set included. Reusing an actionId with different
allocations is 409 ACTION_ID_REUSED.
Required permission: company.payment:update
Authorizations
API key authentication. Send your raw API key as the Authorization header value with NO scheme prefix — Authorization: YOUR-API-KEY. Do NOT prefix it with Bearer or ApiKey, and do not use an X-API-Key header; those are not accepted.
Headers
The invoice's current headJournalId — the optimistic-concurrency watermark every single-invoice write after creation must send. Read it off any invoice read or write response and echo it verbatim (a bare uuid; an entity-tag dressing of it — "uuid" or W/"uuid" — is also accepted). Missing or malformed is a 400 (IF_MATCH_REQUIRED / IF_MATCH_INVALID); a stale value is a 409 IF_MATCH_CONFLICT whose body carries the current invoice. An idempotent actionId replay short-circuits BEFORE the watermark is evaluated.
Path Parameters
Company identifier
Invoice identifier
Body
Client-minted idempotency key — becomes the FIRST created mark's journal id (sibling marks mint their own). An identical retry replays the original outcome (the created paymentIds included); reuse with a different payload is 409 ACTION_ID_REUSED
The payment date, stamped on every created mark (ISO YYYY-MM-DD); must not be in the future
Optional display label for the source system's author (e.g. the integrator-side user). Stamped as the journal record's display attribution; the acting principal stays the External API service user, so a label can never impersonate an in-app user. Ignored on idempotent replays
1 - 255Settle the WHOLE document: one mark per open line item at that line's full remaining, both directions at once, so the gesture's net cash equals balanceDueCents. Only the literal true is accepted. Mutually exclusive with allocations — send exactly one of the two
true Explicit per-line marks — exactly the line items named, for exactly the cents named; nothing is spread onto an unnamed line. Mutually exclusive with payBalanceDue — send exactly one of the two. Each lineItemId may appear at most once
1Free-text memo, stamped on every created mark
REQUIRED on a reserved-category invoice — whether the created marks erode the linked event's reserves; must be OMITTED on an operating-category invoice. No headroom bound applies: the remaining reserve is a signed balance and may cross zero
Response
The refreshed invoice state plus the created MARK rows: paymentIds are the server-minted removal handles in mark order, and createdPayments the full mark rows they name (each also present in the refreshed payments)
The response of every single-invoice write — refreshed read state, not an ack: the emitted journal id(s), THE invoice row (identical shape to the reads — one invoice representation everywhere; its headJournalId is the next If-Match), and the invoice's live payments. An idempotent actionId replay returns this same shape rebuilt from the original outcome, server-minted values included.
Every journal id the write emitted — the anchor action's id (the actionId you supplied) first, then any engine-minted siblings (a multi-mark payment's additional marks) and any companion the horizon rule composed (e.g. the reserve unwind of a pre-horizon eroding payment's removal, or a delete's payment sweep)
1THE invoice representation — the same shape everywhere an endpoint returns an invoice (listing rows, the detail read, and every write's refreshed-row response). headJournalId is the invoice's current journal head — the optimistic-concurrency token subsequent writes echo back as If-Match.
The invoice's live payment marks after the write, newest first
The created marks' server-minted ids, in mark order (the request's allocations order, or line-item order under payBalanceDue) — each the handle for DELETE …/payments/{paymentId}
The created mark rows, in mark order
