Skip to content

Finance & Budgets API

The Finance API exposes the same routes the product uses, with the same validation and permissions. See the Finance Setup and Budgets guides for the product behaviour.

All routes use API key authentication and run with the key owner’s live permissions. Finance must be turned on for the organisation; otherwise workspace routes return 409 with code: "FINANCE_NOT_ENABLED". The settings routes stay reachable so Finance can be turned on through the API.

GET /api/v1/finance/status includes tracking: { time, expenses }, with resolved boolean values. Both follow Finance activation by default. A settings manager can PATCH /api/v1/organizations/:id with { "settings": { "tracking": { "time": true, "expenses": false } } }. Each field is optional. Boolean values override Finance; null restores its default. Unknown fields and other value types are rejected. Partial writes keep the other feature’s choice. Normal organisation binding and settings permission apply.

New time entries, timers, schedule conversions and expense drafts return 409 when the feature is off. Task completion still succeeds and ignores supplied actual hours in that mode. Historical reads and corrections retain their permissions. Expense participation can be enabled without Finance, but billing entity and category setup are still required.

GET /api/v1/finance/expenses/options requires expense access and returns billingEntities (id, isDefault, defaultCurrency), expenseCategories (id, name, code, mileage, perDiem, disbursement) and taxRates (id, billingEntityId, name, code). These claim-form choices do not require a Finance seat or expose financial account configuration. The endpoint is also available through call_public_api.

All routes below are under /api/v1/finance/receivable-documents. They require enabled Finance, finance:manage_receivables and price access for the document’s client. Invoice import additionally requires draft or issue authority; payment confirmation requires finance:record_payments.

Method and suffixBehaviour
GET /List documents. Optional status, billingEntityId, clientOrganizationId, limit (1–100) and offset. Returns documents and visible total.
POST /Upload and automatically start extraction. Returns document and duplicate; 201 for a new upload, 200 for identical bytes already uploaded in this organisation.
GET /:idReturn document, suggested invoice matches, duplicateInvoices and existingPayments. Optional invoiceQuery (up to 100 characters) filters invoice numbers. Up to 200 candidates are returned; narrow the search for larger lists.
GET /:id/fileOriginal PDF/image bytes, with private/no-store headers. Add download=true for attachment disposition.
PATCH /:idSave reviewed fields, billingEntityId, nullable clientOrganizationId and expectedRevision. Optional kind corrects the document type.
POST /:id/extractRetry reading with expectedRevision. A new attempt clears the prior review acknowledgement.
POST /:id/import-invoiceCreate a draft, or link existingInvoiceId, using expectedRevision.
POST /:id/confirm-paymentExplicitly record allocations or link existingPaymentId.
POST /:id/dismissRetain the source but stop processing, using expectedRevision.
POST /:id/restoreRestore a dismissed document to received, with review acknowledgement cleared. Uses expectedRevision; imported or reconciled records cannot be restored.

Upload JSON contains kind (remittance or invoice), billingEntityId, optional nullable clientOrganizationId, fileName, contentType and contentBase64. Content types are application/pdf, image/png, image/jpeg and image/webp. Decoded bytes must match the type and be no larger than 10 MB.

The document has status, revision, original file metadata, editable fields, original extractedFields, confidence, reader note/error, reviewedAt, and linked invoiceId or paymentId. Statuses are received, extracting, review_ready, failed, imported, reconciled and dismissed. Poll while extracting; failures support retry or manual review.

In this intake contract, monetary field values and match amounts are bare integer minor-unit strings, paired with currency. Currency codes are normalised to uppercase, and surrounding whitespace is removed from document and payment references. Dates use YYYY-MM-DD. Fields include clientName (a hint only), invoiceReference, paymentReference, documentDate, dueDate, currency, subtotalAmount, taxAmount, totalAmount, description, lineItems and invoiceReferences. Each invoice line has description, decimal-string quantity, unitPrice and an explicitly reviewed taxRateId (or null for no tax). Model-provided tax IDs are discarded. References have reference and optional amount. Arrays are limited to 200.

A proposal is not an acknowledgement: save reviewed fields before any import or payment action. A stale revision returns 409. Invoice imports check the original reference for duplicates and compare calculated figures with supplied source totals. Imports create drafts with retained source references, without issuing or sending them.

Payment confirmation takes expectedRevision, paymentReceived: true, and explicit allocations: [{ invoiceId, amount }]. The allocations must be positive, within current outstanding balances, and sum exactly to fields.totalAmount. Optional method is bank_transfer, card, direct_debit, cheque, cash or other. A matching recorded payment can be linked using existingPaymentId and allocations: []. If a similar payment exists and a new payment is intended, provide duplicateReason. Period and payment permission failures apply normally. Repeated terminal confirmation returns the already-linked record.

These operations are available through call_public_api, including base64 uploads and binary download responses. Pass confirm: true for writes. The internal propose_receivable_fields tool is restricted to a server-created extraction task; external clients use the review and confirmation routes. See document intake.

POST /api/v1/finance/usage accepts usage: "full" or usage: "resource_planning". Both require an active membership and finance:manage_settings plus organization:manage_settings. Enabling also requires the Finance seat entitlement.

For new full setup, send legalName, two-letter jurisdictionCountry and taxRegistered. Supply taxIdentifier when registered; an Australian ABN is validated. Optional fields are tradingName, defaultCurrency, financialYearStartMonth (1-12), addressLine1, city, stateProvince, postalCode and billingEmail. Unknown fields return 400.

The response includes usage, enabled, defaultBillingEntityId, settings and setupChecklist. Resource planning disables Finance and financial planning without deleting records or removing clients. Ordinary POST /finance/enable can enable it again. Writes and their audit record commit together.

MCP offers configure_finance_usage with the same fields and authority, and get_finance_setup for the authorised setup checklist. call_public_api retains the same route validation, organisation binding and failure behaviour.

Most financial record amounts use an object with minor units as a string and an ISO currency code:

{ "amount": "740000", "currency": "AUD" }

740000 in AUD is $7,400.00. Send money the same way on writes, except line fields such as unitPrice, discountAmount, capAmount and estimatedCost, which take the minor-unit string alone in the budget’s currency. Quantities and hours are decimal strings such as "40" or "1.5".

Responses are stripped by the caller’s view capabilities. A field the caller cannot see is absent, not null:

Missing capabilityAbsent fields
finance:view_pricessold, price, rate, invoiced and money custom field values
finance:view_costestimated cost, cost and committed cost
finance:view_profitmargin

Financial record responses generally carry lens, the resolved { prices, cost, profit } flags for the caller on that subject.

Business rule refusals return 409 with a code:

CodeMeaning
FINANCE_NOT_ENABLEDFinance is not turned on
BUDGET_NOT_ACTIVEThe action needs an active budget
BUDGET_MANAGED_BY_OBJECTThe project’s budget figure is managed by a budget and cannot be edited directly
BUDGET_NO_LINES, BUDGET_LINE_UNPRICEDActivation needs at least one line and a price on every billable line
SCHEDULE_UNBALANCEDThe billing schedule does not add up to the schedule base; the response names the unallocated amount
RATE_CARD_CONFIRMATION_REQUIREDThe project’s rate card confirmation is still pending
CHANGE_ORDER_REQUIREDThe edit changes sold or a cap, or an applicable change order is already open. Edit the version instead of live lines
CHANGE_ORDER_INPUTS_CHANGEDSaved line inputs changed since opening, or an older proposal has ambiguous operational differences. Review, withdraw and reopen it
CHANGE_ORDER_OPENAnother change order is already open
VERSION_BASE_MISMATCHThe budget moved on since the change order was opened
APPROVAL_REQUIRED, APPROVAL_PENDING, APPROVAL_STALE, APPROVAL_NO_ELIGIBLE_APPROVERApproval outcomes when applying a change order
BUDGET_PENDING_APPROVALS, BUDGET_UNINVOICED, BUDGET_INVOICEDDeliver, close and cancel guards
LINE_NOT_TRACKING, BUDGET_OVERRUN_BLOCKEDTime cannot be recorded against the line
TAX_RATE_LOCKEDA used tax rate’s percentage cannot change
NUMBER_COLLISIONA rendered number already exists in the sequence
BANK_CHANGE_PENDINGA bank account change is already waiting for confirmation
INVALID_TRANSITIONThe status change is not allowed from the current status
TIMESHEET_LOCKEDThe entry’s date is behind the timesheet lock; needs time:manage_locked and lockOverrideReason
PERIOD_CLOSED, PERIOD_SOFT_CLOSEDThe date falls in a closed period, or in a soft-closed one and needs periodReason. Both carry details.period (YYYY-MM)
TIME_ENTRY_STATE_CHANGEDThe entry changed while the request was in flight; read it again and retry
TIME_ENTRY_SUBMITTED, TIME_ENTRY_APPROVED, TIME_ENTRY_INVOICED, TIME_ENTRY_WRITTEN_OFF, TIME_ENTRY_NOT_APPROVEDThe entry is in a state that refuses the action
TIMER_RUNNING, TIMER_STOPPEDOne timer per person; the timer is already running or already stopped
SEGMENT_DECIDEDThe approval segment was already decided
WIP_ADJUSTMENT_STATEThe write-off is not in a state that allows the action
WIP_SOURCE_CHANGEDThe time in the proposal changed since it was proposed; propose it again
WIP_REVERSAL_EXISTSThe write-off already has a reversal in progress
WIP_ADJUSTMENT_EMPTYThe chosen time has no recognisable value left to write off
TIME_ENTRY_UNVALUEDThe chosen time has no rate or no exchange rate for its day, so it cannot be valued
OVERHEAD_RATE_EXISTSAn overhead rate already starts on that date
NOTHING_TO_INVOICEThe chosen sources produce no lines
SOURCE_RESERVED, SOURCE_UNAVAILABLEAnother draft holds the work, or it was invoiced or credited since the draft was made
INVOICE_IMMUTABLE, INVOICE_ALREADY_ISSUED, INVOICE_NOT_ISSUEDThe invoice’s state refuses the change
LINE_COMPUTEDDeposit and retention lines are calculated, not edited
INVOICE_INVALIDIssue was refused; the response lists the blocking issues
PERIOD_SOFT_CLOSEDThe date falls in a soft-closed period, so a reason is required
RENDER_PENDINGThe document is still being prepared; the response gives its status and attempts
CREDIT_EXCEEDS_LINE, DEPOSIT_APPLIEDThe credit note is larger than the line, or the deposit must be reversed first
INVOICE_PAID, INVOICE_CREDITEDThe invoice cannot be voided in that state
OVER_SETTLEMENTA refund or reversal is larger than what was settled (409)
REFUND_SOURCE_ALLOCATIONEarlier refund allocations do not reconcile to cash receipts (409); no new refund is recorded
SEGREGATION_OF_DUTIESThe same person cannot both raise and settle when segregation is on

A budget or catalogue record in another organisation returns 404.

Settings routes need finance:manage_settings unless noted.

MethodPathNotes
GET/api/v1/finance/statusAny member. Returns enabled, entitled, enforcementMode, hasFinanceAccess, canManageSettings and lens; settings holders also get setupChecklist, settings, defaultBillingEntityId and suggestedJurisdiction
POST/api/v1/finance/enableTurns Finance on. Optional legalName, tradingName, jurisdictionCountry, taxRegistered, taxIdentifier, defaultCurrency, financialYearStartMonth. 201 the first time, 200 after
PATCH/api/v1/finance/settingsbudgetWarnAt (array of percentages), defaultOverrunMode (warn, block_tracking, block_approval), membersHoursOnly, segregationOfDuties, financialYearStartMonth, targetMarginBp, defaultBillingEntityId. Time policy: timeRoundingMode (none, nearest, up, down), timeRoundingMinutes (6, 15 or 30), timeApprovalDefault (required, auto), timeApprovalPolicyType (any, all), timeRequiredFields ({ note, task }), expectedHoursPerWeek, timesheetLockMode (off, weekly, monthly), timesheetLockGraceDays, timesheetLockedBefore (date or null), scheduleConversionEnabled, periodSoftCloseDay
GET, POST, PATCH/api/v1/finance/billing-entities[/:id]One entity per organisation for now. An Australian ABN is checksum validated
GET/api/v1/finance/billing-entities/:id/number-sequencesEach sequence with preview.next and preview.example
PATCH/api/v1/finance/number-sequences/:idformat, reset, separateByClient, startValue (before the first number only)
GET/api/v1/finance/number-sequences/:id/auditEvery number issued, its subject and status
GET, POST, PATCH/api/v1/finance/tax-rates[/:id] and POST .../:id/archive
GET, POST, PATCH/api/v1/finance/activity-codes[/:id], POST .../:id/archive, POST /api/v1/finance/activity-codes/seed-from-rate-cardReads need any finance capability
GET, POST, PATCH/api/v1/finance/expense-categories[/:id] and POST .../:id/archiveDisbursement categories refuse a markup
GET/api/v1/finance/billing-entities/:id/bank-accountsMasked accounts and change history
POST/api/v1/finance/billing-entities/:id/bank-accounts/changesNeeds procurement:manage_bank_details. Proposes a new account or a change
POST/api/v1/finance/bank-account-changes/:id/confirm, .../rejectA different active member with bank-details permission confirms within 24 hours
GET/api/v1/finance/bank-accounts/:id/revealNeeds procurement:manage_bank_details. Returns the full details and records an event
GET, POST, PATCH/api/v1/finance/fields[/:id] and POST .../:id/archiveCustom field definitions: entityKind, type, required, options
GET/api/v1/finance/jurisdictions/:code/rulesStatutory rules with their verified flag
GET/api/v1/finance/billing-entities/:id/periodsAccounting periods. Retained closedChecklist values follow the caller’s current Finance lens; sales amounts are omitted without organisation-wide prices access, including after reopening
GET, POST/api/v1/finance/billing-entities/:id/overhead-ratesOverhead per hour added to cost. Read needs the cost lens or settings; write needs settings: hourlyRate (minor units), currency, effectiveFrom, basis. A second rate from the same date returns 409 OVERHEAD_RATE_EXISTS
GET/api/v1/finance/eventsThe organisation’s finance audit log, redacted by lens

Approval policies for the budget change order gate use the Approvals API with rules[] and an optional billingEntityId.

Writes need finance:manage_budgets. Reads need any finance capability in a scope covering the budget’s project or client; the lens decides which figures come back.

MethodPathNotes
GET/api/v1/finance/budgetsFilters: status (comma list), clientOrganizationId (internal for none), projectId, ownerUserId, kind, q, fieldKey with fieldValue (a custom field value: text, or one option of a select or multi-select), limit, offset. Each budget carries a summary. limit is capped at 200; the response carries total, limit and offset, so page with offset rather than asking for more
GET/api/v1/finance/projects/:projectId/budgetsThe project’s budgets, unmappedTaskCount, legacyBudgetSource, legacyBudget
GET/api/v1/finance/master-projects/:masterProjectId/budgetsEvery child project’s budget with its summary, projectsWithoutBudget, and allocationLimit from the master project’s overall budget. Each budget is redacted by the lens for its own project and client, so one row can carry prices while another does not, and a budget you cannot read is left out. allocationLimit is absent without the prices lens, and the response lens is what every row has in common
POST/api/v1/finance/budgetsname; optional projectId, clientOrganizationId, kind (project, retainer, internal), currency, ownerUserId, startDate, endDate, rateCardId, templateId, warnAt, overrunMode, clientVisible
POST/api/v1/finance/projects/:projectId/budgets/convert-legacyTurns the project’s single figure into a budget with one fixed “Project fee” line
GET/api/v1/finance/budgets/:idbudget, sections, lines, totals, schedule, versions (without snapshots), fieldValues, openVersion, unmappedTaskCount
PATCH/api/v1/finance/budgets/:idHeader fields, fieldValues, and the time policy: timeApproval (inherit, auto, required), timeApprovalPolicyType (any, all), timeApprovers (array of { kind, id? } with kind budget_owner, project_owner, user or group), timeRoundingMode, timeRoundingMinutes
DELETE/api/v1/finance/budgets/:idDiscards a draft
POST/api/v1/finance/budgets/:id/activate, deliver, close, cancel, reopen, set-primary, repricereason (up to 1000 characters; non-blank for cancel and reopen). Reopen changes only closed budgets to delivered, retaining lines, amounts and version history; confirmRateCard: true on activate to confirm a pending rate card
MethodPathNotes
POST, PATCH, DELETE/api/v1/finance/budgets/:id/sections[/:sectionId]name, description, position
POST, PATCH, DELETE/api/v1/finance/budgets/:id/lines[/:lineId]See the line fields below
PUT/api/v1/finance/budgets/:id/lines/orderorder: [{ id, position, sectionId? }]

Line fields: sectionId, serviceTypeId (the activity code), name, description, billingType (time_and_materials, fixed, non_billable, percentage, expense_passthrough), unit (hour, day, item, percent), quantity, unitPrice, rateCardRoleId, rateBasis (line or member), discountBp, discountAmount, markupBp, capAmount, estimatedHours, estimatedCost, tracksTime, tracksExpenses, bookable, taxRateId, recognitionMethod, percentageBase (section or budget), percentBp, burnBasis (rate, hours, cost), warnAt, overrunMode.

Each line in a response carries the budget’s currency, its soldAmount, usedAmount, usedHours, costAmount, committedAmount, invoicedAmount, pendingAmount, pendingHours and unvaluedEntries, subject to the lens.

Example: add a time and materials line.

Terminal window
curl -X POST "https://app.runnit.io/api/v1/finance/budgets/<budgetId>/lines" \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{
"sectionId": "<sectionId>",
"serviceTypeId": "<activityCodeId>",
"name": "Content strategy",
"billingType": "time_and_materials",
"unit": "hour",
"quantity": "40",
"unitPrice": "18500",
"estimatedHours": "40"
}'
MethodPathNotes
GET, POST/api/v1/finance/budgets/:id/versionsPOST opens a change order: reason, clientReference, lines: [{ id?, remove?, ...line fields }]
PATCH/api/v1/finance/versions/:versionIdEdits a draft or approved version. Any change to approval-relevant content invalidates a live approval
POST/api/v1/finance/versions/:versionId/submitReturns outcome: "applied" or outcome: "pending_approval" with approvalRequestId. The reason comes from the version, not this body: save it with PATCH /api/v1/finance/versions/:versionId first
POST/api/v1/finance/versions/:versionId/apply, .../withdrawApply is safe to repeat: an already applied version returns unchanged. Approval starts application automatically; use apply to retry after resolving a blocker; 409 APPROVAL_STALE means the approval no longer matches the change order, so refresh and submit it again

After a budget.change_order approval closes, asynchronous processing sets the current proposal to rejected for rejection, requested changes or expiry, or withdrawn for cancellation. Its approvalRequestId remains available for reading the exact decision and comments. The live budget and current version stay unchanged; budget detail returns openVersion: null once processed. Read the approval and budget again before opening a replacement. A delayed decision cannot close a revised or resubmitted proposal. These outcomes also apply to decisions sent through MCP call_public_api. See approval decisions.

While a change order is open, send all line edits, including estimates, descriptions and position, to PATCH /api/v1/finance/versions/:versionId in lines. Direct line PATCH and line-order PUT return 409 CHANGE_ORDER_REQUIRED. Section deletion also requires withdrawing the open proposal first. Estimates accept null or non-negative values: cost has up to 18 integer minor-unit digits; hours have up to 8 whole digits and 2 decimal places. Budget alternative and template saves enforce the same limits. Read the saved version back before confirming the proposed values. They become live only when application succeeds. Estimate-only edits do not invalidate a commercial approval.

Application can return 409 CHANGE_ORDER_INPUTS_CHANGED without changing either the live budget or proposal. Review both sets of values, withdraw the version, then open a new proposal from the current budget. Older proposals with differing operational inputs can require this review even when the difference was intentional. The same rules apply through MCP call_public_api; propose_change_order still only opens a draft.

MethodPathNotes
GET, POST, PATCH, DELETE/api/v1/finance/budgets/:id/schedule[/:itemId]Items: kind (deposit, milestone, interval, final, retention), label, amount or percentBp, trigger (manual, on_date, on_milestone, on_delivery, on_approval), dueOn, projectMilestoneId. PATCH takes the same fields and clears needsReview when amount or percentBp is supplied. Every response carries the balance: scheduleBase, allocated, unallocated, balanced
POST/api/v1/finance/budgets/:id/schedule/:itemId/skipMarks an item as one that will not be invoiced. Only a scheduled or ready item can be skipped (409 otherwise); repeating it is safe. A skipped item stays in the schedule and still counts in the balance

Schedule trigger semantics: on_approval becomes ready on successful budget activation; on_delivery on delivery. Saving lifecycle items on an already active/delivered budget and reopening a closed budget reconcile missed eligible items. Responses return the saved readiness; existing missed items also recover nightly. Only scheduled items are promoted. Amounts, review flags and invoice links remain unchanged. Changing a ready item’s trigger does not reset readiness. Quote acceptance alone leaves a converted draft’s items scheduled. These effects share the lifecycle/save transaction and the generated-token REST and MCP call_public_api paths, with the same live permissions and organisation binding. No invoice is created until a caller explicitly drafts one.

Activation returns 409 BUDGET_REQUIRED_FIELDS when a live required budget field is absent, blank or an empty selection. The error names the fields; the budget remains a draft with no new version or activation event. Zero number and money values are valid. Archived or optional fields do not block activation.

A PATCH supplying a blank required value returns 400. Header edits and field values in the same PATCH save together or both remain unchanged. Activation or field saves can return 409 FINANCE_FIELDS_BUSY while another Finance field action is in progress. Retry after it finishes. These rules apply equally through MCP call_public_api.

MethodPathNotes
GET/api/v1/finance/budgets/:id/workApproved and pending minutes per line
GET/api/v1/finance/budgets/:id/lineage?figure=used&lineId=The rows behind used, cost, sold or forecast. cost needs the cost lens
GET/api/v1/finance/budgets/:id/activityFinance events for the budget, each with eventType, before, after and occurredAt

For figure=used, time rows include their recorded usedAmount in budget currency and current status, including invoiced. Display that amount directly; a billable-rate field is not required. A null amount means no valuation is available. Without price access, the amount is omitted. The same projection is available through MCP call_public_api.

MethodPathNotes
PUT/api/v1/finance/tasks/:taskId/budget-linebudgetLineId or null to unmap. The line must fund the task’s project
PUT/api/v1/finance/tasks/budget-linetaskIds[] (one project) and budgetLineId
MethodPathNotes
GET, POST, PATCH/api/v1/finance/budget-templates[/:id] and POST .../:id/archiveSections, lines with role keys, schedule pattern and thresholds

Saved template responses include read-only currency, billingEntityId and pricingSettings. Writes reject those fields and lines[].pricingTaxIdentity. Line money fields are minor-unit strings: unitPrice, discountAmount, capAmount and estimatedCost. Quantities support four decimal places.

For row edits, retain lines[].id so omitted fields keep their values. Use sections[].id and lines[].sectionId when section names repeat. Omitted collections stay unchanged; an empty array removes that collection. A section name without an ID must match exactly one section. Creation with templateId commits the complete draft or nothing. A missing active rate card role or a conflicting saved currency or billing entity returns 409. These contracts also apply through MCP’s call_public_api with the same permissions and organisation.

| POST | /api/v1/finance/budgets/:id/save-as-template | name | | GET, POST, PATCH | /api/v1/finance/catalogue[/:id] and POST .../:id/archive | Reusable priced lines |

Time routes are participation routes: they need the capability shown but not the Finance plan, so everyone can log and submit time. Acting on someone else’s time needs time:approve. Writes dated behind the timesheet lock need time:manage_locked and a lockOverrideReason; writes dated in a soft-closed period need a periodReason. Members see hours and their own billable minutes, never rates.

MethodPathCapabilityNotes
GET/api/v1/time/week?weekStart=&userId=time:logThe week: entries, the batch and its segments, minutes per day, lock flags, expectedMinutes, lockedBefore, closedPeriods, periodLocks (one row per billing entity and month covering the week, including months with nothing logged), lineEntities, defaultBillingEntityId and lens. Each entry is serialised through its own project’s lens
GET/api/v1/time/entriestime:log for your own, time:approve for othersFilters userId, projectId, budgetId, taskId, status (comma list), from, to, limit, offset
POST/api/v1/time/entriestime:logtaskId, or projectId with budgetLineId; date or startTime and endTime; minutes, note, billable, userId (others need time:approve). Returns 201
PATCH/api/v1/time/entries/:idOwner or time:approveSame fields. Editing a submitted entry withdraws its approval segment
DELETE/api/v1/time/entries/:idOwner or time:approveDraft or rejected entries only
POST/api/v1/time/timer/start, /api/v1/time/timer/:id/stoptime:logOne running timer per person in the organisation, including concurrent starts. Stopping writes minutes onto a dated entry, so it accepts periodReason
POST/api/v1/time/weeks/submittime:logweekStart (a Monday), idempotencyKey, note, entryIds. Returns the batch and segments, each with outcome approved or pending_approval and an approvalRequestId
GET/api/v1/time/approvals?status=&scope=mine&userId=&budgetId=&projectId=time:approve organisation wide, in any project or client scope, or an assignment on a time approvalSegments waiting on you by default; scope=all lists every segment and is for organisation-wide holders, everyone else is served their own assignments; budgetId and projectId narrow the list
GET/api/v1/time/approvals/:segmentIdSubmitter, time:approve or request accessThe segment, its entries, the approval request, its approvers and canDecide
POST/api/v1/time/approvals/:segmentId/decidetime:approve in a covering scopedecision, comment, entryOutcomes (array of { entryId, outcome, reason }), edits (array of { entryId, minutes?, budgetLineId?, note?, billable? }). Omit both arrays to endorse the outcome an earlier approver proposed; sending either replaces it, and everyone who already approved on the stage is asked again
POST/api/v1/time/approvals/bulktime:approvesegmentIds, decision, comment. One transaction per segment with per-segment results
POST/api/v1/time/entries/unapprovetime:approveentryIds, reason. Clears billable minutes and the approved rate and returns the entries to draft for correction; holds survive. Time behind the timesheet lock needs time:manage_locked

Completing a task (PUT /api/v1/tasks/:taskId/status) also accepts lockOverrideReason and returns a timeEntry result beside the task, because task completion records time.

These need Finance to be turned on. Queue reads need any of finance:draft_invoices, finance:manage_budgets, finance:view_invoices, finance:manage_recognition or finance:issue_invoices. Amounts follow the caller’s lens.

MethodPathNotes
GET/api/v1/finance/uninvoicedRows client, budget and line with approved uninvoiced hours and amount, ready schedule items, the oldest unbilled date and its age bucket, and a totals strip in the reporting currency covering only the rows you may see. Filters clientOrganizationId, ownerUserId, billingType, ageBucket, includeHeld, minAmount
GET/api/v1/finance/uninvoiced/budgets/:budgetIdThe review grid: every approved entry with its recognisable allocation and state (ready, held, written_off), the line rows and the budget’s write-offs
POST/api/v1/finance/uninvoiced/hold, .../release, .../transfer, .../non-billableentryIds. Hold and release take an optional reason, transfer takes budgetLineId, reprice and reason, non-billable needs reason. All are dated, so the reason doubles as the soft-close reason. Needs finance:draft_invoices, finance:manage_budgets or finance:manage_recognition
POST/api/v1/finance/uninvoiced/write-offentryIds, reason. Anyone with queue write access proposes; finance:manage_recognition posts. Returns 201 with adjustments, each with outcome proposed, posted or pending_approval
GET/api/v1/finance/wip-adjustments?budgetId=&status=Write-offs and their state
POST/api/v1/finance/wip-adjustments/:id/post, .../withdraw, .../reversePost needs finance:manage_recognition and may answer pending_approval when a policy applies; withdraw and reverse take a reason
POST/api/v1/finance/periods/ensurebillingEntityId, year, month. Creates the period row for a month with no dated activity so it can be closed. Needs finance:close_periods. Returns 201
GET/api/v1/finance/periods/:idThe period and its month-end checklist. Needs finance:close_periods, finance:manage_settings, finance:manage_recognition, finance:view_prices or finance:issue_invoices
POST/api/v1/finance/periods/:id/soft-close, .../close, .../reopenNeeds finance:close_periods. reason (required to reopen; reopening may answer pending_approval), note on close

Use kind to distinguish an invoice from a credit note. A credit note’s positive totalAmount and amountDue describe the credit document; amountDue is not a collectible balance. Issuing the credit adjusts its parent and can create a separate client-credit balance when the parent has already been paid.

Invoice-targeted refunds, client-credit applications, write-offs, disputes, reminder changes and promised-payment dates reject credit notes with 409 INVOICE_NOT_RECEIVABLE. Explicit manual payment allocations retain 409 INVOICE_NOT_OPEN for credit-note targets. Refused writes do not change financial state. Ordinary client-credit application and refund remain supported. Credit-note list rows return daysOverdue: 0 without changing stored amounts. Payment status returns eligible: false and reason: "nothing_due" for credit notes, including through get_payment_status. These rules also apply through call_public_api, with the same permissions and confirmation requirements.

For an ordinary credit, lines[].amount is a nonnegative minor-unit integer string in the original invoice’s price-entry mode. It includes tax for inclusive invoices and excludes tax for exclusive invoices. For example, selecting 5000 from an inclusive line with original total 11000, net 10000 and tax 1000 credits 5000 in total: 4545 before tax and 455 in tax. An amount selection uses quantity one and clears the original discount. Negative adjustments use a signed quantity instead.

Other non-voided credit drafts and issued credits count towards the remaining line amount. Inclusive credits must stay within both the original before-tax and tax-inclusive amounts. An excessive edited draft can be saved for correction, but cannot be issued. Creation and issue refusals return 409 without posting changes. Inclusive selections use the original line’s remaining net and each tax component, after other credit drafts and issued credits. An exact full-line selection retains its recorded tax. Partial selections divide those remaining amounts while keeping the requested gross exact; the net/tax split can differ from a fresh tax calculation. Unchanged drafts keep their allocation when a sibling is discarded. Existing issued values stay unchanged.

For a retention-release credit, each selected line keeps the original release’s recorded tax amounts. lines[].amount selects net minor units; a quantity selects that share of the release. Other non-voided credit drafts reserve both net and tax. Changing the amount recalculates its share of the remaining recorded components. An unchanged allocation stays the same if another draft is discarded.

Issuing restores only the credited net and tax to retention. A later release can use those restored amounts. Written-off retention stays written off. Changing a retention credit’s source tax is refused with 409 CREDIT_SOURCE_MISMATCH; exceeding the remaining net or tax returns 409 CREDIT_EXCEEDS_LINE without saving the change. The same rules apply through MCP call_public_api.

Invoice detail requires scoped finance:view_invoices. A matching client grant or organisation-wide grant covers the document. Project grants must cover every project on a consolidated invoice; a document without project scope needs client or organisation-wide access. Unreadable detail returns 404.

Draft header and line edits, line ordering, source additions, credit creation, void, dispute and promised-date writes need this view permission alongside their action permission. Missing view access returns 404 without saving the change. Adding sources, removing lines and creating a credit also require access to the resulting document; an unreadable result is refused without saving. The same rules apply through MCP call_public_api. These operations do not grant price access.

Figures appear only with finance:view_prices; a preview or a PDF is refused outright without it. Cost never appears on an invoice.

MethodPathCapabilityNotes
GET/api/v1/finance/invoicesany invoice capabilityview (drafts, awaiting_approval, unpaid, overdue, paid_this_month, all), kind, status, clientOrganizationId, budgetId, projectId, q, limit, offset. Returns the invoices with their days overdue and timeline, a count, totals by currency, and the lens. A scoped reader gets no totals
POST/api/v1/finance/invoicesdraft or issueComposes a draft. method is one of uninvoiced, schedule_item, percentage, custom_amount, remaining, consolidated, standalone or retention_release, with the sources for that method (budgetIds, entryIds, scheduleItemIds, holdingIds), plus header fields such as issueDate, dueDate, subject, clientPoReference, documentTemplateId, applyDeposit and retentionBp. Returns 201 with the full detail
GET/api/v1/finance/invoices/composer/candidates?budgetId=draftingWhat can go on an invoice: every approved entry with its availability and state, ready schedule items, deposits and retention holdings
GET/api/v1/finance/invoices/:idfinance:view_invoicesThe invoice, lines, sources, reservations, events, validation, whether it can be issued, warnings about a stale draft or an expiring reservation, the entity and client profile, documents, credit notes and remaining balances
PATCH, DELETE/api/v1/finance/invoices/:iddraftingEdit or discard a draft. Discarding releases its reservations and returns schedule items to ready
POST, PATCH, DELETE/api/v1/finance/invoices/:id/lines[/:lineId]draftingManual lines. Deposit and retention lines are calculated and cannot be edited
PUT/api/v1/finance/invoices/:id/lines/orderdraftingorder, listing every line once
POST/api/v1/finance/invoices/:id/sourcesdraftingAdd approved work or ready schedule items to an existing draft
GET/api/v1/finance/invoices/:id/validationanyThe blocking and non-blocking issues, and whether the invoice can be issued
GET/api/v1/finance/invoices/:id/previewany, with pricesComplete styled HTML; current draft or saved issued document
POST/api/v1/finance/invoices/:id/request-approvaldraftingAsk for approval without issuing
POST/api/v1/finance/invoices/:id/issuefinance:issue_invoicesidempotencyKey, and a reason when the period is soft closed. Returns the invoice and an outcome of issued, pending_approval or already_issued
POST/api/v1/finance/invoices/:id/sendfinance:issue_invoicesmode (email, mark_sent, schedule), recipients, cc, replyTo, scheduledFor, message. Returns a job id
GET/api/v1/finance/invoices/:id/pdfany, with pricesThe frozen bytes of an issued document
GET/api/v1/finance/invoices/:id/documentsanyEvery version with its status, checksum, size, attempts and template version, plus the send jobs
POST/api/v1/finance/invoices/:id/documents/:documentId/retryfinance:issue_invoicesRe-queue a failed render
GET, POST/api/v1/finance/invoices/:id/link, .../link/rotate, .../link/revokefinance:issue_invoicesMint, rotate or revoke the client’s hosted link
POST/api/v1/finance/invoices/:id/credit-notesfinance:issue_invoicesreason, lines[{ lineId, quantity?, amount? }] (all lines when omitted), and sourceMode reissue (the work returns to the queue) or write_off
POST/api/v1/finance/invoices/:id/void, .../write-off, .../dispute, .../reminders, .../promiseissue, record payments or manage receivablesVoid and write off take a reason; dispute pauses reminders; promise records a promised date
POST/api/v1/finance/invoices/bulkfinance:issue_invoicesaction (issue, send, apply_sequence) over ids. One transaction per invoice, with a per-invoice result

Approved time composition and adding sources preserve the approved net amounts. A single-rate group uses hours only when its four-decimal quantity and rate produce that exact net amount. Otherwise it uses unit: "item", quantity: "1.0000" and the approved net unitPrice, with minutes in its description. Sources retain their minutes and rates, and tax groups remain separate. REST, call_public_api and draft_invoice share this behaviour. Explicit draft price edits still apply. Discard and recompose an older incorrect draft before issuing; existing issued invoices remain unchanged.

Credit-note creation includes signed adjustments when you omit lines. Explicit quantity values must retain the original quantity’s sign and fit its magnitude. Duplicate lineId values, wrong signs and quantities rounded to zero are refused. amount stays a nonnegative minor-unit string; use signed quantity for a negative-net line. A credit cannot have a negative total or exceed the remaining invoice or signed line amounts. Other non-voided credit drafts count towards the line limit; discard unused drafts before retrying.

Parented credit lines must retain their source binding and tax treatment, and the credit must retain the original price-entry and rounding settings. Draft edits that break these rules block issue. The response validation identifies CREDIT_EXCEEDS_LINE, CREDIT_EXCEEDS_INVOICE, NEGATIVE_TOTAL or CREDIT_SOURCE_MISMATCH. REST and MCP share these checks and settlement effects.

Credits and voids update both positive and negative on-invoice revenue amounts on the affected budget lines. Successive partial credits apply only the remaining credited share.

MethodPathCapabilityNotes
GET/api/v1/finance/paymentsany invoice capabilityFilters by client, invoice, kind and date range
POST/api/v1/finance/paymentsfinance:record_paymentsreceivedOn, amount, currency, clientOrganizationId, billingEntityId, method, reference, and allocations (oldest open invoice first when omitted). Any unallocated remainder becomes a client credit. Returns 201
GET/api/v1/finance/invoices/:id/paymentsanyAllocations, payments, per-line allocations and credit applications
POST/api/v1/finance/payments/refundsfinance:record_paymentsRefund against an invoice or a client credit
GET, POST/api/v1/finance/client-credits, .../:id/applyany, record paymentsList open credits and apply one to an invoice

A manual invoice refund can return several allocations for one payment. Each reversesId identifies its original cash receipt allocation. reversesPaymentId on the refund is populated when there is one source payment. Credit applications and write-offs cannot fund a cash refund. Refunds use remaining receipts in reverse date order. The invoice lock protects concurrent refunds. REST and MCP share these results, permissions and 409 failures.

Allocations are made against each line’s remaining balance and each tax component, never the original totals, so repeated part payments settle exactly. Every valuation is stored with the rate that applied on its own economic date, so a later rate change never restates a settled figure.

MethodPathCapabilityNotes
GET/api/v1/finance/receivablesany invoice capabilityasOf, basis (due_date or issue_date), and optional client and entity filters. Returns the ageing per client with buckets, holds, disputes, promised dates and available credit, rollups by entity, the invoices with their bucket, and the average days to pay
POST/api/v1/finance/receivables/clients/:clientId/holdfinance:manage_receivablesPut a client on hold or release them, with a reason
GET, POST/api/v1/finance/statementsread any, write manage receivablesGenerate a statement for a client and a date range
GET/api/v1/finance/statements/:id, .../pdfany, PDF needs pricesThe statement and its document
POST/api/v1/finance/statements/:id/sendfinance:manage_receivablesrecipients, cc, message
GET, POST, PATCH/api/v1/finance/reminder-sequences[/:id]read any, write manage receivablesA named sequence of steps with offsets in days, a channel and an escalation target, and the conditions that stop it

Billing profiles, templates and statutory rules

Section titled “Billing profiles, templates and statutory rules”
MethodPathCapabilityNotes
GET/api/v1/finance/billing-profiles, /api/v1/finance/clients/:clientId/billing-profileany invoice capabilityThe client’s billing contact, terms, credit limit, purchase order requirement, tax treatment and document defaults. Internal notes need finance:manage_settings
PATCH/api/v1/finance/clients/:clientId/billing-profilefinance:manage_settingsThe same fields, plus onHold and the reminder sequence
GET, POST, PATCH/api/v1/finance/document-templates[/:id] and .../archiveread any, write settings or integrationsTemplates are an ordered list of blocks with options. System templates are read-only; copy one to change it
GET/api/v1/finance/document-templates/:id/previewany, prices when an invoice is namedHTML, with sample data when no invoice is given
GET, POST, PATCH/api/v1/finance/document-styles[/:id]read any, write settings or integrationsFont, accent colour, logo size, paper size and density
POST/api/v1/finance/statutory-rules/confirmfinance:manage_settingsConfirms the statutory rules that ship unverified for a jurisdiction, which is what unblocks issuing for an Australian entity

The purchase side is what the organisation spends: claims from people, suppliers, purchase orders, supplier bills, and the runs that pay people back. See the Expenses, Purchasing and Paying suppliers guides for the product behaviour.

Reading and submitting your own claims is a participation lane, like logging time: it needs Finance turned on and any of expense:submit, expense:view_all, expense:approve or expense:manage, but not a Finance seat. Everything else in this section needs the Finance plan. Your own claim always carries its amounts and its receipt whatever your lens says, and somebody else’s claim answers 404 without expense:view_all, expense:approve or expense:manage.

Purchase reads take any of procurement:manage_pos, procurement:approve_pos, procurement:manage_bills, procurement:manage_suppliers or expense:manage. The tables below call that any purchase capability.

An expense held by an invoice draft or still included in an issued invoice cannot be unapproved or moved to another budget line. These operations return 409 EXPENSE_INVOICED and keep approval, allocation and budget values unchanged. Release the draft reservation or fully credit the expense for reissue first. A partial credit or a write-off credit does not unlock corrections. List and detail billing statuses update when the related invoice is issued. The same rules apply through generated-token REST and MCP. If a historical claim is non-approved but still invoiced, editing, submitting or deleting it also returns 409 EXPENSE_INVOICED; resolve the invoice first.

receiptAssetId must identify a live, readable uploaded file in the same organisation. Missing, foreign, inaccessible, remote and protected Finance sources return 404. Upload an existing cloud file again as a receipt; a generic cloud file cannot be attached while its original public address could still work. Receipt file access follows current expense permissions across file reads, search and downloads. See private expense receipts.

MethodPathCapabilityNotes
GET/api/v1/finance/expensesany expense capabilityview (mine, awaiting_my_approval, unallocated, unreimbursed, billable_uninvoiced, this_month, all), projectId, budgetId, supplierId, categoryId, submitterUserId, from, to, q, limit, offset. Without expense:view_all, expense:approve or expense:manage the list is your own. totals appears only with the cost lens
GET/api/v1/finance/expenses/:idany expense capabilityThe claim, its lines, its policy checks and its extraction runs
POST/api/v1/finance/expensesany expense capabilityexpenseCategoryId and expenseDate are required, then amount, currency, entryMode, merchantName, supplierId, projectId, taskId, budgetLineId or splits, taxRateId, billable, markupKind, markupBp, markupAmount, paidWith, cardReference, description, note, mileageDistance, mileageVehicleType, perDiemDays, perDiemSalaryBand, perDiemLocation, receiptAssetId, receiptHash. Returns 201
PATCH/api/v1/finance/expenses/:idown claim, else expense:manage or expense:approveAny create field, plus reason when the date falls in a soft-closed period. 409 EXPENSE_APPROVED once it is approved
DELETE/api/v1/finance/expenses/:idown claim, else expense:manageDrafts only. Returns 204
POST/api/v1/finance/expenses/:id/submitown claim, else expense:managereason. Returns an outcome of approved, pending_approval or already_submitted. 409 PURCHASE_INVALID carries the blocking policy findings
POST/api/v1/finance/expenses/:id/decisionexpense:approve or expense:managedecision (approve, reject), reason. 409 EXPENSE_STATE or APPROVAL_PENDING, and 403 when segregation of duties refuses the submitter
POST/api/v1/finance/expenses/bulk-decisionexpense:approve or expense:manageexpenseIds, decision, reason. One record at a time, with a result for each
POST/api/v1/finance/expenses/:id/unapproveexpense:approve or expense:managereason is required. 409 EXPENSE_STATE, EXPENSE_INVOICED or EXPENSE_IN_RUN
POST/api/v1/finance/expenses/:id/reallocateexpense:approve or expense:managelineId, budgetLineId, reason. 409 EXPENSE_INVOICED
POST/api/v1/finance/expenses/:id/settle-externallyexpense:managereference. 409 EXPENSE_STATE, EXPENSE_NOT_PERSONAL or EXPENSE_IN_RUN
POST/api/v1/finance/expenses/:id/extractionown claim, else expense:manageassetId, assetVersion. 202 when a run started, 200 when one was already claimed for the same receipt
GET/api/v1/finance/expenses/:id/extractionsany expense capabilityThe runs with their status, proposals, confidence and corrections
POST/api/v1/finance/extractions/:id/applyany expense capabilityfields. Applies only the fields nobody has corrected, and returns applied and skipped
GET/api/v1/finance/expenses/:id/policy-checksany expense capabilityThe notes, each with a code, a severity and a state

Expense categories are in the settings table above. They also accept agencyTreatment, defaultMarkupKind, defaultMarkupAmount, mileageVehicleType and mileageUnit. A disbursement is an agency payment, so agencyTreatment defaults to the disbursement flag when you leave it out, and sending disbursement: true with agencyTreatment: false is a 400.

MethodPathCapabilityNotes
GET/api/v1/finance/suppliers[/:id]any purchase capability or finance:manage_settingsList filters: q, includeArchived, contractorsOnly, limit. The detail adds contacts and the freelancer link. List spendYearByCurrency needs complete cost access
POST, PATCH/api/v1/finance/suppliers[/:id]procurement:manage_suppliers or finance:manage_settingsname is required, then legalName, code, taxIdentifier, taxIdentifierKind, gstRegistered, statementBySupplier, defaultCurrency, paymentTerms, defaultExpenseCategoryId, defaultTaxRateId, isInternal, internalClientOrganizationId, internalBudgetId, clientOrganizationId, address, notes. 400 when an ABN fails its checksum
POST/api/v1/finance/suppliers/:id/archiveprocurement:manage_suppliers or finance:manage_settingsarchived (true by default). 409 while open purchase orders or bills remain
POST, DELETE/api/v1/finance/suppliers/:id/contacts[/:contactId]procurement:manage_suppliers or finance:manage_settingsname is required, then email, phone, role, isPrimary. 201 on create, 204 on delete
POST/api/v1/finance/suppliers/backfill-freelancersfinance:manage_settingsLinks every freelancer membership that has no live supplier. Safe to repeat; returns scanned, created, linked and skipped
GET/api/v1/finance/bank-accounts/:ownerKind/:ownerIdprocurement:manage_bank_detailsownerKind is billing_entity, supplier or employee (a membership id). Masked identifiers only
POST/api/v1/finance/bank-accounts/:ownerKind/:ownerId/changesprocurement:manage_bank_detailslabel, country, accountHolder, currency, identifiers, isDefault, bankAccountId. Returns 201, and a different person with bank details permission has to confirm it within 24 hours through the confirm and reject routes in the settings table. 409 BANK_CHANGE_PENDING when one is already waiting

For an employee reimbursement account, use the claim’s payeeMembershipId as ownerId and set isDefault:true. A user ID is not a membership ID. Confirmation makes the account available for future reimbursement selection. Pending changes block eligibility; existing run items retain their frozen account version.

Without the cost lens a purchase order or a supplier bill comes back with its quantities, unit prices, markups and totals removed rather than set to null. A reader who has prices but not cost still sees a line’s sell total, and nothing that would reconstruct the cost behind it.

With cost access, order and bill lists, details and mutation responses retain supplier unit prices, tax, totals, receipt and payment values, and retained cost versions even without finance:view_prices. The matching preview follows the same rule after checking access to the bill and its referenced orders. Selling totals still require prices; markups require both cost and prices. These rules also apply to raise_po, draft_supplier_bill, list_supplier_bills and call_public_api. Existing money shapes are unchanged.

MethodPathCapabilityNotes
GET/api/v1/finance/purchase-ordersany purchase capabilityview (open, awaiting_approval, awaiting_bills, over_received, closed, all), supplierId, projectId, budgetId, q, limit
GET/api/v1/finance/purchase-orders/:idany purchase capabilityThe order, its lines, versions, receipts, reversals and policy checks. pendingRevision is null or { baseVersion, approvalRequestId }. totals is absent without the cost lens
POST/api/v1/finance/purchase-ordersprocurement:manage_possupplierId is required, then projectId, currency, orderDate, expectedDelivery, termsText, note, quoteReference, quoteAmount, quoteValidUntil, quoteAssetId, documentTemplateId and lines (description, activityCode, budgetLineId, quantity, unit, unitPrice, taxRateId, billable, markupKind, markupBp, markupAmount, sellAmount). Returns 201
PATCH, DELETE/api/v1/finance/purchase-orders/:idprocurement:manage_posDrafts only, before the order is issued. 409 PO_ISSUED, and 204 on delete
POST/api/v1/finance/purchase-orders/:id/issueprocurement:manage_posidempotencyKey (or the Idempotency-Key header) and reason. Returns an outcome of issued, pending_approval or already_issued. 409 PO_ALREADY_ISSUED, PO_STATE, APPROVAL_STALE or PURCHASE_INVALID
POST/api/v1/finance/purchase-orders/:id/reviseprocurement:manage_posbaseVersion, lines (give a line’s id to keep it), changeNote, expectedDelivery, termsText, note. 409 PO_VERSION_STALE, PO_REVISION_BELOW_CONSUMED, APPROVAL_STALE or PO_STATE
POST/api/v1/finance/purchase-orders/:id/cancel, .../closeprocurement:manage_posCancel needs a reason and takes lineIds to cancel part of the order. Close releases the remaining commitment
POST/api/v1/finance/purchase-orders/:id/receipts, .../receipts/:receiptId/reverseprocurement:manage_posReceive lineId, quantity, receivedOn, note (201); reverse quantity, reversedOn, reason. 409 RECEIPT_REVERSAL_OVER

A revision cannot reduce or remove a line below the greater of its net received quantity and its quantity on-charged on issued invoices. The refusal returns 409 with code PO_REVISION_BELOW_CONSUMED and the message A line cannot go below what has already been received or on-charged. It leaves the order unchanged and does not include the consumed quantity or line description in the error. This applies through both the API and call_public_api.

MethodPathCapabilityNotes
GET/api/v1/finance/supplier-billsany purchase capabilityview (to_approve, unpaid, due_this_week, overdue, disputed, all), supplierId, projectId, budgetId, q
GET/api/v1/finance/supplier-bills/ageingany purchase capability, with costBuckets current, 1_30, 31_60, 61_90 and 90_plus, with internal suppliers left out. 403 without the cost lens
GET/api/v1/finance/supplier-bills/:idany purchase capabilityThe bill, its lines, its checks and its payments. The match snapshot and the payments need the cost lens
POST/api/v1/finance/supplier-billsprocurement:manage_billssupplierId is required, then supplierInvoiceNumber, projectId, currency, billDate, dueDate, entryMode, note, attachmentAssetId, attachmentHash and lines (description, purchaseOrderLineId, expenseCategoryId, budgetLineId, quantity, amount, taxRateId, billable, markupKind, markupBp, markupAmount). Returns 201
PATCH, DELETE/api/v1/finance/supplier-bills/:idprocurement:manage_billsApproved, paid and void bills refuse edits. Delete requires a draft and returns 204. Retained manual invoice attachments use the separate upload action below.
GET/api/v1/finance/supplier-bills/:id/matchany purchase capability, with costThe three-way match line by line. 409 MATCH_BOUNDARY when the bill reaches across suppliers, currencies or billing entities
POST/api/v1/finance/supplier-bills/:id/submitprocurement:manage_billsreason. Returns an outcome of approved, pending_approval or already_approved
POST/api/v1/finance/supplier-bills/:id/dispute, .../voidprocurement:manage_billsDispute takes disputed and note; void needs a reason and takes voidedOn. 409 BILL_SETTLED
GET/api/v1/finance/supplier-payments/payableprocurement:manage_bills or expense:manage, with costbillingEntityId, currency, dueBy. Each row reports bankPending and hasBankAccount. 403 without the cost lens
POST/api/v1/finance/supplier-paymentsprocurement:manage_bills or expense:manageamount is required, then paidOn, method, reference, note and either billIds or allocations. Returns 201. 409 SUPPLIER_INTERNAL, BILL_STATE or BILL_DISPUTED
POST/api/v1/finance/supplier-payments/:id/reverseprocurement:manage_bills or expense:managereason is required, and reversedOn is optional
MethodPathCapabilityNotes
GET/api/v1/finance/reimbursement-runs/eligibleexpense:managebillingEntityId, currency. Someone who cannot be paid is listed with a reason rather than hidden
GET/api/v1/finance/reimbursement-runs[/:id]expense:managestatus on the list. The detail carries the run, its items and the payment-file history (identities only)
POST/api/v1/finance/reimbursement-runsexpense:manageexpenseIds is required, then billingEntityId, currency, paymentDate, note. 409 EXPENSE_NOT_ELIGIBLE or EXPENSE_IN_RUN
DELETE/api/v1/finance/reimbursement-runs/:id/items/:itemIdexpense:manageTakes an item out while the run is still being selected. 409 RUN_STATE
POST/api/v1/finance/reimbursement-runs/:id/cancelexpense:managereason is required. 409 RUN_STATE
POST/api/v1/finance/reimbursement-runs/:id/exportexpense:manage and procurement:manage_bank_detailsformat (aba by default, or csv). Returns the file identity, a downloadUrl and every skipped item with its reason. 409 RUN_STATE, RUN_EMPTY, ABA_CURRENCY, ABA_ORIGINATOR_MISSING and the other ABA_ codes
GET/api/v1/finance/payment-files/:tokenprocurement:manage_bank_detailsThe file bytes, once. A second attempt is 410 DOWNLOAD_SPENT, and a run that changed since the export answers 409 PAYMENT_FILE_STALE, so export it again
POST/api/v1/finance/reimbursement-runs/:id/confirmexpense:managereference, paidOn. Marks the expenses reimbursed. 409 RUN_STATE or RUN_EMPTY
POST/api/v1/finance/reimbursement-runs/:id/reverse-confirmationexpense:manageRequired reason; optional reversedOn and expectedSettlementId. Returns the exported run and dated signed settlement history. Rejects a linked bank match, a stale confirmation, missing original evidence or a date before payment

Reimbursement CSV downloads contain masked accounts and deterministic row order. The stored hash must match the rebuilt contents. Changed contents return 409 PAYMENT_FILE_STALE without consuming the link; used or revoked links return 410 DOWNLOAD_SPENT. Export again for a fresh link when needed. Downloading does not confirm payment. Generated REST and MCP call_public_api use the same checks; the MCP bridge returns CSV as text.

Accruals, and purchases as invoice sources

Section titled “Accruals, and purchases as invoice sources”
MethodPathCapabilityNotes
GET/api/v1/finance/accruals/periods/:periodIdany purchase capability, with costcurrency. Returns the stored balances beside the computed ledger, so the two can be compared
GET/api/v1/finance/accruals/pass-throughany purchase capability, with costprojectId, supplierId. What was on-charged against what the supplier billed, per purchase order line
GET/api/v1/finance/budgets/:budgetId/purchase-candidatesfinance:draft_invoices, finance:view_invoices or any purchase capability, with pricesexcludeDraftId. Refused with 403 without the prices lens, and costAmount needs the cost lens
POST/api/v1/finance/invoices/:id/purchase-sourcesfinance:draft_invoices or finance:issue_invoicesbudgetId is required, then expenseIds, purchaseOrderLineIds, supplierBillLineIds. 409 SOURCE_RESERVED or SOURCE_UNAVAILABLE

Quotes are the sold side before a budget exists, and retainers are the repeating agreements that fund work period by period. See the Quotes and Retainers guides for the product behaviour.

Reads take any of finance:manage_quotes, finance:send_quotes or finance:approve_quotes, which the tables call any quote capability. A quote outside your scope answers 404, never 403. Money appears only with the prices lens, and the pipeline is refused outright with 403 without it, because it is nothing but money.

The composition notes field is staff-only. It is retained for authorised quote readers and omitted from all client document HTML and PDFs. An older quote PDF that included notes is redacted when downloaded or attached to an email. Its number, version, commercial content and signer evidence stay unchanged. Rendering failure prevents delivery of that copy; it never falls back to the older file. Previously delivered copies cannot be recalled.

MethodPathCapabilityNotes
GET/api/v1/finance/quotesany quote capabilitystatus (comma list), clientOrganizationId, projectId, open, q, limit, offset
GET/api/v1/finance/quotes/pipelineany quote capability, with pricesclientOrganizationId. All open quotes at recorded reporting values, with missing valuation counted and native amounts grouped in unvalued.byCurrency
GET/api/v1/finance/quotes/lost-reasonsany quote capabilityThe organisation’s configurable list
POST/api/v1/finance/quotesfinance:manage_quotesclientOrganizationId and title are required, then currency, projectId, billingEntityId, validUntil, expectedCloseDate, probabilityBp, termsText, scopeText
GET/api/v1/finance/quotes/:idany quote capabilityversionId reads an earlier version
PATCH/api/v1/finance/quotes/:idfinance:manage_quotesHeader fields. 409 QUOTE_NOT_EDITABLE once the quote has been sent
PATCH/api/v1/finance/quotes/:id/compositionfinance:manage_quotescoverText, scopeText, assumptionsText, exclusionsText, notes, termsText, documentLanguage, priceEntryMode, roundingMode, selectionLocked
POST/api/v1/finance/quotes/:id/clonefinance:manage_quotesversionId, title
POST, PATCH, DELETE/api/v1/finance/quotes/:id/sections[/:sectionId]finance:manage_quotesname is required on create, then description, position, clientVisibility, packageGroup
POST, PATCH, DELETE/api/v1/finance/quotes/:id/lines[/:lineId]finance:manage_quotesserviceTypeId and name are required on create. The budget line fields, plus optional, packageGroup, requiredInPackage, selectedByDefault and hiddenContingency
PUT/api/v1/finance/quotes/:id/schedulefinance:manage_quotesitems, each with kind, label, either amount or percentBp, trigger, dueOn, milestoneRef and lineAllocation
POST/api/v1/finance/quotes/:id/request-approvalfinance:send_quotesReturns outcome and approvalRequestId
POST/api/v1/finance/quotes/:id/sendfinance:send_quotesrecipients, cc, message. Returns outcome, approvalRequestId, hostedUrl and version. 409 ENVELOPE_TOO_WIDE when the selectable options make too many combinations to enumerate, so an approver could not see the worst case: lock the version’s selection or cut the optional items back. Also 409 APPROVAL_STALE or QUOTE_STATE, and 400 QUOTE_INVALID with details.issues
POST/api/v1/finance/quotes/:id/resendfinance:send_quotesrecipients, cc, message. Returns the hostedUrl for the version already sent
POST/api/v1/finance/quotes/:id/revise, .../withdrawfinance:send_quotesBoth take a reason. 409 REVISION_OPEN when a revision is already open
POST/api/v1/finance/quotes/:id/lost-reasonfinance:manage_quoteslostReasonId, note
POST, DELETE/api/v1/finance/quotes/:id/link, .../linksfinance:send_quotesMint a fresh hosted link, or revoke every live one. 409 before the quote has been sent
POST/api/v1/finance/quotes/:id/convertfinance:manage_budgets, plus project:create or project:update when a project is involvedmode (new_project, existing_project, standalone), projectId, projectName, budgetName, overrideReason. 201, or 200 when it was already converted. 409 QUOTE_NOT_ACCEPTED
POST/api/v1/finance/quotes/price-brieffinance:manage_quotesbriefJobId is required, then projectId, title, validUntil, expectedPlanRevision, expenseAllowances. 201, or 200 when that plan revision was already priced. 409 PLAN_REVISION_STALE. Roles with no rate come back in unpricedRoles rather than being priced at a guess
GET/api/v1/finance/quotes/:id/lineageany quote capabilityversionId. The per-task sources behind the price, with role and phase aggregates

Change orders sent to the client, and the terms library

Section titled “Change orders sent to the client, and the terms library”
MethodPathCapabilityNotes
POST/api/v1/finance/budgets/:budgetId/change-orders/:versionId/sendfinance:manage_budgetsrecipients, cc, message. Returns hostedUrl and requiresClientAcceptance
POST/api/v1/finance/budgets/:budgetId/change-orders/:versionId/cancel-acceptancefinance:manage_budgetsreason. Revokes the link and lifts the acceptance requirement
GET/api/v1/finance/budgets/:budgetId/change-orders/:versionId/acceptancefinance:manage_budgetsrequiresClientAcceptance, acceptedAt, declinedAt
GET/api/v1/finance/termsany quote capabilitykind, clientOrganizationId
POST, PATCH, DELETE/api/v1/finance/terms[/:id]finance:manage_settingsname and body are required on create, then kind, clientOrganizationId, billingEntityId, isDefault. Editing the body bumps the version, and DELETE archives

Applying a version that is marked as needing client acceptance before the client has accepted answers 409 CLIENT_ACCEPTANCE_REQUIRED on every path. A reduction below what has already been recognised or invoiced answers 409 REDUCTION_BELOW_POSTED, and a closed accounting period answers 409 PERIOD_CLOSED.

New retainers default trueUp to true when overuseTreatment is true_up. Explicit false and existing settings are preserved. trueUp must be a JSON boolean. overageRateAmount accepts null or a nonnegative minor-unit string up to 9223372036854775807; malformed values return 400. Explicit true-up with another recovery treatment also returns 400.

After any consumption window closes, changing trueUp, overuseTreatment, overagePolicy or overageRateAmount returns 409. Unchanged values and unrelated edits remain allowed. Before close, selecting another recovery treatment without a trueUp value turns true-up off.

True-up requires billable overage terms, true-up recovery, an enabled flag and a closed, non-cancelled period with a positive trueUpAmount. The period fee and true-up can be invoiced separately. Period-end policies find an outstanding true-up even after the fee was invoiced. Read reason on successful results: it explains when requested excess was skipped. These rules apply equally to REST and MCP. Review older missed charges before making a separate corrective invoice; saved opt-outs and issued invoices are not changed automatically.

Use PATCH /api/v1/finance/clients/:clientId/billing-profile with finance:manage_settings to change portalEnabled, allowChangeOrderAcceptance and showBudgetConsumption. Send booleans; null, strings, numbers, arrays and objects are rejected with 400 and leave the profile unchanged. Omitted values are kept. Turning the portal off keeps the other settings and saved documents. The client still needs the relevant portal permission in their own organisation.

Read back the saved profile to confirm a change. The individual profile GET creates default settings when a valid client has no profile yet; it does not enable access. List and detail reads include the flags. A visible creditLimit is a minor-unit string or null; it is omitted from reads and PATCH responses when the caller cannot see it.

The same change is available through MCP’s call_public_api using PATCH /finance/clients/:clientId/billing-profile. The named get_client_billing_profile reads the same flags. See Client Portal Billing for the manual setup.

MethodPathCapabilityNotes
GET/api/v1/finance/retainersfinance:manage_retainers or finance:manage_budgetsstatus, clientOrganizationId
POST/api/v1/finance/retainersfinance:manage_retainersclientOrganizationId, name, cadence and anchorDate are required, then startDate, endDate, templateBudgetId, generateAheadPeriods, rolloverPolicy, rolloverCapHours, rolloverCapAmount, rolloverExpiryPeriods, overagePolicy, overageRateAmount, overuseTreatment, invoicePolicy, invoiceDayOffset, trueUp, allowAutoIssue
GET/api/v1/finance/retainers/:idfinance:manage_retainers or finance:manage_budgetsThe series, its period strip with regenerable per period, the next period start and the outstanding carry
PATCH/api/v1/finance/retainers/:idfinance:manage_retainersHeader fields. The cadence and the anchor cannot change once periods exist: that answers 409, and the way forward is to end the retainer and start a new one
POST/api/v1/finance/retainers/:id/generatefinance:manage_retainersCatches up any periods that are due. Returns created and skipped, and is safe to repeat
POST/api/v1/finance/retainers/:id/pause, .../resumefinance:manage_retainersPause takes a reason and records the resume point; resume catches up the periods that were missed
POST/api/v1/finance/retainers/:id/endfinance:manage_retainerssettlement is required (invoice_outstanding, forfeit_carry, credit_carry), and endDate is optional
POST/api/v1/finance/retainers/:id/apply-to-futurefinance:manage_retainersReturns regenerated and, for each period it left alone, a skipped entry with its periodIndex and reason. Recorded time, manual adjustments, reserved invoice work and financial period locks prevent rebuilding; existing carry is retained
GET/api/v1/finance/retainer-periods/:periodIdfinance:manage_retainers or finance:manage_budgetsConsumption for the period: allowance, used, included and excess per line with its money, the breakdown by project, and what is still outstanding
POST/api/v1/finance/retainer-periods/:periodId/close-windowfinance:manage_retainersreason. 409 PERIOD_CLOSED
POST/api/v1/finance/retainer-periods/:periodId/adjustments, /api/v1/finance/retainer-adjustments/:adjustmentId/reversefinance:manage_retainerskind and reason are required on an adjustment, then quantity and amount. A reversal needs a reason
POST/api/v1/finance/retainer-periods/:periodId/invoicefinance:draft_invoicesincludeFee, includeTrueUp, runPolicy. Returns outcome (drafted, issued, pending_approval, already_invoiced, nothing_to_invoice or not_due), a reason and the invoice

Mid-period proration and termination adjustments answer 501 rather than guessing a figure.

Every finance figure comes from one metric service, so a number in a report, a dashboard, an export and an MCP answer is the same number with the same published definition. See the Revenue, Forecast, Finance reports and Month end guides for the product behaviour.

Reads in this section take any of finance:view_prices, finance:view_cost, finance:view_profit, finance:manage_recognition or finance:close_periods. The tables call that any figures capability.

Scope goes into the query rather than on top of the answer: a scoped total is computed from scoped rows. If your only grant is a project or client scope, name the subject you are asking about with projectId or clientOrganizationId. Without a subject your organisation-wide lens applies, which for a scoped-only holder carries no money, so the money metrics are absent from the result.

Money comes back on a metric cell shaped { metric, unit, amount, decimal, currency, unvalued }: amount is minor units in the reporting currency, decimal is major units for display and export, and unvalued counts the rows that had no frozen reporting value and were left out of the total rather than counted as zero. A figure your lens does not cover is absent, not null.

invoiced_revenue includes issued standalone and manual invoice lines, less issued credit notes, excluding tax and dated by issue date. It uses recorded reporting values. Missing budget/project attribution is unallocated rather than excluded from organisation or client totals; a specific dimension filter still requires a matching link. Saved reports, exports and MCP reads use this definition.

The forecast omits currencies[].overdue without prices access. Cost-only reads retain permitted estimate-at-completion costs, while cash, revenue and profit amounts remain absent. Handle a missing summary without interpreting it as zero. Generated REST and MCP call_public_api return the same projection.

Each POST /api/v1/finance/revenue/runs evaluates current approved work and method inputs, including changes made after an earlier run on the same day. It returns a new run with current coverage and posts only the difference from previously recognised revenue. Unchanged or concurrent repeats add no duplicate revenue. A completed run also saves budget and line recognisedAmount and reportingRecognisedAmount for subsequent authorised budget reads. Zero-delta and partial runs update these totals from retained entries, including archived line history. Missing frozen reporting values remain null; unrelated budget amounts are unchanged. Failed runs commit neither new entries nor changed totals. The route accepts no retry key. MCP call_public_api uses the same operation with the same permissions and validation.

MethodPathCapabilityNotes
GET/api/v1/finance/metrics/catalogueany figures capabilityEvery metric you may read, with its published definition, unit, groupings, dating and report link. The list shrinks with your lens
GET/api/v1/finance/metricsany figures capabilitymetrics (comma list), from, to, groupBy, billingEntityId, clientOrganizationId, projectId, budgetId, serviceTypeId, revenueLens (recognised, invoiced or cash). Returns reportingCurrency, from, to, groupBy, rows, totals, coverage, definitions and dating, which says per metric whether it was bucketed by the economic period (recognition) or the posting date (invoicing, cash and cost). An unknown metric is a 400, and so is a grouping the metric does not support
GET/api/v1/finance/revenueany figures capabilityRecognised, invoiced, WIP and deferred over a range, grouped
GET/api/v1/finance/revenue/runs[/:id]any figures capabilitybillingEntityId, periodId, limit on the list. The detail carries the run’s frozen inputs, its coverage failures and its entries
POST/api/v1/finance/recognition/catch-upfinance:manage_recognitionHistorical approved time; optional reason (up to 2,000 characters) for soft-closed source months. Closed months refuse. Returns { posted, scanned }
POST/api/v1/finance/revenue/runsfinance:manage_recognitionbillingEntityId and period (YYYY-MM), with an optional budgetId. Returns 201 with the run, its entries and its coverage. 409 RECOGNITION_RUN when every period from the economic one onward is closed
GET/api/v1/finance/revenue/lines/:lineId/explainany figures capabilityThe source rows, their frozen exchange rates, the method formula and basis, the runs that wrote them, the versioned method inputs and a fingerprint. Add since to get changedSince
PATCH/api/v1/finance/revenue/lines/:lineId/method-inputsfinance:manage_recognitioneffectiveFrom, inputs, reason. 400 when a manual distribution does not add up to sold
MethodPathCapabilityNotes
GET/api/v1/finance/wipany figures capabilityasOf, movementFrom, and entity, client, project and budget filters. Balances with ageing, rollups by budget, client, entity, service type and project manager, the top contributors and the monthly movement
GET/api/v1/finance/forecastany figures capabilityasOf, horizonMonths. One view per currency: committed, committedWeekly, pipeline, pipelineWeekly, cash and cashWeekly, plus estimateAtCompletion, forecastUtilisation, revenuePerPerson and lateness
GET/api/v1/finance/operating-costsfinance:view_cost or finance:manage_settingsThe operating cost register for a billing entity
POST, PATCH/api/v1/finance/operating-costs[/:id]finance:manage_settingsarchived: true archives, archived: false restores
GET, POST/api/v1/finance/overhead/proposalsread finance:view_cost or finance:manage_settings, write finance:manage_settingsbillingEntityId, from and to are required, and billableCapacityHours is optional. A proposal is computed, not stored
POST/api/v1/finance/overhead/proposals/:id/publishfinance:manage_settings:id is the computation key, so send billingEntityId, from and to again: publishing recomputes under a lock. 201 the first time, 200 with created: false and the same rate on a repeat
GET, POST/api/v1/finance/journalread finance:view_prices or finance:close_periods, write finance:manage_recognitionperiodId and billingEntityId on the read. POST takes periodId, recognitionRunId, destination (csv or xero) and currencies, and builds the journal against the period’s run and close generation
POST/api/v1/finance/journal/:id/exportfinance:manage_integrationsValidates first: 409 JOURNAL_INVALID with the validation block when an account mapping is missing. A csv journal comes back as file text, and a xero destination is refused
GET/api/v1/finance/tax/basfinance:view_prices or finance:close_periodsbillingEntityId, from, to. Sales and purchases on both the cash and accrual bases, G1, 1A and 1B, grouped by tax rate identity

Recognition journal rebuilds retain reversed original WIP adjustments and their posted reversals in their recorded periods. totals.accruedMovement includes signed adjustment movement; totals.reporting.adjustments uses signed recorded reporting values with missing-valuation coverage. Draft and validated journals can rebuild. Exported journals retain their original content. The separate revenue reconciliation continues to compare recognised and invoiced revenue. These rules also apply through call_public_api.

For newly created correction journals, totals.accruedMovement, totals.deferredMovement, totals.revenueMovement, adjustment buckets and totals.reporting.adjustments reverse the original journal’s signs. Debit and credit totals remain positive. Recorded period recognition, invoicing, credits and coverage stay unchanged. totals.reconciliation starts with the original journal’s projected ledger total, then applies the correction. Its reconciles flag can be false because the period’s recognised revenue has not changed. Missing legacy figures are omitted. Existing exported records remain unchanged.

Listing and exporting a saved correction use the routes above, including through call_public_api. There is no public route or named MCP tool to create a journal correction. See Month End for reviewing the period.

MethodPathCapabilityNotes
GET/api/v1/finance/reportsany figures capabilityThe reports you can see, the report families and their schedules. includeArchived, reportKey
POST, PATCH/api/v1/finance/reports[/:id]any figures capabilityreportKey, name, filters, grouping, columns, sort, visibility. archived on the PATCH archives or restores
GET/api/v1/finance/reports/:id/runany figures capabilityRuns under your own lens and scope. Returns rows, totals, coverage, definitions for the glossary panel, and link
GET/api/v1/finance/reports/:id/exportfinance:exportformat (csv or xlsx). The file opens with a header block stating the filters, the currency, the coverage and each metric’s tax basis
POST/api/v1/finance/reports/:id/schedulesany figures capabilitycadence (daily, weekly, monthly), dayOfWeek, dayOfMonth, minuteOfDay (organisation time), format, destinationKind (email, slack, teams), destination. Recipients have to be members of the organisation, and every delivery re-checks the owner’s live authority at run time rather than the authority they held when they saved it
DELETE/api/v1/finance/report-schedules/:idany figures capabilityStops a scheduled delivery. Returns 204
GET/api/v1/finance/findingsany figures capabilityYour own findings from the nightly review, re-checked against your live lens and scope. Returns findings, withheld and run
POST/api/v1/finance/findings/:id/dismissany figures capabilityDismisses one of your own findings
GET/api/v1/finance/periods/:id/assistantUnrestricted organisation-wide finance:view_prices, with no project/client price deniesThe month-end brief: blocking, advisory, canClose, invoiceDrafts, movement, closeSummary and nextActions, with draftsOnly: true. It never closes the period
GET/api/v1/finance/periods/:id/close-reportfinance:close_periods or finance:view_prices, plus unrestricted organisation-wide price and cost accessThe report frozen against the period’s close generation, with every earlier generation in history
GET/api/v1/finance/periods/:id/export-packfinance:export, plus unrestricted organisation-wide price and cost accesscloseGeneration, generatedAt and files, each with a name and its csv. Every file states its period, filters, currency and coverage before the header row

Closing a period, POST /api/v1/finance/periods/:id/close in Uninvoiced work and periods, also takes runRecognition, which is true unless you send false: the close runs recognition for the period first, then checks inside its own transaction that the figures still stand up. If they do not it answers 409 RECOGNITION_NOT_FRESH with period, reason, pendingLines and notComputable.

A successful close also saves purchase accrual balances separately by currency. Saving those balances and closing the period succeed or fail together. Revenue recognition may already have completed before a close failure. Repeating a close on a closed period leaves the saved balances unchanged. Reopening keeps them until the next close replaces them. Older closed periods are not automatically backfilled. Closing requires the existing period-closing permission; reading accrual balances separately still requires cost access to every contributor.

Merchant payments run on the organisation’s own Stripe account, joined through Stripe Connect. Charges are direct charges on that account, so the organisation is the merchant of record and Runnit never holds client funds. This shares nothing with Runnit’s own subscription billing: different credentials, different webhook, different records. See the Online Payments, Payment Plans, Late Fees and Client Portal Billing guides for the product behaviour.

Where the platform is not configured for merchant payments, reads answer normally and payment eligibility comes back with reason: "not_configured".

Workspace routes here sit on the shared finance router, so each one answers on both /api and /api/v1. Two lanes deliberately do not: the webhook and the Connect OAuth callback carry no session and assert no capability.

MethodPathCapabilityNotes
GET/api/v1/finance/payments/connectionsfinance:manage_integrationsconfigured and connections. A connection never carries a credential, a webhook secret or an OAuth bundle
POST/api/v1/finance/payments/connectionsfinance:manage_integrationsStarts the Connect flow. Returns authorizeUrl and connectionId, and sets an HttpOnly cookie that binds the browser
POST/api/v1/finance/payments/connections/redeemfinance:manage_integrationscode, plus the binding cookie. 403 when the organisation, the person or the browser differs from the one that started it
POST/api/v1/finance/payments/connections/:id/refreshfinance:manage_integrationsPulls the connected account’s live capabilities
PATCH/api/v1/finance/payments/connections/:idfinance:manage_integrationsenabled, statementDescriptor, webhookSecret. Enabling re-checks eligibility and answers 409 when Stripe has not enabled charges on the account
DELETE/api/v1/finance/payments/connections/:idfinance:manage_integrationsDeauthorises at Stripe, then clears the stored credential
GET/api/v1/finance/payments/connections/:id/eventsfinance:manage_integrationsThe last 50 received provider events with their processing state
MethodPathCapabilityNotes
GET/api/v1/finance/payments/application-feefinance:manage_integrationsfee (basisPoints, source, basis) and canOverride
PUT/api/v1/finance/payments/application-feeplatform administratorbasisPoints, 0 to 500, or null to clear. 403 for everyone else, including the organisation owner
PUT/api/v1/finance/payments/entities/:entityId/late-feesfinance:manage_settingsenabled, annualRateBp, graceDays, intervalDays, minimumFeeAmount, dayCountBasis (actual_365, actual_360, thirty_360), preferStatutory, compensationEnabled, contractReference, taxRateId
PUT/api/v1/finance/payments/entities/:entityId/surchargesfinance:manage_settingsenabled, methodRatesBp per method (0 to 1000), fixedAmount, disclosureText, taxRateId. Off by default, and a jurisdiction prohibition wins whatever is set

The effective application fee is snapshotted onto each payment attempt with its basis and its source, so changing the configuration never alters a retry, and a refund returns the fee in proportion. No public, portal or client-supplied parameter can influence it.

Reads take any invoice capability.

MethodPathCapabilityNotes
GET/api/v1/finance/invoices/:id/payment-statusany invoice capabilityonlinePaymentState (none, available, processing, settled, disputed), paymentProcessingAmount, settlementConfirmedAt, disputeWindowEndsAt, eligible, reason, attempts and disputes
POST/api/v1/finance/invoices/:id/payment-attemptsfinance:record_paymentsCreates or adopts the invoice’s live attempt. Never returns a client secret: that belongs to the payer’s own surfaces
POST/api/v1/finance/invoices/:id/charge-mandatefinance:record_paymentsmandateId, optional amount and scheduledDebitOn. 202 with waiting.reason when the advance notice for this debit is not satisfied, 201 when it charges
GET/api/v1/finance/invoices/:id/debit-noticesany invoice capabilityThe advance notices raised for this invoice and their delivery state
GET/api/v1/finance/payments/mandatesany invoice capabilityStored cards and bank accounts, display detail only
DELETE/api/v1/finance/payments/mandates/:idfinance:record_paymentsRevokes consent

An attempt is keyed from the payable balance (the amount due less anything already authorised and still in flight), read under the invoice lock, not from the document version. At most one attempt per invoice is live, so two callers adopt the same one instead of racing two; when the balance moves the intent is updated in place, and when it reaches zero the intent is cancelled. A payment that succeeds after a credit closed the invoice keeps its receipt and becomes client credit.

Money authorised on a bank rail is a collection state, not a status. It never reaches amountPaid, cash or recognition, and it is subtracted from the payable balance so it cannot be collected twice. The invoice status union, receivables and reminders are unchanged.

Recording positive cash through POST /api/v1/finance/payments updates the active plan’s oldest unpaid instalments in the same save. Reusing the receipt’s idempotencyKey does not settle it twice. Payments recorded before activation never settle a later or replacement plan. Partial-amount plans retain their agreed total. These effects also apply through call_public_api.

Activation returns 409 if the proposed amount exceeds the current invoice balance. Cancel that proposal and create a new one.

Refunds and imported-receipt reversals restore only money settled against the same live plan. Client-credit applications, credit notes and write-offs reduce the agreed unpaid amount in the same save. Partial agreements are never expanded to the whole invoice debt. Paid instalments retain their receipt history. A fully credited tail completes the plan; zero instalments are cancelled. Completed, defaulted and cancelled plans are not reopened. The same validation, permissions and effects apply through call_public_api.

Older unmatched receipts and previously inconsistent schedules still need review as described in Payment Plans.

MethodPathCapabilityNotes
GET/api/v1/finance/payments/plansfinance:manage_receivablesFilters by status and clientOrganizationId
GET/api/v1/finance/payments/plans/:idfinance:manage_receivablesplan, instalments, settledAmount, outstandingAmount, nextDueOn and missedCount
POST/api/v1/finance/payments/plansfinance:manage_receivablesinvoiceId, instalmentCount (2 to 60), cadence (weekly, fortnightly, monthly), firstDueOn, optional amount and note. Creates a proposed plan, which collects nothing
POST/api/v1/finance/payments/plans/:id/activatefinance:manage_receivablesActivates it and pauses the invoice’s reminders
POST/api/v1/finance/payments/plans/:id/cancelfinance:manage_receivablesreason. Resumes reminders

Instalments always sum to the plan total exactly and create no cash: an instalment is settled only by a real payment allocated to the invoice. A missed instalment defaults the plan and resumes reminders.

MethodPathCapabilityNotes
GET/api/v1/finance/payments/late-feesfinance:manage_receivablesAssessments, filterable by invoice and status
GET/api/v1/finance/invoices/:id/late-fee-previewfinance:manage_receivablesThe next chargeable interval with its rate, basis, day count, interest and compensation, or the reason there is none
POST/api/v1/finance/invoices/:id/late-feesfinance:manage_receivablesAssesses the next interval. 409 LATE_FEE_NOT_CHARGEABLE when there is none
POST/api/v1/finance/payments/late-fees/:id/invoicefinance:manage_receivablesRaises the fee as a separate invoice through the ordinary drafting and issue path. Idempotent
POST/api/v1/finance/payments/late-fees/:id/waivefinance:manage_receivablesreason. Refused once the fee has been invoiced

An assessment is unique on the invoice and its interval, so a repeated run cannot charge the same days twice, and intervals never double-count the boundary day. A fee invoice cannot itself accrue a fee.

MethodPathCapabilityNotes
GET/api/v1/finance/payments/held-settlementsfinance:record_paymentsSettlements held out of a closed period, with their economic date
POST/api/v1/finance/payments/held-settlements/:id/resolvefinance:record_paymentspostOn, reason. Posts it into an open period, keeping the economic date on the record
GET/api/v1/finance/receivables/clients/:id/brieffinance:manage_receivablesThe account summary before a collections call
POST/api/v1/finance/receivables/clients/:id/draftfinance:manage_receivablestone, invoiceIds, proposePlan, instalmentCount, cadence. Draft only: answers draftOnly: true, sends nothing and creates no plan
PATCH/api/v1/finance/quotes/:id/deposit-at-acceptancefinance:manage_quotesenabled, percentBp. Refused once the quote is accepted

A settlement whose economic date falls in a closed period is held rather than discarded or forced in, and the webhook still answers normally.

MethodPathNotes
POST/api/finance/payments/webhooks/stripeVerifies the signature over the raw bytes. 401 on a bad signature, 400 on an unparseable body, 200 otherwise. Receiving records the event; processing is separate, so a replay records once
GET/finance/payments/oauth/callbackAuth host only. Exchanges the code and redirects to the tenant with a single-use redemption code

Portal routes are participation routes: a client user authenticates but does not consume a seat on the agency’s plan, so the Finance entitlement does not apply. Authority is resolved live on every request from four things together: an active client relationship with an agency reachable from the request host, an active membership in the client organisation, the capability below held in the client organisation, and the agency’s per-client toggle.

MethodPathClient capability
GET/api/portal/billing/summaryclient_portal:billing
GET/api/portal/billing/invoicesclient_portal:billing
GET/api/portal/billing/invoices/:idclient_portal:billing
GET/api/portal/billing/invoices/:id/pdfclient_portal:billing
POST/api/portal/billing/invoices/:id/payclient_portal:billing
POST/api/portal/billing/invoices/:id/queriesclient_portal:billing
GET/api/portal/billing/statementsclient_portal:billing
GET/api/portal/billing/creditsclient_portal:billing
GET/api/portal/billing/payment-methodsclient_portal:billing
GET/api/portal/billing/plans/:idclient_portal:billing
GET/api/portal/budgets/consumptionclient_portal:budgets
GET/api/portal/billing/statements/:id/pdfclient_portal:billing
GET/api/portal/budgets/retainersclient_portal:budgets
GET/api/portal/acceptance, /api/portal/acceptance/:subjectType/:id, /api/portal/acceptance/:subjectType/:id/pdfclient_portal:approve_changes
POST/api/portal/acceptance/:subjectType/:id/code, /accept, /decline, /queriesclient_portal:approve_changes

These routes require a signed-in client account and are deliberately absent from generated-token /api/v1 and MCP. Statement downloads return 409 RENDER_PENDING until their verified PDF is ready. Retainer and pending-acceptance lists accept offset (0 to 100000) and limit (1 to 50, default 25), returning items, hasMore, offset and limit. Retainer hours are absent as a group when hours are hidden; only client-visible period budgets qualify. Period ends and rollover expiry boundaries are exclusive.

Acceptance addresses an issued version: subjectType is quote_version or budget_version, and id is that version’s id. The GET supports comma-separated selection ids. Accept takes signerName, signerEmail, optional selection, signatureText and code; the document’s signature and OTP requirements still apply. Code requests take email and action (accept or decline). Decline accepts reason, declinedByName, declinedByEmail and code. Quote queries take message and optional from; change orders do not support queries. Acceptance returns 201, or 200 with the original receipt on replay. Client acceptance keeps the agency’s internal approval requirement; applied: false means the change has been signed but not applied. Access is checked again before recording a decision.

Every document is fetched by agency and client together, never by id alone, so another client’s document answers 404, never 403. Each response carries a fixed field list rather than a filtered copy of the workspace payload: no cost, no margin, no internal notes, no staff names, and no provider identifier or secret. The rendered body is filtered before it is rendered; a PDF cannot be filtered after the fact, so one whose appendix this client may not see is refused rather than served in a reduced form. Every response sends Cache-Control: no-store, Referrer-Policy: no-referrer and X-Robots-Tag: noindex, nofollow. Paying and asking a question are rate limited per user, and everything is rate limited per address.

Ledgers, e-invoicing, scenarios, self-billing, suppliers and bank statements

Section titled “Ledgers, e-invoicing, scenarios, self-billing, suppliers and bank statements”

A second accounting ledger, e-invoicing over Peppol, scenarios beside a budget or a quote, recipient-created tax invoices, the supplier area, bank statement matching, and the jurisdiction rules that decide what a document may say. See the Accounting Connections, E-Invoicing, Scenarios, Self-Billing, Supplier Portal, Bank Reconciliation and Tax Rules by Jurisdiction guides for the product behaviour.

Each of these ships off: e-invoicing is disabled on every billing entity and client profile, tax calculation is off on every entity, and self-billing is off on every supplier. A QuickBooks connection needs Intuit credentials configured on the deployment.

Workspace routes sit on the shared finance router, so each answers on both /api and /api/v1. Three lanes deliberately do not: the Peppol callback receiver and the hosted purchase order page carry no session, and the supplier area authenticates with its own credential rather than a Runnit session.

The phase 3 ledger routes now serve every registered provider, selected per connection, and an entity may hold one connection per provider. An issued document fans out to every enabled connection, and each keeps its own remote identity, mapping set and status.

MethodPathCapabilityNotes
GET/api/v1/finance/ledger/connections/:id/receiptsfinance:manage_settings or finance:manage_integrationsProvider receipts with their allocation children. Money goes through the finance lens
GET/api/v1/finance/ledger/receipt-allocationssameThe reconciliation queue: allocations nothing matched, and allocations more than one local payment could be. An ambiguous identity is never allocated automatically
GET/api/v1/finance/ledger/connections/:id/cursorssameChange feed cursors, carrying windowExpired and recoveryRequired
PUT/api/v1/finance/ledger/connections/:id/productionfinance:manage_integrationsenabled, evidence, note. 409 LEDGER_NOT_READY while the connection is not ready, 400 LEDGER_EVIDENCE_REQUIRED with no recorded evidence. Records who, when and what was checked

Behaviour changes on the existing routes:

  • POST /api/v1/finance/ledger/connections/start with provider: "quickbooks" answers 503, a known provider with no credentials configured here. netsuite, sage_intacct and myob answer 400: the value is accepted so a future adapter needs no migration, but no authorisation flow exists.
  • POST /api/v1/finance/ledger/connections/redeem answers 409 when the provider returns a different company file from the one already bound.
  • Connection reads carry capabilities, unsupportedOperations, productionEnabled and a reference summary that now counts items, classes and departments. Readiness adds item_unknown, item_no_income_account, item_inactive and adapter_not_production_proven.

An operation a provider cannot do is skipped with the reason on the operation row rather than retried against a method that does not exist.

Reads take finance:view_invoices or finance:manage_settings.

MethodPathCapabilityNotes
GET/api/v1/finance/einvoicing/settingsreadThe mapped business terms, the implemented rule set, and the registered provider
PUT/api/v1/finance/einvoicing/entities/:id/settingsfinance:manage_settingseinvoiceEnabled, einvoiceProfile, endpoint scheme and id, legal registration. Off by default
PUT/api/v1/finance/einvoicing/clients/:id/settingsfinance:manage_settingsThe same for a client billing profile, plus einvoiceChannel and buyerReference
GET, POST/api/v1/finance/einvoicing/participantsread, finance:manage_settingsAddressable identities for an entity or a client
DELETE/api/v1/finance/einvoicing/participants/:idfinance:manage_settings
POST/api/v1/finance/einvoicing/participants/:id/lookupfinance:manage_settingsResolves through the provider. A participant that does not resolve blocks submission
POST/api/v1/finance/einvoicing/participants/:id/primaryfinance:manage_settings
GET/api/v1/finance/invoices/:id/einvoicereadEligibility. On a draft, problems[] names each missing business term and where it would come from
GET/api/v1/finance/invoices/:id/einvoice/snapshotread, and the prices lensThe data frozen at issue, with frozen. An issued document that never had one answers snapshot: null rather than 404: it is ineligible, and saying so is more useful
POST/api/v1/finance/invoices/:id/einvoice/submitfinance:issue_invoices202 in flight, 200 when an identical earlier submission is adopted, 409 with the failed rules when the rule set refuses it
GET/api/v1/finance/einvoicing/documents/:idreadSubmission and delivery state, with the events behind it
GET/api/v1/finance/einvoicing/documents/:id/xmlread, and the prices lensThe transmitted document as evidence, with its X-Content-Sha256
POST/api/v1/finance/einvoicing/documents/:id/recheckfinance:manage_integrationsAdministrative recovery for a stuck submission
POST/api/v1/finance/einvoicing/documents/:id/cancelfinance:manage_integrationsRefused once the network has delivered it
GET/api/v1/finance/invoices/:id/deliveriesreadEvery channel the document went out on
POST/api/v1/finance/invoices/:id/deliveries/authorisefinance:issue_invoiceschannel, recipient, reason. The reason is required and peppol is refused as an alternative to itself. The invoice row is unchanged
POST/api/public/peppol/callbacks/:providernone, signature verifiedPublic, raw body. An unknown provider and a bad signature answer identically. Not on /api/v1

Submission is idempotent on the document version and the content of the snapshot. Refusals carry a code: SNAPSHOT_MISSING, ENTITY_NOT_ENABLED, CLIENT_NOT_ENABLED, PARTICIPANT_NOT_FOUND, RECEIVER_ADDRESS_MISSING, NOT_ISSUED, INVOICE_VOIDED, ALREADY_DELIVERED.

Scenario line inputs accept sectionName and pricing: discountBp, discountAmount, markupBp, capAmount, percentBp, percentageBase, burnBasis and recognitionMethod. Percentage fields use basis points; amounts use minor-unit strings. Choose a fixed or percentage discount. Tax evidence is inherited from source lines. Quote comparisons use default selections. estimatedCost: null means unknown; an explicit "0" means estimated zero. missingCostEstimates marks a partial cost subtotal and suppresses margin. missingHoursEstimates marks incomplete effort and suppresses hours deltas and modelled duration. Promotion is atomic, creates named sections in the draft, retains matched quote schedule allocations, and refuses changed pricing with SCENARIO_PRICE_CHANGED or allocated-line removal with SCENARIO_SCHEDULE_REVIEW_REQUIRED (409).

A scenario is draft only. Nothing here writes a budget line, a total, a cache, the billing schedule, recognition or an approval, and every apply path refuses a scenario.

MethodPathCapabilityNotes
GET, POST/api/v1/finance/budgets/:id/scenariosfinance:view_prices or finance:manage_budgets to read, finance:manage_budgets to createScenarios are numbered on their own counter, independent of the budget version counter, and do not occupy the one open change order
GET/api/v1/finance/budgets/:id/scenarios/comparisonreadTotal, cost, margin, hours and duration in days as its own figure, with durationSource and durationBasis. A scenario with no team assumptions answers null, never a number derived from the hours
GET, PATCH, DELETE/api/v1/finance/scenarios/:scenarioIdread, finance:manage_budgetsA promoted scenario is read only
PUT/api/v1/finance/scenarios/:scenarioId/linesfinance:manage_budgets
POST/api/v1/finance/scenarios/:scenarioId/archive, /restorefinance:manage_budgets
POST/api/v1/finance/scenarios/:scenarioId/promotefinance:manage_budgetsOpens a distinct change order through the ordinary path, with lineage to an immutable snapshot. 409 on a stale base, naming the version the budget is on now. Two concurrent promotions produce one change order
GET, POST/api/v1/finance/quotes/:id/scenariosfinance:view_prices or finance:manage_quotes to read, finance:manage_quotes to createKeeps the selection and package semantics a budget snapshot has no columns for
GET/api/v1/finance/quotes/:id/scenarios/comparisonread
GET, PATCH, DELETE/api/v1/finance/quote-scenarios/:scenarioIdread, finance:manage_quotes
PUT/api/v1/finance/quote-scenarios/:scenarioId/linesfinance:manage_quotes
POST/api/v1/finance/quote-scenarios/:scenarioId/archive, /restorefinance:manage_quotes
POST/api/v1/finance/quote-scenarios/:scenarioId/promotefinance:send_quotesFills the open draft, or opens the next version as a draft. Sent and accepted versions are untouched, and an accepted quote is refused

Money on a scenario follows the same lens as everywhere else: a caller holding prices and not cost has no cost or margin key at all. Hours and duration are not money and stay visible.

Reads take a purchase read capability. Every figure on a document is cost, so finance:view_cost is needed to see any of them.

MethodPathCapabilityNotes
GET, POST/api/v1/finance/self-billing/agreementspurchase read, then finance:manage_settings and procurement:manage_billsThe agreement freezes both parties, the supplier tax identifier, the rules relied on and the retained signed document
GET/api/v1/finance/self-billing/agreements/:idpurchase read
POST/api/v1/finance/self-billing/agreements/:id/sign, /activate, /withdrawboth capabilitiesSigning is refused while the jurisdiction is unresolved
GET/api/v1/finance/self-billing/enablementpurchase readAnswers which of the five conditions is unmet, not just a boolean
PATCH/api/v1/finance/self-billing/suppliers/:idboth capabilitiesenabled and the supplier’s agreed rate. Off by default
GET, POST/api/v1/finance/rctipurchase read, procurement:manage_bills
GET/api/v1/finance/rcti/:id, /preview, /pdfpurchase read, and the cost lens on the preview and the PDF
POST/api/v1/finance/rcti/:id/approveprocurement:manage_billsThe supplier bill’s own approval is the authority
DELETE/api/v1/finance/rcti/:idprocurement:manage_billsDiscards a draft and frees the work it claimed. An issued document cannot be discarded
POST/api/v1/finance/rcti/:id/issueboth capabilitiesRe-derives the agreement’s validity under the row lock, so a withdrawal racing an approval refuses. Idempotent on issueIdempotencyKey; a key held by another document answers 409 IDEMPOTENCY_KEY_IN_USE
POST/api/v1/finance/rcti/:id/void, /correctboth capabilitiesAn issued document is immutable. A correction voids it and drafts a new one referencing it
POST/api/v1/finance/self-billing/runsprocurement:manage_billsThe period draft, on demand. Nothing is scheduled

Refusal codes: SELF_BILLING_NOT_PERMITTED, SELF_BILLING_RULE_MISSING, SELF_BILLING_RULES_UNVERIFIED, RCTI_NOT_ISSUED, plus PERIOD_CLOSED.

Signed agreement files and supplier delivery

Section titled “Signed agreement files and supplier delivery”

GET /api/v1/finance/self-billing/suppliers/:id returns selfBilling with enabled, currency, unit and selfBillingRateAmount (only with cost access). PATCH takes enabled, optional rateAmount in minor units, rateCurrency and unit (hour, day, item). A changed rate applies to new drafts.

Agreement reads include evidenceRetained. POST to an agreement’s /sign requires agreementAssetId, optional agreementAssetVersion, and agreementSha256. It verifies the actual accessible file version before recording the signature. Remote links, unavailable versions and mismatched checksums refuse. An identical signature retry preserves the original signature.

POST /api/v1/finance/self-billing/agreements/:id/file-evidence takes the same file fields and retains missing proof for an already signed agreement. Its checksum must match the original signature; parties, dates and rules remain. Both file actions need settings and bill-management capabilities. GET .../:id/document downloads the retained bytes and additionally needs cost access. Files are limited to 25 MB. Missing proof returns 404 on download and blocks further self-billing with AGREEMENT_MISSING_DOCUMENT at enablement.

POST /api/v1/finance/rcti/:id/send takes required UUID idempotencyKey, recipients (1 to 20 email addresses), optional cc (up to 20), replyTo and message (up to 5000 characters). It requires both management capabilities and cost access and returns 202 with sendJob. The document must be issued. The issued PDF is attached; pending rendering is retried. A matching request identity returns the same job; changed request details return 409. GET .../:id/sends returns sendJobs to purchasing readers with cost access.

Delivery rechecks current authority, issued state and document identity and preserves dataset suppression. Provider acceptance is distinct from delivery. An interrupted send with an unknown outcome is marked failed for review before an explicit new request, without automatically sending a duplicate.

Runs create further documents after each 200 lines. A failed page reports remaining sources in truncated; rerunning resumes without duplicating earlier drafts. skipped may include count for unpriceable sources. A standalone void releases time for a later run while preserving its original claim history.

A correction’s void, replacement draft and source claims succeed together. voidedOn dates both the void and replacement bill; service dates stay as originally recorded. Source-backed lines must keep their order, budget links and quantities; price and description may change. Ambiguous mappings return 409 CORRECTION_SOURCE_MAPPING. Missing required line fields return 400. The new draft still needs approval and issue. Existing generated-token and MCP call_public_api validation, permissions and organisation binding apply.

The supplier surface is not on /api/v1 and carries no Runnit session. A supplier presents an independently issued credential, scoped to the organisation, the supplier, the permitted billing entities and the permitted actions, and exchanges it for a short session. A hosted document token cannot reach any of it, and a supplier session cannot open a hosted document.

MethodPathAction requiredNotes
POST/api/public/supplier-portal/sessionsa valid credentialUnknown, wrong, revoked, expired, locked and wrong-organisation answer identically. Five failures lock the credential for 15 minutes
DELETE/api/public/supplier-portal/sessionsa live session
POST/api/public/supplier-portal/credentials/recovernone{ email }; uniform 202, queues one-time links to recorded active grants
POST/api/public/supplier-portal/credentials/recover/completeone-time proof{ token }; returns the same session shape as sign-in, or uniform 404
GET/api/public/supplier-portal/purchase-orders, /:id, /:id/pdfview_purchase_ordersFixed field lists. 409 RENDER_PENDING while a document is being prepared
GET/api/public/supplier-portal/billsview_billsDraft bills are not listed, and internal states are collapsed
GET, POST/api/public/supplier-portal/uploadsupload_invoice, or view_bills to readPDF, PNG, JPEG or WEBP, at most 15 MB. The supplier, entity and order are resolved on the server. Produces a proposal only
GET/api/public/supplier-portal/remittancesview_remittance
GET/api/public/hosted/purchase-orders/:token, /pdfa live tokenThe hosted purchase order page, on the same token contract as the hosted invoice. An invoice token answers 404 here
POST/api/public/hosted/purchase-orders/:token/recovernoneQueues the current link and verified PDF to the recorded supplier contact, once per 15-minute cooldown. Revoked links cannot recover

The action catalogue is four entries: view_purchase_orders, view_bills, view_remittance and upload_invoice. There is no approve, pay, receive or bank action. Both public routers send Cache-Control: no-store, Referrer-Policy: no-referrer and X-Robots-Tag: noindex, nofollow, and both are rate limited per address and per credential or token.

Managing the area is an ordinary workspace surface:

MethodPathCapabilityNotes
GET, POST/api/v1/finance/suppliers/:supplierId/portal-credentialsread takes a purchase read capability or finance:manage_settings; issuing needs procurement:manage_suppliers or finance:manage_settingsThe secret is returned once, on issue and on rotation, and never again
POST/api/v1/finance/supplier-portal/credentials/:id/rotate, /revokeprocurement:manage_suppliers or finance:manage_settingsRotation issues a new credential with the same scope and kills the old one and its sessions in one transaction
GET/api/v1/finance/supplier-portal/credentials/:id/sessionspurchase read or finance:manage_settings
GET/api/v1/finance/supplier-portal/uploads, /:idpurchase read or finance:manage_settingsThe proposal’s amounts additionally need the cost lens
POST/api/v1/finance/supplier-portal/uploads/:id/acceptprocurement:manage_billsCreates a draft supplier bill with the upload attached and reviewed invoice lines. Accepting twice returns the same bill
POST/api/v1/finance/supplier-portal/uploads/:id/rejectprocurement:manage_billscode, reason; the internal reason is not emailed
POST/api/v1/finance/purchase-orders/:id/hosted-linkprocurement:manage_pos and cost access for the recorded project/clientFreezes the issued version’s document if needed and mints a link bound to it

Creating a purchase order link requires permission to see that order’s costs, using its recorded project and client. A cost refusal returns 403; an unknown or other-organisation order returns 404. Neither creates a document or link. An authorised request for an unissued draft returns 409 without preparing a document. Existing supplier links and their recovery rules remain valid when a staff member’s cost access changes. The same checks apply through call_public_api with its normal write confirmation.

Acceptance supports an optional review object containing currency, billDate, supplierInvoiceNumber, nullable dueDate and 1–200 lines. Each line needs description, positive decimal quantity, amount as a gross whole-line minor unit string, and explicit taxRateId (a current purchase rate for the selected billing entity, or null for no tax). Quantity does not multiply amount. Optional billingEntityId and projectId apply to the draft. The extracted proposal stays unchanged. Missing fields or unresolved tax return SUPPLIER_PROPOSAL_REVIEW_REQUIRED; source line/header total mismatch returns SUPPLIER_PROPOSAL_TOTAL_MISMATCH. Complete untaxed proposals can still be accepted without review. These contracts also apply through call_public_api.

Recovery is limited to one request per credential every 15 minutes and at most 20 current matching grants per address. A proof lasts up to 30 minutes, is usable once and cannot extend the grant. Each grant retains its own organisation, supplier, entity and action scope. Decision emails omit internal rejection reasons; acceptance only means a draft was created. Provider acceptance does not confirm delivery. Recovery remains outside generated-token REST and MCP access.

Reading needs both finance:view_prices and finance:view_cost, because one statement mixes receivable and payable money and no field name can be both. Mappings and imports need finance:manage_settings.

MethodPathCapabilityNotes
GET, POST/api/v1/finance/bank-statements/mappingsread, finance:manage_settingsThe column map and the parsing conventions. Versioned, never edited in place
GET/api/v1/finance/bank-statements/mappings/:idread
POST/api/v1/finance/bank-statements/mappings/:id/archivefinance:manage_settings
GET, POST/api/v1/finance/bank-statements/importsread, finance:manage_settingsThe file arrives base64 in the JSON body, capped at 8MB, CSV only, with an optional sourceAssetId. An exact reimport is refused; an overlapping export imports only the new rows and marks the rest duplicate
GET/api/v1/finance/bank-statements/imports/:idreadCarries overlapCount, parseErrors and the opening and closing balances
POST/api/v1/finance/bank-statements/imports/:id/revertfinance:manage_settingsRefused once any line has settled money
POST/api/v1/finance/bank-statements/imports/:id/rescanany settlement capability
GET/api/v1/finance/bank-statements/lines, /:id, /:id/candidatereadCandidates carry confidenceBp, rank, tiedWith, their reasons and a candidateHash. Every candidate sharing the top score is tied and nothing is suggested
POST/api/v1/finance/bank-statements/lines/:id/ignore, /rescanany settlement capability
POST/api/v1/finance/bank-statements/lines/:id/confirmany settlement capability at the route, and the action’s own capability in the serviceFive distinct actions: link_existing_payment, record_receivable_settlement, record_payable_settlement, confirm_reimbursement_run, mark_card_spend_paid
GET/api/v1/finance/bank-statements/confirmations/:idread
POST/api/v1/finance/bank-statements/confirmations/:id/reverseas confirmingreason required. Refused twice on the same confirmation
GET/api/v1/finance/bank-statements/matches/:idread

Each action carries its own capability inside the service: finance:record_payments for linking and a receivable settlement, procurement:manage_bills for a payable settlement, and expense:manage for a reimbursement run and card spend. There is no confidence score at which anything is recorded; confirmation is always an explicit call, and every check is re-run under the line’s lock.

Refusal codes: STATEMENT_FILE_ALREADY_IMPORTED, STATEMENT_AMBIGUOUS, STATEMENT_FORMAT, STATEMENT_IMPORT_CONFIRMED, STATEMENT_LINE_DUPLICATE, STATEMENT_LINE_IGNORED, STATEMENT_ALREADY_CONFIRMED, STATEMENT_ACTION_MISMATCH, STATEMENT_TARGET_MISMATCH, STATEMENT_CANDIDATE_REJECTED, STATEMENT_CANDIDATE_STALE, STATEMENT_TARGET_BALANCE_CHANGED, STATEMENT_EXISTING_PAYMENT, STATEMENT_EXISTING_PAYMENT_AMBIGUOUS, STATEMENT_SPLIT_TOO_LARGE, STATEMENT_ALREADY_REVERSED, plus PERIOD_CLOSED.

Reads take finance:manage_settings or finance:view_invoices.

MethodPathCapabilityNotes
GET/api/v1/finance/jurisdictions/rulesreadThe rules in force for an entity on a date, with their verification state. Takes billingEntityId, onDate and consumer
POST/api/v1/finance/jurisdictions/rules/:ruleId/confirmfinance:manage_settingsBinds the confirmation to the rule row, its effective date and a hash of its value, so a superseded rule is not still confirmed
GET/api/v1/finance/jurisdictions/verificationreadWhat each consumer requires and what it has: unverified, missing, superseded or future_effective
POST/api/v1/finance/jurisdictions/reconcilefinance:manage_settingsRe-checks confirmations and revalidates drafts. Issued documents keep the rules they froze
GET/api/v1/finance/jurisdictions/coveragereadThe coverage matrix as data, including what is deferred and why
GET, POST/api/v1/finance/jurisdictions/mileage-rates, /per-diem-ratesread, finance:manage_settingsEffective dated, with a verified flag. An unverified rate is never used silently
PATCH/api/v1/finance/jurisdictions/mileage-rates/:id, /per-diem-rates/:idfinance:manage_settingsAn edit closes the open row rather than rewriting history. A second row starting the same day answers 409
GET, PUT/api/v1/finance/billing-entities/:id/tax-calculation-settingsread, finance:manage_settingsoff, optional or required. off for every entity
GET, POST/api/v1/finance/invoices/:id/tax-calculationread, finance:draft_invoicesBound to the draft’s content hash and computed outside any transaction. Any edit supersedes it

Issue consumes the calculation inside its own transaction and makes no network call there. In required mode it refuses a missing, expired, superseded, failed or disagreeing answer; in optional mode it falls back to the configured rates and the document records which basis it used. Codes: TAX_CALCULATION_MISSING, _EXPIRED, _SUPERSEDED, _FAILED, _MISMATCHED, and UNVERIFIED_RULES when a rule the document relies on has not been confirmed.

Every issued document has a link the client can open without signing in. It is served outside the API and needs no key:

MethodPathNotes
GET/api/public/hosted/documents/:tokenThe public view: kind, title, number, status, dates, currency, total, amount due, the parties, whether the PDF is ready, the rendered HTML and the client’s own queries
GET/api/public/hosted/documents/:token/pdfThe frozen bytes, or 409 while the document is being prepared
POST/api/public/hosted/documents/:token/recoverAlways answers the same way. A fresh link goes only to the billing contact Runnit resolves, once per cooldown
POST/api/public/hosted/documents/:token/queriesThe client asks a question, which lands on the invoice’s timeline

Tokens are random, stored hashed, bound to one document version, and can be rotated or revoked. An invalid, expired or revoked token gets the same 404 as one that never existed. The page never carries cost, margin or internal notes, and it is not indexed.

The html field contains a complete styled document suitable for a sandboxed iframe. After acceptance, it and pdfReady refer to the accepted document, with the agreed selection and signer evidence. Until the accepted document has been created, html is empty, pdfReady is false and PDF download returns the pending response. Once its content is saved, HTML is available even if its PDF is still rendering. Staff quote previews return 409 until the accepted document has been created. The earlier unsigned PDF is never substituted. The accepted state and summary remain available. The same behaviour applies in the client portal.

Quotes and client-signed change orders use the same public lane, with the same headers and the same uniform 404:

MethodPathNotes
GET/api/public/hosted/quotes/:tokenThe public projection of the version that was sent. ?selection=id,id recalculates the total for a selection the client is trying, and records nothing
GET/api/public/hosted/quotes/:token/pdfThe quote PDF with internal notes excluded, or 409 RENDER_PENDING while it is being prepared
POST/api/public/hosted/quotes/:token/codeemail and action (accept or decline). Answers 202 with required, the same way whether a code was sent or none was needed
POST/api/public/hosted/quotes/:token/acceptsignerName and signerEmail are required, then selection, signatureText and code. 201 the first time and 200 with the same acceptance hash after that: acceptance is idempotent on the token and never overwrites the original signer evidence. 400 on an illegal selection, 403 on a bad code, and 409 after a decline, a withdrawal, a revision or an expiry
POST/api/public/hosted/quotes/:token/declinereason, lostReasonId, declinedByName, declinedByEmail, code
POST/api/public/hosted/quotes/:token/queriesmessage and from. The question lands on the quote’s timeline
POST/api/public/hosted/quotes/:token/recoverAlways answers the same way. A fresh link goes only to the contact Runnit resolves, once per cooldown

The same accept and decline paths serve a change order: the token’s subject decides which, and a change order’s answer carries applied. The page never carries cost, margin or a hidden contingency line.

PO revision submission can return pending_approval while preserving the issued order. The matching approval applies the stored proposal atomically and is consumed once. An original issue approval cannot authorise a revision. Identical resubmission retains the pending request; changed proposal content, including quantity units, returns 409 APPROVAL_STALE. Resolve that request before submitting different changes. A period override reason does not change the approved content. Automatic application requires the original requester to remain an active member with procurement:manage_pos. PO currency/order-date edits and supplier bill currency/bill-date/entry-mode edits need replacement lines; otherwise they return 400. PO budget lines must use the PO currency. Statutory mileage and per diem claims must use the rate’s currency.

Matching rejects unissued orders with MATCH_PO_UNISSUED. Receipt shortages block approval with MATCH_RECEIPTS_REQUIRED. Price tolerance includes prior bills; inherited fixed markup is shared by partial bills.

An issued invoice replay stays idempotent after its period closes. A concurrent issue-date edit can return 409 INVOICE_DATE_CHANGED; reload and retry. Computed invoice lines may include taxAllocation with netAmount, individual tax components and an optional sourceLineId. Ordinary inclusive credits also return entryBasis: "gross"; their entry amount equals net plus allocated tax. An omitted basis means net, including retention-release credits. Ordinary line inputs cannot provide this calculated allocation. Price-restricted readers do not receive it, through either REST or MCP.

GET /api/v1/finance/ledger/connections includes providers.quickbooks with configured and enabled, plus unattemptedLedgers with provider keys and reasons. These are configuration facts, not production evidence.

Recognition honours the requested organisation-local cutoff, capped at period end and today. Older targets cannot replace later covered targets. Ratios calculate internal denominators without exposing unrequested cells. Foreign sold, pipeline and retainer overage without reporting valuations are unvalued. Journal rebuilds reuse exported accounting identities; corrections require compensation. Forecast recognised history is bounded by asOf.

The same validation, permissions and side effects apply through MCP’s call_public_api. Hosted query routes validate document tokens before message content and return the same 404 for invalid links.

Use get_finance_guide for the Finance User Guide and API reference supplied with the current release. Omit page for the index; then pass a page id and follow nextOffset until the page is complete. get_finance_operations lists areas, operation ids, path parameters and validated fields. Pass area for an operation list, or operation for its contract.

read_finance_operation accepts reviewed GET operations. perform_finance_action accepts reviewed mutations with confirm:true after the user authorises the specific effect. Both accept operation, optional parameters and query; actions also accept a JSON body or multipart. Responses preserve HTTP status and the route’s data or error. A confirmation preview reports confirmationRequired:true and changes nothing.

For example, after reading and reviewing an invoice:

{
"operation": "POST /finance/invoices/:id/issue",
"parameters": { "id": "<invoice-id>" },
"body": {},
"confirm": true
}

These tools use your live route permissions, financial visibility and approval rules. They are available to interactive workspace chat and unscoped generated credentials. Component, automation and explicitly scoped sessions keep their narrower tools. Bank details/payment files, signatures, statutory attestations, provider connections and deferred provider execution have explicit exclusions in the catalogue. call_public_api retains its existing external-client contract. See Finance with Ru for user guidance.

Do not send actor or organizationId when creating a quote or retainer. These fields come from your authenticated credential; supplying them in the request body returns 400.

Retainer hour quantities are returned as decimal strings with four places. rolloverCapHours accepts nonnegative hours with up to four decimal places; excess precision, negative values and overflow return 400 on create or update.

Retainer period reads include manualAdjustments. Add and reverse routes return { adjustment }, with appliedQuantity (signed hours) and budgetLineId. For hours adjustments, send a positive quantity and optionally the period’s budgetLineId. For amount-only records, send a positive minor-unit amount. All adjustments require a reason.

Reversal returns the existing reversal on replay. A 409 with ADJUSTMENT_EFFECT_UNKNOWN means legacy history lacks the actual effect; ADJUSTMENT_ALLOWANCE_CONFLICT means later changes prevent exact restoration. Review the period before recording a separate correction. Closed accounting periods and closed consumption windows block new changes.

WIP metrics and reporting totals use chronological applied adjustments. Suppressed write-offs do not reduce later recognition. The applied proportion uses the recorded reporting value; missing values are excluded and counted in coverage. Existing response fields and access requirements are unchanged.

Self-billing eligibility and RCTI issue validate confirmations against the current statutory rule version and value. Matching an old rule name alone does not confirm changed wording. Existing unverified-rule refusals apply.

Recognised revenue grouped by person or role uses approved billable values recorded for the same economic month, including when month is not selected as a grouping. Shares reconcile exactly to the reporting-currency total. Missing or incompatible rate evidence leaves that line and month unallocated; later-month work does not change earlier-month shares. Existing metric access and filters apply before allocation.

WIP write-off reversals restore the source’s applied amount, including when the requested write-off exceeded available WIP. Native balances, ageing and reporting metrics share this behaviour; reversal does not create new revenue.

Retainer hour adjustments and carry change the delivery allowance while leaving priced quantity and the agreed fee unchanged. Period consumption returns the resulting allowanceHours. Budget line reads expose the signed, read-only allowanceAdjustment separately from quantity. Pricing changes that conflict with an existing allowance adjustment return 409 with RETAINER_ALLOWANCE_CONFLICT, including at change-order application.

Retainer detail includes carryLots, with originPeriodId, generatedFromPeriodId, parentRolloverId, originPeriodStart, expiresAfterPeriodStart, allocated and remaining quantities, and expired. Forwarded unused hours preserve the original expiry and share the per-line cap with new carry. Expired lots remain in history but are excluded from available and end-settlement totals. Money fields follow the price lens.

The sold metric allocates the budget’s recorded reporting value exactly across its lines before applying line filters. Source currency and amount must match the applied budget version. Missing or inconsistent valuations remain unvalued; later exchange-rate changes do not alter this read.

Time approval, unapproval, catch-up, invoice issue, credit and void actions reconcile cumulative recognition within their transaction, including the sold limit, line cap and posted write-ups. Recognition history includes any required line adjustment. Repeated partial credits reduce the remaining recognised share; voided invoices remain excluded from later runs. An older run preserves later source postings. If budget lines change during run preparation, the run returns 409 RECOGNITION_RUN; retry with the current budget. The same behaviour and permissions apply through MCP and supported Ru actions.

Historical catch-up cannot post into a closed source month. Reopening requires your normal authority and approval. For a soft-closed month, provide a reason; the change is recorded with that reason. The API accepts an optional reason string of up to 2,000 characters. Ru can supply your reason for an authorised catch-up.

The forecast retains the positive unbilled net amount of each schedule item after issued invoice lines and issued reissue credits. Write-off credits do not restore an obligation; voiding an invoice does. Percentage schedules exclude time and materials. Conflicting schedule links or currencies return HTTP 409 with code SCHEDULE_SOURCE_ALLOCATION and invoiceId, invoiceLineId and scheduleItemId details. Resolve the identified allocation before retrying.

Invoice-based recognition and cumulative credit reversals count issued documents only. Approval alone does not change recognised revenue. These rules apply equally through the public API and MCP.

Composer candidates include previously invoiced schedule items that still have an unbilled amount. remainingAmount is a money object; it and amount are omitted without price visibility. Use the existing schedule_item method to draft the remaining original line allocations. An edited line above its remaining allocation returns HTTP 409 SCHEDULE_AMOUNT_EXCEEDED at issue. A schedule item remains exclusively reserved while its draft is live.

Budget entry pricing and source invoices (9 September 2026)

Section titled “Budget entry pricing and source invoices (9 September 2026)”

New quote conversions return budget.pricingSettings with priceEntryMode, taxRoundingMode and roundingMode. Budget lines retain entered unitPrice and expose price-lens-protected netUnitPrice for net hourly valuation. Sold and schedule amounts remain net. pricingTaxIdentity is server-maintained; clients choose taxRateId, not a tax snapshot. Budget scenario snapshots include their frozen pricingBasis; quantities retain four decimal places.

Invoice composition from finance sources defaults to exclusive entry. Explicit inclusive source composition, a change to inclusive on a sourced draft, or adding sources to an inclusive draft returns 409 SOURCE_PRICE_ENTRY_MODE. Older sourced inclusive invoice drafts have a blocking validation issue until corrected. Standalone manual inclusive invoices remain supported. The same validation applies through /api/v1, call_public_api, draft_invoice and Ru’s reviewed Finance operations. Existing permissions and confirmation apply.

Quote versions expose read-only pricingTaxRates, keyed by rate ID, with id, name and components (name, rateBp). These terms are included in the approval and remain unchanged after sending. A settings edit cannot change the sent quote’s calculated totals through REST or MCP.

On the hosted quote view, a legacy option without recorded tax treatment has a null amount. Show it as unavailable and prevent selection. A request to price or accept such a selection returns 409 QUOTE_TAX_SNAPSHOT_MISSING; the sender must prepare a revised quote with a recorded treatment.

MethodPathCapabilityResult
GET/api/v1/finance/payment-recoveriesfinance:view_invoices{ recoveries }; optional client, billing entity, asOf and openOnly filters
GET/api/v1/finance/payment-recoveries/:idfinance:view_invoices{ recovery, receipts, payments }
POST/api/v1/finance/payment-recoveries/:id/receiptsfinance:record_paymentsRecords recovered cash; returns 201
POST/api/v1/finance/payment-recoveries/:id/receipts/:receiptId/reversefinance:record_paymentsCorrects one receipt; returns 200

A receipt requires positive minor-unit amount, calendar receivedOn and an idempotencyKey of 1 to 120 characters. Optional fields are method, reference and reason. Use the same key to retry the same receipt. Currency, client and billing entity come from the recovery; overriding them is refused. A correction requires calendar reversedOn and a nonblank reason.

Amounts cannot exceed the remaining balance. Dates cannot precede the latest recovery activity and must pass period rules. State conflicts return 409, including RECOVERY_EXCEEDED, RECOVERY_DATE and IDEMPOTENCY_CONFLICT. Cross-organisation IDs return 404; money is omitted without the prices lens. These operations are also available through Ru and the MCP public API path with the same validation, authority and side effects.

Receivables reads include separate recoveries; invoice payment reads include receiptReversals. Statements include recovery and payment_reversal entries. POST /api/v1/finance/statements also accepts optional ISO currency, alongside billingEntityId, so a statement can use the recovery’s recorded currency.

GET /api/v1/finance/forecast and get_forecast accept billingEntityId, clientOrganizationId, projectId and budgetId; supplied filters intersect. Malformed ids return 400 through REST and the route bridge. Valid ids with no matching source return empty result arrays. Invoice and supplier-bill residuals use their attributed lines; reimbursement runs retain expense-split attribution. General operating costs are excluded from client/project/budget views. Budget views exclude pipeline quotes and future retainer fees without a budget. Recovery balances have client/entity attribution only. Capacity bookings use the same filters; their capacity denominator is the related team’s working hours less closures and leave, before bookings and external meetings. Lateness output includes only clients with matching expected receipts.

Weekly API totals cover the whole returned series. To match the browser’s cash heading, sum cashWeekly.weeks.slice(0, 13) net amounts in the view currency. Access checks and financial lenses apply identically through the public API, call_public_api, Ru’s operation reader and the named forecast tool.

pipeline_weighted and the Sales pipeline use recorded reporting values. Defaults are 10%, 15%, 30% and 50% for draft, pending approval, sent and viewed. An explicit probability, including zero, takes precedence. Weighting rounds half up per quote. Overage records reporting valuation when a consumption window closes and is dated to its last included day. Unchanged recalculation or re-close preserves the value; missing historical foreign evidence remains in coverage.

Metric filters reject malformed UUIDs with 400. Project/client/entity filters intersect caller scope. Budget/service/person filters exclude the unattributed whole-quote price. Overage supports budget/project/client/entity attribution; service/person filters exclude the whole-period amount. Draft quote creation, editing, copying and revision pricing commit together. Copied schedule allocations point to the new line IDs. Existing price lenses and generated-token/MCP authority remain unchanged.

The metrics catalogue includes standard_billable_value: approved billable time at its recorded standard rate in the reporting currency. realisation divides recognised revenue by that value and returns basis points. Both require the prices lens. An unvalued source makes the ratio null and increments coverage; a companion denominator requested only to compute the ratio is omitted from the result.

Approved used and delivery time cost retain their recorded currency evidence. Unapproval clears it; reapproval records new values. Transfer keeps the price unless reprice: true is explicit. A different destination currency without repricing returns 409 TIME_TRANSFER_CURRENCY. Repricing retains recorded cost in matching currencies, and marking non-billable clears billable value while keeping cost. Existing routes expose the same behaviour through MCP’s call_public_api.

Existing expense routes derive header billability and sell totals from the split lines. Fixed markup is allocated only to billable weights; non-billable lines carry zero sell value. Budget used includes approved billable expense and bill lines, and budget cost includes approved purchase cost. Recorded currencies and missing-value coverage apply to both. Internal valuation fields are not added to the public projection.

Supplier-bill voids produce dated negative used/cost facts and preserve earlier report months. Project filters include source-project time funded by a client retainer. REST and MCP calls share these behaviours and their existing permission and organisation checks.

Cost metrics include unbilled receipt accruals, using the same dated values as budget caches. Receipt reversals cannot consume quantity already matched to an active supplier bill. Partial movements retain their recorded currency shares, and the final movement takes the exact remainder. Bill voids use the selected voidedOn economic date. Self-billing relieves time cost on the bill date and restores it on the void date, preserving the original work month.

POST /finance/rcti returns 400 for caller-provided consumptions, correctsRctiId or correctionReason. Sources are reserved by the worker and replacement identity is set by the correction action. Unapproving or moving reserved time returns 409 TIME_ENTRY_SELF_BILLED; editing or voiding the underlying self-billed document through the ordinary bill route returns 409 BILL_SELF_BILLED. Draft creation and document voiding commit with their source reservations. Asynchronous supplier-bill approval updates the document status in the same transaction. REST and MCP share these validations, permissions and effects.

The metric catalogue includes capacity_hours, grouped by month, person or role, with hours scaled by 10,000. billable_utilisation uses approved billable hours divided by these available hours, rounded half-up to basis points; zero available hours returns null. Send explicit from and to dates for comparison. Without dates, the range starts in the first approved work month (or the selected end/current month without work) and ends at the later of today and the last approved work day. Reversed ranges and capacity windows over a century return 400.

Forecast forecastUtilisation includes userName and unbooked months, using only days after asOf through the exact horizon date. Subject filters select the related team once per person; this is not additive project capacity. Capacity uses the current default organisation role, while time uses its recorded role. Existing access and financial lenses apply equally through REST and MCP, including get_forecast and call_public_api.

Change-order actions return CHANGE_ORDER_SCOPE_CHANGED (409) if the budget’s project or billing entity changes during the request. The request rolls back; refresh the budget and retry. Preparing and withdrawing drafts remains possible in closed accounting periods, but application retains its period-closed refusal. The same rules apply through generated API keys and MCP.

Bank confirmations and reversals commit their settlement and match together. A split client settlement returns one payment with multiple allocations. An identical replay returns the existing confirmation; changing the target, date, method, candidate hash or splits returns 409. A reversed line can receive a fresh settlement. Both supplied opening and closing balances must reconcile against all rows, including overlaps; unread rows or a difference return STATEMENT_BALANCE_MISMATCH without importing.

Reimbursement detail includes run.paidOn and settlements[]. Each settlement has id, kind, economicOn, signed amount, reversesSettlementId, reference and reason. Confirmation and correction obey the same permissions and period rules through generated-token REST and call_public_api.

Retainer close and invoice operations keep hourly allowances separate from monetary carry credits. The invoice source kind retainer_carry identifies the signed credit or correction. Existing permissions and price visibility apply through REST and MCP. See Retainers.

Pricing adoption is available through the shared Finance routes. Read GET /finance/budgets/:id/quote-pricing-review, then send its reviewHash and a reason to POST /finance/budgets/:id/adopt-quote-pricing. Active budgets open a correction for normal approval. For templates, read GET /finance/budget-templates/:id/pricing-review?sourceBudgetId=..., then send sourceBudgetId, reviewHash and reason to POST /finance/budget-templates/:id/adopt-pricing. Direct REST paths use the /api/v1 prefix. Generated-token MCP callers use the same paths through call_public_api. Management scope, price visibility and evidence validation apply to both. See Budgets for the review workflow.

The accountant export endpoint GET /api/v1/finance/periods/:id/export-pack accepts format=csv|pdf. CSV is the default and returns the files envelope. PDF returns application/pdf, an attachment filename and X-Close-Generation. Both formats require export permission and unrestricted organisation-wide price and cost access. The same access requirement applies to the complete JSON close report and its history. Any project or client restriction on prices or costs returns 403, including a restriction outside the selected period. No partial report is returned. These checks apply before creating a report or rendering a download, including older report generations. They also apply through call_public_api. Unknown formats return 400 and insufficient access returns 403. The PDF uses the same recorded generation as the CSV pack.

GET /api/v1/finance/periods/:id/review/:key returns rows, total, limit, offset and scope. Rows contain id, label, status, date and link. Use limit from 1 to 200 (default 50) and a non-negative offset. The total remains available when a requested page is empty. Results contain no amounts.

Use the checklist item’s key, except recognition_run, whose link opens Revenue with the entity and month selected. The scope is period, or billing_entity for ready schedules, received purchase orders and old drafts. The latter queues include outstanding records across all dates for that entity.

You need a period-read capability and an organisation-wide capability for the source: time approval for time; expense review, approval or management for expenses; purchasing read for purchase orders; invoice read for invoices; payment or receivables management for payments; budget, invoice drafting, issuing or recognition for uninvoiced work; budget or recognition for WIP; time approval or recognition for missing valuations. A scoped grant or period closing alone does not grant source access. Invalid keys or paging return 400; inaccessible source queues return 403; foreign periods return 404.

GET /api/v1/time/approvals accepts segmentId to read one exact segment within your current status filters and approval access. These reads are also available through call_public_api with the same permissions and validation.

Supplier create/update accepts nullable UUIDs internalBudgetId and internalCreditBudgetLineId with isInternal: true. The credit line must be active, non-billable and on the selected active internal budget. Supplier management permissions apply. New bill drafts freeze the destination; saving a legacy draft without one adopts the current supplier default.

Bill approval requires complete receiving allocations, no purchase tax and writable receiving and providing periods. Missing or invalid setup returns 409 INTERNAL_CHARGE_SETUP_REQUIRED; foreign credit lines return 404. Existing allocation validation may return PURCHASE_INVALID first.

Bill detail includes internalCharges with the cost lens. Each entry has id, supplierBillLineId, providingBudgetId, providingBudgetName, providingBudgetLineId, date, description, reversesChargeId, and signed internalCreditAmount and internalReportingCreditAmount money values. A null value means missing recorded currency evidence. Cost access is sufficient to read these values without price access. No cost access omits the collection.

Approval records both sides together; retries do not duplicate credits. Voids record exact inverses on their correction date. Internal bills cannot be paid, even after the supplier’s classification changes. Credit history also prevents archiving or changing its line to billable (INTERNAL_CHARGE_HISTORY, 409). Generated-token REST and call_public_api share these contracts. The named draft_supplier_bill tool creates a draft and freezes the same destination. See Purchasing.

Source values and omitted scenario estimates

Section titled “Source values and omitted scenario estimates”

Money metric cells may include documentValues, with separate currency, amount, decimal and unvalued entries. Amounts are minor-unit strings. Reporting-currency fields retain recorded values. Do not sum entries across currencies. Used and cost source values use the recorded budget currency.

Budget lineage supports sold, used, cost, invoiced, committed, forecast, remaining, margin and hours in figure, with optional UUID lineId. BAS sales and purchase sides follow price/cost permissions; net GST needs both. These contracts also apply through MCP’s call_public_api.

When replacing scenario lines, send the existing scenario-line id to retain an omitted unitPrice or estimatedCost. Send null cost to clear its estimate. A stale or foreign line ID is refused. New lines can omit id.

Billing-entity create/update accepts nullable UUID logoAssetId. Upload the file using the Assets API, then save its ID on the entity. The asset must belong to the organisation, have organisation or public visibility, and contain an active PNG/JPEG image up to 500 KB. Invalid image input is 400; unavailable or foreign assets are 404. Finance settings access is required. New invoice, quote and change-order documents retain their own copy of the logo.

Invoice templates, previews and email delivery

Section titled “Invoice templates, previews and email delivery”

Read templates with GET /api/v1/finance/document-templates and styles with GET /api/v1/finance/document-styles. If a list request fails, keep its error visible and retry the read when appropriate. Treat only a successful empty response as an empty list. Each request checks current access and organisation binding, including through call_public_api. Simultaneous first reads share the same built-in templates and style.

Copy a template with POST /api/v1/finance/document-templates and copyOf. Omitting styleId creates an independent copy of its style in the same transaction. An explicit styleId selects an existing style. Select a template on a draft with PATCH /api/v1/finance/invoices/:id and documentTemplateId. Issued documents keep their saved template, branding and renderer version.

POST /api/v1/finance/document-templates/:id/preview returns { html } using sample invoice details, without saving changes. It accepts optional language, blocks[{ blockType, options }] and style containing font, accentColor, logoSize, paperSize and density. It requires finance:manage_settings or finance:manage_integrations. Invalid settings return 400; an unknown or foreign template returns 404. Template and style reads also allow these capabilities.

Invoice preview and PDF reads still require invoice access and the price lens. PDF downloads return the saved bytes. A document retry also checks that the document belongs to the invoice named in the URL.

Invoice sends with mode: "schedule" require a future scheduledFor; missing or past dates return 400. Email sends return a queue job, not a delivery receipt. Inspect sendJobs from GET /api/v1/finance/invoices/:id/documents for the result. Email includes the PDF and a secure document link; a declined transport does not mark the invoice sent.

These operations are available through call_public_api with the same permissions and organisation binding. Its standard confirmation requirement applies to POST/PATCH calls, including the unsaved sample preview. Hosted document links use separate document-token authority and do not grant workspace API access.

New document line tables show saved quantities with up to four decimal places, without trailing zeros. Earlier issued documents keep their saved presentation and PDF bytes. Amounts and endpoint schemas are unchanged across generated-token REST and MCP.

These GET operations return standalone html, title, language and frozen:

PathRequired access
/api/v1/finance/quotes/:id/previewQuote read access and prices; optional UUID versionId selects a version
/api/v1/finance/purchase-orders/:id/previewPurchase read access and cost
/api/v1/finance/statements/:id/previewInvoice read access and prices for the statement’s client
/api/v1/finance/rcti/:id/previewPurchase read access and cost; also retains payload and hash

Draft previews use current saved values. Sent and issued previews use the retained document’s contents and layout. Statements preview their saved ledger. Responses use Cache-Control: no-store. Missing retained document history returns 409; invalid identifiers return 400, unavailable records 404, and missing access 403. Opening a preview does not number, issue or send anything.

The same operations are available through MCP call_public_api, with paths relative to /api/v1, method: "GET" and the same access checks. For a selected quote version, pass query: { "versionId": "<version-id>" }.

Deleting a populated quote section with hidden, subtotal-only or package behaviour returns 409. Move its lines out, or explicitly choose full visibility and clear the section package before removing it. This preserves the client’s agreed visibility through both REST and MCP.

Document-template listing and creation accept change_order and rcti alongside the other document kinds. A template copy must keep the source kind. These operations also work through MCP’s call_public_api with the same settings permissions.

PATCH /finance/billing-entities/:id accepts matchTolerancePercentBp, matchToleranceAmount and abaOriginator. These are also the settings used by the Purchasing policy forms. A percentage is an integer from 0 to 10,000; the cash amount is a non-negative minor-unit string. This operation does not change a confirmed bank account.

The invoice composer accepts an empty entryIds array alongside selected expenseIds, purchaseOrderLineIds or supplierBillLineIds. A purchase-only draft reserves its sources through both REST and MCP; a second draft cannot reserve the same work. Standalone invoices can specify clientOrganizationId without budgetIds.

Quote and budget line writes accept nullable hoursPerDay: a number greater than zero and at most 24, with up to four decimal places. New day lines use the organisation’s daily capacity unless you supply an override. Send null on a day line to reset it to the current default. Saved day hours remain stable if the organisation changes its default later.

Changing serviceTypeId adopts the activity’s default billing type, unit and rate-card role. Explicit fields in the same request take precedence. Without a linked role, the old rate is cleared to zero. Hour/day changes convert the saved rate; an explicit unitPrice overrides that calculation. Selecting percentage billing starts at zero when no percentage is recorded. Enter percentBp and percentageBase to calculate it. Invalid hours or unavailable activity and role references return 400 without saving a partial change.

For example, PATCH a line with { "unit": "day" } to convert its rate, then { "hoursPerDay": 6 } to use a six-hour day. To set a daily price directly, send { "unitPrice": "120000" } for 1,200.00 in a two-decimal currency.

Scenario pricing.hoursPerDay retains a copied line’s day basis when the scenario is promoted.

These rules also apply to MCP call_public_api. update_quote_draft exposes serviceTypeId, billingType, unit, hoursPerDay, rateCardRoleId, percentBp and percentageBase inside line. Sent quotes remain frozen, and active budget price changes use change orders. Existing permissions and organisation scope apply. See editing quote lines.

For country: "AU", identifiers.bsb must contain six digits, either 123456 or 123-456. Outer whitespace is trimmed. Both bank proposal routes apply this to billing entities, suppliers and employees. Invalid BSBs return 400 with code: "BANK_BSB_INVALID" and a six-digit format explanation in error.

Confirmation also checks older pending proposals. A refused confirmation leaves the current account, default and pending proposal unchanged. Withdraw or reject the pending change, then submit corrected details. Existing confirmed accounts and document snapshots are unchanged. Other country formats keep their existing rules. This is a format check, not verification that a branch or account exists. The same validation and failure behaviour apply through MCP call_public_api.

Supplier and employee bank proposals use the same confirm/reject routes as billing entity accounts. Confirmation returns { change, account }, with masked account identifiers. The proposer cannot confirm their own change (403). Rejection accepts an optional reason of up to 500 characters; a proposer can use rejection to withdraw their pending change. Rejection retains existing accounts.

After an uncertain response, read the owner’s bank accounts and change history before trying again. A repeated or expired confirmation returns 409. Reads and decisions require current bank-details permission in the credential organisation. The same operations and validation are available through MCP call_public_api.

Read GET /api/v1/finance/jurisdictions/rules with billingEntityId and optional onDate (YYYY-MM-DD). Each row includes the version applicable on that date, verification state and valueHash. Read access needs finance:manage_settings or finance:view_invoices.

After reviewing applicability, call POST /api/v1/finance/jurisdictions/rules/:ruleId/confirm with billingEntityId, expectedValueHash from the read and an optional note (up to 2000 characters). Confirmation needs finance:manage_settings. The hash is optional for existing clients; supply it to protect the reviewed value. A changed value returns409; a malformed hash returns400. Reload and review again before a new decision. Foreign billing entities return404 and missing permission returns403.

Identical repeated confirmations return the original record, including concurrent requests. Confirmation does not issue a document or bypass its other checks. MCP call_public_api exposes the same operations, fields and permissions. Supply read parameters in query; mutations require confirm: true.

POST /api/v1/finance/supplier-bills/:id/attachment attaches a PDF, PNG, JPEG or WebP to an ordinary draft bill. Supply fileName, contentType, canonical contentBase64 and expectedEvidenceId. Use null for the first file, or the current attachment’s ID when replacing it. The decoded file must contain between 1 byte and 10 MB and match its declared type. An optional reason of up to 2000 characters permits an otherwise authorised change in a soft-closed period.

Upload requires procurement:manage_bills and cost access for the bill’s project and its current client. Finance and the workspace entitlement must be enabled. The result includes attachment: { id, fileName, contentType, byteSize, sha256, uploadedAt } in the bill detail. Metadata is absent without cost access; file bytes are never included in ordinary bill responses.

An explicit repeat of the same current bytes returns the same attachment. A stale expected ID with different bytes returns 409. Replacement is refused after submission, on self-billed or already asset-linked bills, and in closed periods. Older versions remain retained until an explicit draft deletion. Generic bill PATCH fields cannot overwrite a retained manual file. An attachment does not submit, approve or pay the bill; its content is bound to the later approval.

GET /api/v1/finance/supplier-bills/:id/attachment downloads the current file. It requires purchasing read permission and scoped cost access, with private, no-store response headers. Each read uses consistent permission, document and file information. Completed access or scope changes apply on the next read. Unavailable bills/files return 404, missing permission 403, malformed input 400, unsupported file contents 415 and oversized files 413.

Both routes are available through MCP call_public_api. POST takes the same JSON body and confirm: true; GET returns bodyBase64, encoding and byteLength. After an uncertain upload result, read the bill and check its attachment before sending another request. See attaching an invoice.

POST /api/v1/finance/supplier-bills/:id/matches changes purchase order links on an ordinary draft. It needs procurement:manage_bills and cost access to the bill’s project and current client. Send the current expectedContentHash, optional period reason, and mappings containing every current bill line exactly once:

{
"expectedContentHash": "<current-content-hash>",
"mappings": [{
"billLineId": "<bill-line-id>",
"purchaseOrderLineId": "<purchase-order-line-id>",
"usePurchaseOrderBudget": true,
"expectedPurchaseOrderBudgetLineId": "<reviewed-budget-line-id>"
}]
}

Use null purchaseOrderLineId to remove a link. Omit the two budget fields when no allocation is being adopted. An unallocated bill line can explicitly adopt an eligible order allocation in the same currency. The expected budget-line ID must still match when saved. An existing allocation must match the order line. Orders must be issued and share supplier, project, billing entity and currency. Each order line can appear once; a request accepts up to 200 bill lines.

The response contains the saved supplierBill. Quantities, amounts, tax, currency valuations, invoice attachment and on-charge choices stay unchanged. Saving neither submits nor approves the bill. Read it again to confirm the saved links, then GET /:id/match to review receipt and tolerance checks. Preview needs cost access to every referenced order as well as the bill.

Malformed or incomplete line sets return400. Missing or foreign records return404, permission denial returns403, and changed bill or allocation returns409 BILL_MATCH_CHANGED. State, period, allocation, order and fixed-markup conflicts also return409. Self-billed invoices use their own workflow. A repeat of the completed mapping does not repeat the matching change; permission checks for every selected order and period audit rules still apply. Both operations are available through call_public_api, with confirm:true for the POST.

Retained supplier bill matching visibility

Section titled “Retained supplier bill matching visibility”

Bill detail, bill mutation responses and GET /api/v1/finance/supplier-bills apply cost permissions separately for each bill’s project and current client. Retained matching figures also require current cost access to every referenced purchase order’s project and client. If an order is unavailable or access is denied, matchSnapshot and policy-check details are omitted and matches is empty. The bill’s own permitted figures remain available. Stale check details are omitted, and reading does not change saved history or totals.

These rules also apply to MCP call_public_api, list_supplier_bills and draft_supplier_bill responses. Existing inputs and write permissions apply. See matching visibility.

POST /api/v1/finance/budgets/:id/save-as-template accepts a non-empty name and returns 201 with { template }. Manual budgets may have null pricingSettings; saving preserves that absence along with currency and billing entity. Older quote-converted budgets without recorded pricing return 409 until quote pricing has been reviewed and adopted. A failed save creates no partial template. Invalid input returns 400, foreign budgets return 404, and missing or revoked template-management permission returns 403. MCP callers use the same operation through call_public_api with mutation confirmation.

Use PUT /api/v1/finance/tasks/:taskId/budget-line with { "budgetLineId": "<line-uuid>" } to map one task, or null to clear it. The response returns taskId, budgetLineId and previousLineId. For several tasks in one project, use PUT /api/v1/finance/tasks/budget-line with taskIds and budgetLineId; the response returns the number mapped.

These writes require finance:manage_budgets for the affected scope. The line must fund the task’s project. Invalid IDs, foreign records and denied permission are refused. Task details, personal task lists and project task reads then return the saved nullable budgetLineId. Unrelated task updates retain it.

Both writes and reads are available through call_public_api, using paths without /api/v1. Full get_task and list_tasks records also include the ID; compact task lists omit it. Budget amount visibility retains its own permissions.

Bill detail, lists and mutation responses use the bill’s project and that project’s current client for cost access. Purchase order responses and document previews use the project and client recorded on the order. Creating an order or explicitly changing its draft project sets the client from that project; clearing the project also clears the client. Later project-client changes do not rewrite existing orders. Organisation, client and project grants apply in that order, followed by the normal Finance lens rules.

Each list row uses its own scope. Purchase read and management capabilities stay separate from cost visibility. Foreign bill projects return 404 before saving. Manual attachment and matching writes recheck the bill’s project and client. If either changed after permission was checked, the request returns 409 BILL_ATTACHMENT_CHANGED or BILL_MATCH_CHANGED without saving. Reload before trying again. A repeated mapping also requires cost access to each selected order.

Generated REST and MCP call_public_api share these rules. Named raise_po, draft_supplier_bill and list_supplier_bills return the same scoped document figures. See Purchasing for the browser workflow.

A bill’s payment history shows the amount allocated to that bill when you can view its costs. A payment covering several bills shows its full total only when you can view costs for all of them. Viewing any unallocated amount also needs organisation-wide cost access. Reimbursement-linked payment totals are omitted from bill history.

Supplier-payment record and reversal responses show amounts only with cost access to every contributing bill, using each bill’s current project and client. Selling-price access alone is insufficient. A record response also checks every selected bill, including bills that receive no allocation; allocations takes precedence over billIds when both are supplied. An unallocated remainder needs organisation-wide cost access as well. Reimbursement-linked totals, reversals without a valid original and amounts with incomplete source information are omitted.

An authorised write still succeeds when its amounts are hidden. The response keeps payment identity and the successful HTTP status but omits amount, reportingAmount, realisedFxAmount, allocated and unallocated. Visible payment amounts retain { amount, currency }; the two allocation totals remain minor-unit strings. Explicit API overpayments remain supported, including payments entirely unallocated to an already-paid bill. These need organisation cost access and cost access to every selected bill to show the record result. Reversing wholly unallocated ordinary cash needs organisation cost access. The bill’s own visible allocation in payment history remains independent of the whole-payment total. The same rules apply through MCP call_public_api.

Supplier spend needs organisation-wide cost access and access to every bill included in the period. The supplier list and named get_supplier return spendYearByCurrency, sorted by currency, as { amount, currency } pairs. Amounts are exact minor-unit strings in the bill’s original currency, including tax. For example, [{ "amount": "10000", "currency": "AUD" }, { "amount": "5000", "currency": "USD" }] means AUD 100 and USD 50. Do not add the currencies or label them with the supplier’s default. No exchange-rate conversion is applied.

The period runs from 365 days ago through today in the organisation timezone, including those dates. Drafts, voids and future bills are excluded. An empty array means no included bills; a zero total retains its currency. The legacy spendYear scalar remains for zero or one currency and is omitted for mixed currencies. Use the array for new integrations. Both spend fields are omitted when any included bill is restricted, including in get_supplier; you never receive a partial total. A supplier beyond the first page has the same rules. The supplier detail route continues to return master data, contacts and the freelancer link. Open counts and last-bill date are independent of the spend period. These reads do not change saved bills.

These complete reports return 403 if any contributing order or bill is restricted:

  • GET /api/v1/finance/supplier-bills/ageing
  • GET /api/v1/finance/accruals/periods/:periodId
  • GET /api/v1/finance/accruals/pass-through

A project or supplier filter does not grant access to linked bills. Period access includes earlier receipts, historical and voided bills, and zero-net balances. The currency query selects the computed balance; it does not limit the stored currency rows returned. All returned currencies need access. Without a requested or stored currency, the report computes the first currency, in alphabetical order, from receipts dated on or before the period end.

GET /api/v1/finance/supplier-payments/payable lists only bills whose costs you can view. It keeps its organisation cost and payment capability requirements. An empty list is not a complete statement of what the organisation owes.

PO detail can omit line billed, line accruedAmount and totals.billed when a linked bill is restricted, while retaining the order’s permitted amounts. PO list rows do not include computed detail totals. Saved matching figures need access to their historical bill contributors; live previews need access to current contributors. A denied refresh does not substitute zero.

Permitted purchase report amounts need cost access independently of selling prices. These rules apply equally to generated REST credentials and MCP through call_public_api. See Purchasing and Paying suppliers for the browser workflow.

Use get_profitability with projectId or clientOrganizationId when your financial access is limited to that subject. Amounts follow its price, cost and profit permissions. Cost-only access keeps delivery cost amounts but omits revenue and margins. If you omit the subject, organisation-wide visibility applies, so a scoped-only reader can receive no money metrics.

The tool uses the same metrics, filters and date validation as GET /api/v1/finance/metrics. Its default grouping remains client. Successful responses keep rows, totals, definitions and coverage at the top level with success: true. A route failure includes success: false, HTTP status and the route’s response in data, with error and code when supplied by the route. Invalid dates or filters return 400. Expired grants, revoked access and inactive membership do not retain report access.

A session limited to one client always uses that client, and refuses a different client argument. A tool allowlist must include get_profitability. Job-bound sessions cannot use this report because it has no job-level financial scope; use your ordinary generated API key or unscoped MCP session. See MCP Server for credentials and Finance Reports for metric definitions.

Staff GET /finance/supplier-portal/uploads and GET /finance/supplier-portal/uploads/:id return a cost lens for each row. Proposal amounts, nested quantities/notes, original asset/hash and internal rejection details are absent when any linked purchase order or bill denies cost access. Purchase orders retain their client scope; bills follow the current client of their project. Unattached uploads use organisation cost access. Cost-only access preserves purchase amounts without requiring prices.

POST /finance/supplier-portal/uploads/:id/accept needs bill-management access. Creating a draft from a linked upload also needs source cost access, including when review fields or a different target project are supplied. Denial changes nothing. Complete untaxed unattached proposals retain acceptance without cost visibility when creating an unscoped draft. Supplying a target projectId for an unattached upload requires organisation cost access. Changing or clearing an existing draft bill’s projectId requires cost access to its original scope. A repeated accepted request returns the existing bill; response projection follows current access. Decline keeps bill-management authority and returns the same protected projection. REST and call_public_api share these rules. Original file reads follow supplier invoice source access.

GET /api/v1/finance/periods/:id/assistant returns a complete live brief only with unrestricted organisation-wide prices. Any active project or client price deny returns 403, including a restriction outside the selected month. A scoped allow alone is insufficient. Cost restrictions do not block this sales/WIP brief. A successful response reads policy and figures from one consistent snapshot and creates no records.

Period list/detail and ensure, soft-close, close and reopen responses omit current and saved checklist amounts under those price restrictions. Counts, status and review links remain available under the route’s entry permissions. Period audit history applies the same omission. Stored history is unchanged, and closing permission remains independent of price visibility.

These rules also apply through call_public_api. Named get_period_checklist uses the same checklist projection for either selector. Complete close reports and exports retain their separate price-and-cost requirements. See Month End.