Documentation menu

API reference

32 operations in billing. Raw spec: openapi.json.

GET/api/v1/billing/invoice-runs

GET /api/billing/invoice-runs — most recent runs first.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/billing/invoice-runs

POST /api/billing/invoice-runs — evaluate every billable contract over the window (optionally one client's) and persist one invoice per contract with at least one candidate line. Idempotency-keyed (IF1): the same key with the same body replays the stored run.

Request body

CreateInvoiceRunRequest

Responses

StatusDescription
200
409mixed-currency run or idempotency key reused
422validation failure

Security: bearer

GET/api/v1/billing/invoice-runs/{id}

GET /api/billing/invoice-runs/{id} — one run's roll-up.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404run not found

Security: bearer

GET/api/v1/billing/preview

GET /api/billing/preview — evaluate EVERY billable contract in the org over `[from, to]` (cancelled contracts excluded; effective windows must intersect). READ-ONLY: the org-wide dry-run of a Wave-9 invoice run.

Parameters

NameInTypeRequiredDescription
fromquery—NoWindow start (inclusive), ISO date.
toquery—NoWindow end (inclusive), ISO date.

Responses

StatusDescription
200
422missing or inverted window

Security: bearer

GET/api/v1/billing/verification

GET /api/billing/verification — the workspace for one contract over one window. READ-ONLY: no entry changes, no side effects.

Parameters

NameInTypeRequiredDescription
contract_idquerystringYesThe contract under review. Required: the workspace is per-contract because pricing, rate card, and blocks are.
fromquerystringYesInclusive period start (YYYY-MM-DD).
toquerystringYesInclusive period end (YYYY-MM-DD).

Responses

StatusDescription
200
404contract not visible in scope (rls_sparse)
422inverted or oversized window

Security: bearer

POST/api/v1/billing/verification/entries/{id}/action

POST /api/billing/verification/entries/{id}/action — every in-context entry edit, one envelope. Gated on the instant-first mutation contract: same key + same payload replays, different payload 409s.

Parameters

NameInTypeRequiredDescription
idpathstringYesTime entry id

Request body

EntryActionRequest

Responses

StatusDescription
200
403own_entry: an approver cannot decide their own entry
404entry not visible in scope (rls_sparse)
409entry already invoiced; verification is pre-run only
422missing reason, unknown action, or invalid value

Security: bearer

POST/api/v1/billing/verification/signoff

POST /api/billing/verification/signoff — the close ritual (report V8). The checklist booleans are RE-VERIFIED against the live workspace, never taken on trust: signing off "every entry dispositioned" while a draft entry sits in the window is a 422, not a stored lie. The overlap warning rides the response.

Request body

SignoffRequest

Responses

StatusDescription
200
404contract not visible in scope (rls_sparse)
422checklist claim does not match the live window

Security: bearer

GET/api/v1/contracts

GET /api/contracts — list with optional client/status/effective-date filters, ordered by contract number.

Parameters

NameInTypeRequiredDescription
client_idquery—No
statusquery—NoExact status: `draft|active|ended|cancelled`.
effective_onquery—NoOnly contracts effective on this date.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/contracts

POST /api/contracts — instant-first (IF1/IF2): idempotency-keyed and returns the read-your-write `mutation` metadata.

Request body

CreateContractRequest

Responses

StatusDescription
200
409duplicate contract number, idempotency key reused, or version conflict
422validation failure

Security: bearer

GET/api/v1/contracts/{id}

GET /api/contracts/{id} — the contract aggregate (agreement + rate card + fee schedule).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200

Security: bearer

PATCH/api/v1/contracts/{id}

PATCH /api/contracts/{id} — instant-first (IF1/IF2) partial update. Absent fields keep their values; `rates`/`fees`, when present, replace the whole card/schedule. The version gate rejects stale writes with a typed 409.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

PatchContractRequest

Responses

StatusDescription
200
404contract not visible in scope
409version conflict, duplicate number, or idempotency key reused
422validation failure

Security: bearer

DELETE/api/v1/contracts/{id}

DELETE /api/contracts/{id} — removes the contract aggregate. A typed 404 (`rls_sparse`) means the target was never visible in scope. The Wave 9 invoicing lane's billing-lock guard refuses the delete once invoices reference the contract (typed 409).

The response is an acknowledgement, not the aggregate: deleting is a commercial action, not a rate read (RB2), so a commercial-delete holder without `financial:read` can still remove a contract and never receives its money. Every rate-bearing surface in this module keeps its `financial:read` gate.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404contract not visible in scope
409contract has invoices

Security: bearer

GET/api/v1/contracts/{id}/billing-preview

GET /api/contracts/{id}/billing-preview — evaluate this contract's billing rules over `[from, to]` and return the draft invoice-line candidates. READ-ONLY: nothing persists, no billed markers exist.

Parameters

NameInTypeRequiredDescription
idpathstringYes
fromquery—NoWindow start (inclusive), ISO date.
toquery—NoWindow end (inclusive), ISO date.

Responses

StatusDescription
200
404contract not found
422missing or inverted window

Security: bearer

POST/api/v1/contracts/{id}/change-orders

POST /api/contracts/{id}/change-orders — create a DRAFT order. The lifecycle forward-path (approve → billed at run time) keeps billing honest: only APPROVED orders ever price, and the run itself records which invoice consumed them.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

CreateChangeOrderRequest

Responses

StatusDescription
200
404contract not visible in scope
409duplicate change order number or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/contracts/{id}/change-orders/{order_id}/transition

POST /api/contracts/{id}/change-orders/{order_id}/transition — move a draft order through its lifecycle. `approve` stamps the approver + time (the audit trail's who/when); `reject`/`cancel` are terminal.

Parameters

NameInTypeRequiredDescription
idpathstringYes
order_idpathstringYes

Request body

TransitionChangeOrderRequest

Responses

StatusDescription
200
404order not visible in scope
409transition not allowed from the current status
422validation failure

Security: bearer

POST/api/v1/contracts/{id}/sla-credits

POST /api/contracts/{id}/sla-credits — record a pending service credit. NEGATIVE at billing: the next run whose window covers `applies_on` prices it as the invoice's `sla_credit` line.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

CreateSlaCreditRequest

Responses

StatusDescription
200
404contract not visible in scope
422validation failure

Security: bearer

POST/api/v1/contracts/{id}/sla-credits/{credit_id}/cancel

POST /api/contracts/{id}/sla-credits/{credit_id}/cancel — withdraw a pending credit before any run consumes it. Applied credits are immutable (additive-ledger rule: corrections issue NEW credits).

Parameters

NameInTypeRequiredDescription
idpathstringYes
credit_idpathstringYes

Responses

StatusDescription
200
404credit not visible in scope
409credit already applied

Security: bearer

GET/api/v1/credit-notes

GET /api/credit-notes — newest first, client/status filters.

Parameters

NameInTypeRequiredDescription
client_idquery—No
statusquery—NoExact status: `issued` | `void`.

Responses

StatusDescription
200

Security: bearer

POST/api/v1/credit-notes

POST /api/credit-notes — issue a client credit through the instant-first write path.

Request body

IssueCreditNoteRequest

Responses

StatusDescription
200
404client or invoice not visible in scope
409idempotency key reused
422validation failure

Security: bearer

GET/api/v1/credit-notes/{id}

GET /api/credit-notes/{id} — the credit plus its application ledger.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404not visible in scope

Security: bearer

POST/api/v1/credit-notes/{id}/apply

POST /api/credit-notes/{id}/apply — allocate part of the remaining credit onto an issued invoice. The allocation inserts a real payment receipt (provider `credit_note`) through the same apply path as ordinary payments, then moves the credit's counter in the SAME transaction (both compare-and-set: over-allocation is impossible).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

ApplyCreditNoteRequest

Responses

StatusDescription
200
404credit note or invoice not visible in scope
409not enough remaining credit, invoice not payable, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/credit-notes/{id}/refund

POST /api/credit-notes/{id}/refund — pay part of the remaining credit back to the client. v1 approves the refund locally (none-mode provider, exactly like charges); the amount may never exceed the remaining credit.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

RefundCreditNoteRequest

Responses

StatusDescription
200
404credit note not visible in scope
409not enough remaining credit, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/credit-notes/{id}/void

POST /api/credit-notes/{id}/void — withdraw a note that has moved nothing. Terminal.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

VoidCreditNoteRequest

Responses

StatusDescription
200
404credit note not visible in scope
409already void, has allocations, version conflict, or idempotency key reused

Security: bearer

GET/api/v1/invoices

GET /api/invoices — list with client/contract/status/dunning filters.

Parameters

NameInTypeRequiredDescription
client_idquery—No
contract_idquery—No
statusquery—NoExact status: `draft|issued|paid|void`.
dunning_statequery—NoExact dunning state: `none|reminder_sent|overdue|final_notice|collections`.

Responses

StatusDescription
200

Security: bearer

GET/api/v1/invoices/{id}

GET /api/invoices/{id} — the invoice aggregate (document, lines, payment receipts).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404invoice not found

Security: bearer

GET/api/v1/invoices/{id}/document

GET /api/invoices/{id}/document — the print-ready HTML invoice document (print-to-PDF from any browser; v1 ships no PDF binary output — see the private build tracker).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404invoice not found

Security: bearer

POST/api/v1/invoices/{id}/dunning

POST /api/invoices/{id}/dunning — move an ISSUED invoice's dunning state forward (none → reminder_sent → overdue → final_notice → collections; skipping rungs is allowed, backwards is refused). States are data: the write emits `invoice.dunning_changed` and lands in the write trail. Instant-first gated (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

DunningTransitionRequest

Responses

StatusDescription
200
404invoice not visible in scope
409invoice not issuable, version conflict, or idempotency key reused
422backwards or same-state transition

Security: bearer

POST/api/v1/invoices/{id}/dunning/statement

POST /api/invoices/{id}/dunning/statement — generate an account statement (the client's OPEN invoices with outstanding balances) and queue it to the client's contacts through the outbound ledger (`MailTransport`; none-mode stays queued like every other leg). The statement rides the dunning path but is a document delivery, not a dunning-state change. Instant-first gated (IF1).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

SendStatementRequest

Responses

StatusDescription
200
404invoice not visible in scope
409invoice not issuable or idempotency key reused
422no contact emails on the client to deliver to

Security: bearer

GET/api/v1/invoices/{id}/payments

GET /api/invoices/{id}/payments — the payment receipts.

Parameters

NameInTypeRequiredDescription
idpathstringYes

Responses

StatusDescription
200
404invoice not found

Security: bearer

POST/api/v1/invoices/{id}/payments

POST /api/invoices/{id}/payments — record a payment through the `PaymentProvider` seam. The charge runs INSIDE the mutation transaction so receipt, payment row, and invoice status commit (or roll back) atomically. Instant-first gated (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

RecordPaymentRequest

Responses

StatusDescription
200
404invoice not visible in scope
409invoice not payable, overpayment, version conflict, or idempotency key reused
422validation failure

Security: bearer

POST/api/v1/invoices/{id}/void

POST /api/invoices/{id}/void — withdraw an ISSUED invoice. Terminal: consumption markers stay, so re-running the window can never double-bill. Instant-first gated (IF1/IF2).

Parameters

NameInTypeRequiredDescription
idpathstringYes

Request body

VoidInvoiceRequest

Responses

StatusDescription
200
404invoice not visible in scope
409invoice not voidable, version conflict, or idempotency key reused

Security: bearer

POST/api/v1/webhooks/stripe

stripe_webhook_receive

Request body

array<integer>

Responses

StatusDescription
200
400
403
409the invoice refused the settlement: it left its payable state, its total moved since the event was priced, or the amount exceeds the balance