Drafts, lines, deposits, progress billing and reminders. Every write here produces or edits a draft; sending is an action, approved by a person or the owner’s capped rule.
List invoices, newest first. A discarded draft is left out unless discarded=true.
Scope
invoices:read
Effect
Read only
MCP tool
list_invoices
Retries
Safe to repeat
Parameters
List parameters
Name
In
Type
Notes
limitoptional
query
integer
min 1 · max 50
cursoroptional
query
string
up to 256 chars
statusoptional
query
enum
One of draft, sent, viewed, partiallyPaid, paid, overdue, void, uncollectible
clientIdoptional
query
id
—
discardedoptional
query
boolean
—
Returns
data: Invoice[]. Pass meta.cursor back as cursor for the next page.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/invoices" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
invoices
Create draft
POST/v1/invoices
Create a DRAFT invoice. Never a send.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
create_draft_invoice
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Create draft request body
Field
Type
Notes
businessIdrequired
id
—
clientIdrequired
id
—
currencyoptional
Currency
—
languageoptional
string
up to 8 chars
lineItems[]optional
object[]
up to 200 items
lineItems[].idoptional
id
—
lineItems[].descriptionoptional
string
What the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptional
integer
Quantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptional
enum
How the price applies: fixed for one flat charge, hourly, daily, perUnit, percentage… Optional with a presetKey. — One of fixed, hourly, daily, weekly, monthly, perUnit, perMile, perKilometre, percentage
lineItems[].unitPriceoptional
object
Price per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequired
integer
—
lineItems[].unitPrice.currencyrequired
Currency
—
lineItems[].rateBasisPointsoptional
integer
—
lineItems[].taxTreatmentoptional
string
—
lineItems[].taxRateBasisPointsoptional
integer
—
lineItems[].discountBasisPointsoptional
integer
—
lineItems[].presetKeyoptional
string
The key of one of the business’s products and services (get_business_context lists them); the server fills what the line leaves out. — up to 64 chars
curl -X GET "https://api.billingmart.com/v1/invoices/{invoiceId}" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
invoices
Update draft
PATCH/v1/invoices/{invoiceId}
Update a DRAFT invoice’s lines, fields and notes.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
update_draft_invoice
Retries
No Idempotency-Key on this route
Parameters
Update draft parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Update draft request body
Field
Type
Notes
expectedUpdatedAtoptional
string
up to 40 chars
lineItems[]optional
object[]
up to 200 items
lineItems[].idoptional
id
—
lineItems[].descriptionoptional
string
What the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptional
integer
Quantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptional
enum
How the price applies: fixed for one flat charge, hourly, daily, perUnit, percentage… Optional with a presetKey. — One of fixed, hourly, daily, weekly, monthly, perUnit, perMile, perKilometre, percentage
lineItems[].unitPriceoptional
object
Price per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequired
integer
—
lineItems[].unitPrice.currencyrequired
Currency
—
lineItems[].rateBasisPointsoptional
integer
—
lineItems[].taxTreatmentoptional
string
—
lineItems[].taxRateBasisPointsoptional
integer
—
lineItems[].discountBasisPointsoptional
integer
—
lineItems[].presetKeyoptional
string
The key of one of the business’s products and services (get_business_context lists them); the server fills what the line leaves out. — up to 64 chars
Create a DRAFT invoice from a work record: its client, business and currency, linked to the record, with the answers its type maps onto the invoice’s fields. Never a send.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
create_invoice_from_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Create from job parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Create from job request body
Field
Type
Notes
lineItems[]optional
object[]
up to 200 items
lineItems[].idoptional
id
—
lineItems[].descriptionoptional
string
What the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptional
integer
Quantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptional
enum
How the price applies: fixed for one flat charge, hourly, daily, perUnit, percentage… Optional with a presetKey. — One of fixed, hourly, daily, weekly, monthly, perUnit, perMile, perKilometre, percentage
lineItems[].unitPriceoptional
object
Price per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequired
integer
—
lineItems[].unitPrice.currencyrequired
Currency
—
lineItems[].rateBasisPointsoptional
integer
—
lineItems[].taxTreatmentoptional
string
—
lineItems[].taxRateBasisPointsoptional
integer
—
lineItems[].discountBasisPointsoptional
integer
—
lineItems[].presetKeyoptional
string
The key of one of the business’s products and services (get_business_context lists them); the server fills what the line leaves out. — up to 64 chars
customFieldsoptional
object
—
notesoptional
string
up to 4000 chars
internalNotesoptional
string
up to 4000 chars
termsoptional
string
up to 4000 chars
poNumberoptional
string
up to 64 chars
issueDateoptional
string
matches ^\d{4}-\d{2}-\d{2}$
dueDateoptional
string
matches ^\d{4}-\d{2}-\d{2}$
templateIdoptional
id
—
merchantAccountIdoptional
id | null
—
notesTemplateoptional
string
up to 4000 chars
languageoptional
string
up to 8 chars
kindoptional
enum
One of unbilled
throughoptional
string
matches ^\d{4}-\d{2}-\d{2}$
fromAdjustmentIdoptional
id
—
Returns
data: object
Errors this route can answer besides the shared ones: FIELD_FORBIDDEN, JOB_CLOSED, INVALID_TRANSITION, NOT_FOUND.
Link a DRAFT invoice to a work record of the same client, business and currency, move it to another, or unlink it with null. A deposit, progress or retainage invoice stays with its job.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
set_invoice_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Set job parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Set job request body
Field
Type
Notes
jobIdrequired
id | null
—
expectedUpdatedAtoptional
string
up to 40 chars
Returns
data: object
Errors this route can answer besides the shared ones: NOT_FOUND, NOT_A_DRAFT, INVALID_TRANSITION, VALIDATION_FAILED, JOB_CLOSED, EDIT_CONFLICT.
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Add line parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Add line request body
Field
Type
Notes
linerequired
object
—
line.idoptional
id
—
line.descriptionoptional
string
What the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
line.quantityMillioptional
integer
Quantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
line.rateBasisoptional
enum
How the price applies: fixed for one flat charge, hourly, daily, perUnit, percentage… Optional with a presetKey. — One of fixed, hourly, daily, weekly, monthly, perUnit, perMile, perKilometre, percentage
line.unitPriceoptional
object
Price per unit in integer minor units of the document currency ($5,000.00 is 500000).
line.unitPrice.minorUnitsrequired
integer
—
line.unitPrice.currencyrequired
Currency
—
line.rateBasisPointsoptional
integer
—
line.taxTreatmentoptional
string
—
line.taxRateBasisPointsoptional
integer
—
line.discountBasisPointsoptional
integer
—
line.presetKeyoptional
string
The key of one of the business’s products and services (get_business_context lists them); the server fills what the line leaves out. — up to 64 chars
Returns
data: object
Errors this route can answer besides the shared ones: ACTION_PENDING_ON_TARGET, NOT_A_DRAFT.
Add chosen unbilled time entries, expenses, mileage or an accepted estimate to a DRAFT: one line per record (an estimate brings all its lines), each record marked billed in the same write. Removing the line later releases the record.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
add_to_invoice
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Add sources parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Add sources request body
Field
Type
Notes
sources[]required
object[]
up to 200 items · at least 1
sources[].kindrequired
enum
One of timeEntry, expense, mileage, estimate
sources[].idrequired
id
—
Returns
data: object
Errors this route can answer besides the shared ones: ACTION_PENDING_ON_TARGET, NOT_A_DRAFT.
Draft a progress invoice for a job: earned-to-date (percentCompleteBasisPoints, cumulative) minus what the job already billed, or explicit lines; retainage withheld per retainageBasisPoints.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
create_progress_invoice
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Create progress request body
Field
Type
Notes
businessIdrequired
id
—
clientIdrequired
id
—
jobIdoptional
id
—
jobRefoptional
object
—
jobRef.idrequired
id
—
jobRef.labelrequired
string
up to 200 chars
jobRef.totalrequired
object
—
jobRef.total.minorUnitsrequired
integer
—
jobRef.total.currencyrequired
Currency
—
percentCompleteBasisPointsoptional
integer
min 0 · max 10000
retainageBasisPointsoptional
integer
min 0 · max 10000
lineItems[]optional
object[]
up to 200 items
lineItems[].idoptional
id
—
lineItems[].descriptionoptional
string
What the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptional
integer
Quantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptional
enum
How the price applies: fixed for one flat charge, hourly, daily, perUnit, percentage… Optional with a presetKey. — One of fixed, hourly, daily, weekly, monthly, perUnit, perMile, perKilometre, percentage
lineItems[].unitPriceoptional
object
Price per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequired
integer
—
lineItems[].unitPrice.currencyrequired
Currency
—
lineItems[].rateBasisPointsoptional
integer
—
lineItems[].taxTreatmentoptional
string
—
lineItems[].taxRateBasisPointsoptional
integer
—
lineItems[].discountBasisPointsoptional
integer
—
lineItems[].presetKeyoptional
string
The key of one of the business’s products and services (get_business_context lists them); the server fills what the line leaves out. — up to 64 chars
currencyoptional
Currency
—
languageoptional
string
up to 8 chars
notesoptional
string
up to 4000 chars
customFieldsoptional
object
—
Returns
data: object
Errors this route can answer besides the shared ones: INVALID_TRANSITION.
Jobs billed in this business, folded from the invoices that carry a jobRef: total, deposit, progress, retainage withheld and released. When work records are on for the workspace and the grant holds jobs:read, each row that has a record also carries a `record` summary (status, type, stage, assignee ids), and records with no invoice yet follow the folded rows. At most `limit` rows a page (default 500, up to 1000); `meta.cursor` is present only when more follow. Each row that has a record also carries `terms`, the word its business uses for records, inside the untrusted-data envelope.
Scope
invoices:read
Effect
Read only
MCP tool
list_jobs
Retries
Safe to repeat
Parameters
List parameters
Name
In
Type
Notes
businessIdoptional
query
id
—
clientIdoptional
query
id
—
parentJobIdoptional
query
id
—
rootJobIdoptional
query
id
—
topLevelOnlyoptional
query
boolean
—
limitoptional
query
integer
min 1 · max 1000
cursoroptional
query
string
up to 256 chars
Returns
data: JobSummary[]. Pass meta.cursor back as cursor for the next page.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Create
POST/v1/jobs
Create a work record as a draft: it saves with answers missing and writes no invoice. Activating it is a separate step that numbers it and counts one send.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
create_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Create request body
Field
Type
Notes
businessIdrequired
id
—
typeIdrequired
id
—
labeloptional
string
up to 200 chars
clientIdoptional
id
—
currencyoptional
Currency
—
fieldsoptional
object
—
fieldListsoptional
object
—
fieldAddressesoptional
object
—
assigneeUidoptional
id
—
estimatedValueoptional
object
—
estimatedValue.minorUnitsrequired
integer
—
estimatedValue.currencyrequired
Currency
—
parentJobIdoptional
id
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, UNKNOWN_TYPE, TYPE_ARCHIVED, VALIDATION_FAILED, TREE_TOO_DEEP, TREE_TOO_WIDE, TREE_TOO_LARGE, TREE_CLIENT_MISMATCH, TREE_BUSINESS_MISMATCH, TREE_CURRENCY_MISMATCH.
One work record: its answers, stage, assignee and next action. Without contacts:read, phone and email answers are masked and address and sensitive answers are left out; internal notes are never returned. It also carries `terms`, the singular and plural the workspace calls its work records, inside the untrusted-data envelope, so an assistant can use the same word.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Update
PATCH/v1/jobs/{jobId}
Change a work record’s label, client, assignee, estimate or answers. `null` clears one. `expectedUpdatedAt` must match the record, or the change is refused with TARGET_CHANGED.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
update_job
Retries
No Idempotency-Key on this route
Parameters
Update parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
What a work record would be asked for at a step — draft, activate, complete or bill — with no side effects: `ok`, and one entry per missing or invalid answer (key, code, message). The same list the console and the step itself use. Messages name the type’s questions and come back inside the untrusted-data envelope.
Errors this route can answer besides the shared ones: NOT_FOUND, UNKNOWN_TYPE.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/validate-intake" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Get overview
GET/v1/jobs/{jobId}/overview
A work record at a glance: its label, number, status, client, estimate and contract total, and the figures folded from the documents that belong to it — what was invoiced, paid and is due, what its bills charge it, its expenses, and the progress-billing state. `linked` counts the documents behind each figure. With `includeSubJobs`, `subJobs` adds this record and its live sub-jobs summed per currency from each record’s own figures: a view, with no net, profit or margin, and the figures above stay this record’s alone. The label and the client name come back inside the untrusted-data envelope.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/overview" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Get payables
GET/v1/jobs/{jobId}/payables
What a work record owes its vendors: its share of each bill charged to it (split pro-rata to the allocations), what of that is paid and what is still open, the number of draft bills, and one row per bill. Draft bills are listed but never counted.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/payables" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
List billing
GET/v1/jobs/{jobId}/billing
The invoices and estimates that belong to a work record, as rows: id, number (a draft has none), status, dates and amounts, newest first, at most 200 of each. A discarded draft and a document of another business are left out; bills are `jobs.getPayables`.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/billing" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Preview delete
GET/v1/jobs/{jobId}/delete-preview
What deleting a work record made by mistake would do with its documents: each unpaid invoice and open bill that would be voided, each draft that would be discarded and each document that stays (paid or part-paid, written off, sent estimates, bills shared with other records), with a plan hash the delete request must carry. Read only: it changes nothing. Deleting itself is the action request `deleteJob`, which a person approves.
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, JOB_CLOSED, HAS_SUB_JOBS.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/delete-preview" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Tree
GET/v1/jobs/{rootJobId}/tree
A work record and every sub-job under it, with the depth and parent of each record, parents before children. Titles come back inside the untrusted-data envelope; answers are not part of a tree row.
Scope
jobs:read
Effect
Read only
MCP tool
get_job_tree
Retries
Safe to repeat
Parameters
Tree parameters
Name
In
Type
Notes
rootJobIdrequired
path
id
—
Returns
data: Job[]. Pass meta.cursor back as cursor for the next page.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{rootJobId}/tree" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Move
POST/v1/jobs/{jobId}/move
Move a work record, with its sub-jobs, under another record of the same customer and business, or to the top level with null. Numbers already given stay as they are.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
move_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Move parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Move request body
Field
Type
Notes
newParentIdrequired
id | null
—
expectedUpdatedAtoptional
string
up to 40 chars
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, EDIT_CONFLICT, TREE_CYCLE, TREE_TOO_DEEP, TREE_TOO_WIDE, TREE_TOO_LARGE, TREE_CLIENT_MISMATCH, TREE_BUSINESS_MISMATCH, TREE_CURRENCY_MISMATCH.
Set a work record’s contract total, the basis its deposit and progress invoices are computed from: an amount in the record’s currency, or the total of an estimate linked to the record. Refused with CONTRACT_TOTAL_LOCKED once a live deposit or progress invoice exists.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
set_job_contract_total
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Set contract total parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Set contract total request body
Field
Type
Notes
contractTotaloptional
object
—
contractTotal.minorUnitsrequired
integer
—
contractTotal.currencyrequired
Currency
—
fromEstimateIdoptional
id
—
expectedUpdatedAtoptional
string
up to 40 chars
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, TARGET_CHANGED, VALIDATION_FAILED, CURRENCY_MISMATCH, CONTRACT_TOTAL_LOCKED, JOB_CLOSED.
Move an active work record to another stage of its type. `fromStageId` must be where it is now (TARGET_CHANGED otherwise). A finished stage needs its complete-time answers and required documents and completes the record (MISSING_ANSWER names what is missing; `completeAnyway: true` finishes anyway only where every missing document category allows it); a cancelled stage is refused here.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
set_job_stage
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Set stage parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Set stage request body
Field
Type
Notes
fromStageIdrequired
string
matches ^[a-z][a-zA-Z0-9]{0,31}$
toStageIdrequired
string
matches ^[a-z][a-zA-Z0-9]{0,31}$
noteoptional
string
up to 500 chars
completeAnywayoptional
boolean
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, TARGET_CHANGED, MISSING_ANSWER, INVALID_TRANSITION, JOB_NOT_ACTIVE, JOB_CLOSED.
Set what happens next on a work record, with an optional due date (08:00 in the workspace timezone) or instant. Pass `done: true`, with no text, to mark the current next action finished instead.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
set_job_next_action
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Set next action parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Set next action request body
Field
Type
Notes
textoptional
string
up to 200 chars
dueDateoptional
string
matches ^\d{4}-\d{2}-\d{2}$
dueAtoptional
string
up to 40 chars
assigneeUidoptional
id
—
doneoptional
boolean
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, JOB_CLOSED, VALIDATION_FAILED.
Complete a work record: it moves to the type’s finished stage once its complete-time answers and required documents are in. `completeAnyway: true` finishes it anyway only where every missing document category allows it. Open invoices do not block it.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
close_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Close parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Close request body
Field
Type
Notes
fromStageIdoptional
string
matches ^[a-z][a-zA-Z0-9]{0,31}$
completeAnywayoptional
boolean
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, MISSING_ANSWER, TARGET_CHANGED, JOB_NOT_ACTIVE, JOB_CLOSED.
The record’s timeline: what changed and who changed it, newest first. Keys and ids, never values.
Scope
jobs:read
Effect
Read only
MCP tool
get_job_activity
Retries
Safe to repeat
Parameters
Activity parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
limitoptional
query
integer
min 1 · max 50
cursoroptional
query
string
up to 256 chars
Returns
data: JobLogEntry[]. Pass meta.cursor back as cursor for the next page.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/activity" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Notes
POST/v1/jobs/{jobId}/notes
Add an internal note to a work record. The author is the person behind the connection, never a field of the request. Notes are never returned to a customer or in a webhook.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
add_job_note
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Notes parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Notes request body
Field
Type
Notes
textrequired
string
up to 2000 chars
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.
The emails and texts kept on a work record, newest first: what was sent, blocked, delivered or failed, and the texts of a conversation linked to it. Subjects and bodies are returned as untrusted data, with any link token redacted.
Scope
jobs:read
Effect
Read only
MCP tool
list_job_messages
Retries
Safe to repeat
Parameters
Messages parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
limitoptional
query
integer
min 1 · max 50
cursoroptional
query
string
up to 256 chars
Returns
data: Message[]. Pass meta.cursor back as cursor for the next page.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/messages" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Messages
POST/v1/jobs/{jobId}/messages
Ask to email or text the work record’s client about it. Nothing is sent until a person approves the request; once sent, the message is kept on the record. Needs actions:deliver as well.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
send_job_message
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Messages parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Messages request body
Field
Type
Notes
channelrequired
enum
One of email, sms
subjectoptional
string
up to 200 chars
bodyrequired
string
up to 1600 chars
Returns
data: object
Errors this route can answer besides the shared ones: NOT_FOUND, VALIDATION_FAILED, INSUFFICIENT_SCOPE.
Start adding a file to a work record: a PNG, JPEG, WebP or PDF. Answers the pending document, a short-lived signed URL to PUT the file to, and the exact headers to send with it. Nothing is attached until jobs.documents.complete checks the file, and a document added this way is never shown to the client. The same createRequestId answers the same document. Refused with PLAN_LIMIT when the record is full.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
begin_job_document
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Documents parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Documents request body
Field
Type
Notes
fileNamerequired
string
up to 500 chars
contentTyperequired
enum
One of image/png, image/jpeg, image/webp, application/pdf
sizerequired
integer
min 1 · max 26214400
categoryIdoptional
string
matches ^[a-z][a-zA-Z0-9]{0,31}$
createRequestIdrequired
id
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, JOB_CLOSED, PLAN_LIMIT.
The files attached to a work record, newest first: name (as the person gave it), category, type, size, whether the client may see it, and when it was attached. Pending, failed, expired and removed documents are left out. A file is read through jobs.documents.url.
Scope
jobs:read
Effect
Read only
MCP tool
list_job_documents
Retries
Safe to repeat
Parameters
Documents parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Returns
data: JobDocument[]. Pass meta.cursor back as cursor for the next page.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/documents" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Documents
POST/v1/jobs/{jobId}/documents/{docId}/complete
Finish adding a file: the server checks the uploaded object (it exists, it is within the declared and the allowed size, and its first bytes are the declared type) and attaches it, or marks it failed with a code (missing, tooLarge, sizeMismatch, typeMismatch, expired) and deletes it. A retry of an attached document answers it again.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
complete_job_document
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Documents parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
docIdrequired
path
id
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.
A short-lived signed URL to read one attached file. It is a bearer link: whoever holds it reads the file until it expires. Answers urlUnavailable when no link could be signed. Any other state, or a removed document, is NOT_FOUND.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/documents/{docId}/url" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Documents
PATCH/v1/jobs/{jobId}/documents/{docId}
Change an attached file’s category, or hide it from the client. Showing a file to the client, and marking one sensitive, are done by a person in the product.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
update_job_document
Retries
No Idempotency-Key on this route
Parameters
Documents parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
docIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Remove a file from a work record. It is hidden at once from every list and from the client; the file itself is deleted later, so a link already handed out can finish.
The adjustments of a work record, oldest first: each change's kind, status (draft, issued or void), number (once issued), title, description, signed amount and effective date. The title, the description, the answers and a void reason come back inside the untrusted-data envelope; phone, email, address and sensitive answers are masked or withheld without contacts:read. At most 200.
Scope
jobs:read
Effect
Read only
MCP tool
list_job_adjustments
Retries
Safe to repeat
Parameters
Adjustments parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Returns
data: JobAdjustment[]. Pass meta.cursor back as cursor for the next page.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/adjustments" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Adjustments
POST/v1/jobs/{jobId}/adjustments
Draft an adjustment to a work record: one of the kinds its type offers, a title, an optional description, the kind's own answers, a signed amount and an effective date. It is a DRAFT: it has no number, is not counted and changes no total until a person issues it (action issueJobAdjustment). A kind with no amount refuses one; a record that has not started is refused.
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
create_job_adjustment
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Adjustments parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Adjustments request body
Field
Type
Notes
kindIdrequired
string
matches ^[a-z][a-zA-Z0-9]{0,31}$
titlerequired
string
up to 200 chars
descriptionoptional
string
up to 4000 chars
fieldsoptional
object
—
amountDeltaoptional
object
—
amountDelta.minorUnitsrequired
integer
—
amountDelta.currencyrequired
Currency
—
effectiveDateoptional
string
matches ^\d{4}-\d{2}-\d{2}$
Returns
data: object
Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, JOB_CLOSED, INVALID_TRANSITION, VALIDATION_FAILED.
Errors this route can answer besides the shared ones: NOT_FOUND.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/jobs/{jobId}/adjustments/{adjustmentId}" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
jobs
Adjustments
PATCH/v1/jobs/{jobId}/adjustments/{adjustmentId}
Change a DRAFT adjustment: send only what changes; null clears the description, the amount, the date or an answer. `expectedUpdatedAt` must be the stamp you read (TARGET_CHANGED otherwise). An issued or void adjustment cannot be edited (NOT_A_DRAFT).
Scope
jobs:write
Effect
Writes a draft or record
MCP tool
update_job_adjustment
Retries
No Idempotency-Key on this route
Parameters
Adjustments parameters
Name
In
Type
Notes
jobIdrequired
path
id
—
adjustmentIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Discard a DRAFT adjustment that was never issued: it is deleted, uses no number and was never counted, and answers as it was. An issued or void adjustment is refused (NOT_A_DRAFT); voiding one is an action a person approves.
curl -X GET "https://api.billingmart.com/v1/clients/{clientId}/unbilled" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
invoices
Ledger
GET/v1/invoices/{invoiceId}/ledger
The ledger entries behind one invoice: payments, credits, write-offs, refunds and returns, oldest first.
Scope
payments:read
Effect
Read only
MCP tool
list_invoice_ledger
Retries
Safe to repeat
Parameters
Ledger parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Returns
data: LedgerEntry[]. Pass meta.cursor back as cursor for the next page.
Example requestSample
curl -X GET "https://api.billingmart.com/v1/invoices/{invoiceId}/ledger" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
invoices
Pay link status
GET/v1/invoices/{invoiceId}/pay-link
The state of an invoice’s public pay link: whether one exists, its expiry, whether it was revoked, when it was first viewed and how often. Never the link itself.
curl -X GET "https://api.billingmart.com/v1/invoices/{invoiceId}/pay-link" \
-H "Authorization: Bearer $BILLINGMART_API_KEY"
invoices
Discard
POST/v1/invoices/{invoiceId}/discard
Discard a never-numbered DRAFT invoice: it leaves every default list and cannot be sent. Refused once numbered, or while an action on it is pending. The record stays (soft, function-written).
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
discard_draft_invoice
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Discard parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Returns
data: object
Errors this route can answer besides the shared ones: NOT_A_DRAFT, ACTION_PENDING_ON_TARGET.
Pause or resume automated reminders for ONE invoice. Reversible both ways; resuming may let a missed step catch up with one reminder at the next daily run.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
set_invoice_dunning_paused
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Set dunning paused parameters
Name
In
Type
Notes
invoiceIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Propose a reminder schedule and its text for each group of customers by how they have paid before. Saved as a DRAFT: the reminders that go out do not change until a person publishes it in the console.
Scope
invoices:write
Effect
Writes a draft or record
MCP tool
suggest_dunning_ladders
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours
Parameters
Propose ladder parameters
Name
In
Type
Notes
businessIdrequired
path
id
—
Request body
JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.
Propose ladder request body
Field
Type
Notes
toneoptional
enum
One of friendly, neutral, firm
Returns
data: object
Errors this route can answer besides the shared ones: AI_DISABLED, AI_CAP_REACHED, AI_CREDITS_EXHAUSTED.
An issued or draft invoice. It keeps its own denormalised client block PLUS clientId, so editing a client later cannot rewrite what an invoice was issued for. Money is written only by Cloud Functions; totals here are server-derived and a client-supplied total is never trusted.
Invoice fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
—
merchantAccountIdoptional
Id
WHICH TranzPay merchant account this invoice is charged through.
numberoptional
string
The human invoice number, minted transactionally per business per year at SEND, not at create. Absent on a draft — a draft has no number and burns no quota.
clientIdrequired
Id
—
clientrequired
InvoiceClientSnapshot
—
statusrequired
InvoiceStatus
—
currencyrequired
Currency
The invoice's own currency: every amount on it is in this currency. Nothing is converted between currencies; reports keep one row per currency.
languagerequired
Language
The language the customer-facing artefacts render in.
issueDateoptional
DateOnly
—
dueDateoptional
DateOnly
A calendar date in the tenant's timezone, not an instant. Net-15 means a day, and a UTC instant gets it wrong for a Pacific tenant twice a year.
lineItemsrequired
LineItem[]
—
totalsrequired
InvoiceTotals
—
customFieldsoptional
object
Untrusted textValues for the business's declared invoiceFields, keyed by InvoiceFieldDef.key. Typed and declared, never free-form — the declaration lives on the business, the values live here.
notesoptional
string
Untrusted textPUBLIC. Prints on the customer's copy.
taxCategoryoptional
TaxCategoryCode
The tax category a person confirmed (a code of the taxonomy). Never set by a suggestion alone.
taxCategoryVersionoptional
string
The taxonomy edition taxCategory is a code of, e.g. us-sch-c-2026.
taxCategorySourceoptional
TaxCategorySource
—
taxScheduleCodeoptional
TaxCategoryCode
The TOP category taxCategory resolved to when it was confirmed (itself for a top category; a sub-category's parent), so a later rename or move never rewrites a past report.
taxScheduleLineoptional
string
The form line of taxScheduleCode at confirmation, e.g. Line 8; absent where the category is not a line of the form.
internalNotesoptional
string
Untrusted textINTERNAL. Never crosses to the public pay link.
termsoptional
string
Untrusted text—
poNumberoptional
string
Untrusted text—
templateIdoptional
Id
—
statusHistoryoptional
StatusChange[]
INTERNAL. Never crosses to the public pay link.
sentAtoptional
IsoTimestamp
—
paidAtoptional
IsoTimestamp
—
voidedAtoptional
IsoTimestamp
—
payLinkoptional
InvoicePayLink
The current public capability, minted at send and re-minted by createInvoiceLink. FUNCTION-WRITTEN. The token is the invoiceLinks document id; the email and the console build the URL from it.
feeOverrideoptional
InvoiceFeeOverride
Whether this invoice adds the card pass-through fee, and at what rate. Absent means the workspace default. SERVER-VALIDATED: written through the invoice callables, never by a client write.
recurringIdoptional
Id
The schedule that issued this invoice, if any.
estimateIdoptional
Id
The estimate this was converted from, if any.
jobRefoptional
JobRef
The job this invoice belongs to, if any.
jobRoleoptional
JobInvoiceRole
Deposit, progress or retainage release — set with jobRef by the job callables.
retainageBasisPointsoptional
BasisPoints
Retainage withheld from this invoice as basis points of the subtotal; totals.retainageWithheld is derived from it at every re-derivation, including send.
createdAtrequired
IsoTimestamp
—
updatedAtoptional
IsoTimestamp
—
createdByoptional
Id
INTERNAL. Never crosses to the public pay link.
dunningoptional
InvoiceDunning
Reminder history and pause. FUNCTION-WRITTEN ONLY: the daily scheduler records each cadence step it sent, sendReminder records a manual one, pauseDunning sets the pause. Absent means nothing sent and not paused.
discardedAtoptional
IsoTimestamp
. Set by a function when a never-numbered draft is discarded: it leaves every default list and cannot be sent or opened. Soft, never a status value (a money-enum addition is a breaking change).
discardedByoptional
Id
. The uid or credential that discarded it.
lastWriteActoroptional
WriteActor
—
openDisputesoptional
integer
PR B: how many of this invoice's payments carry an OPEN card dispute — what shows the invoice, and its job, as Disputed. Written only by the dispute callables, in the same transaction as the dispute; absent means none.
cardOnlyoptional
boolean
Server-written: this invoice can be paid by CARD ONLY — an ACH debit, direct or from a saved method, is refused before the gateway.
creditOrderoptional
CreditOrderRef
—
revisionoptional
integer
How many times a sent invoice has been revised; absent means never. FUNCTION-WRITTEN by reviseInvoice only, in the transaction that writes revisions/{n}; in no client allowlist.
revisedAtoptional
IsoTimestamp
When the invoice was last revised. FUNCTION-WRITTEN with revision. INTERNAL: never crosses to the pay link, the PDF or a customer view.
autopayHeldoptional
InvoiceAutopayHold
—
jobAdjustmentIdoptional
Id
The job adjustment this invoice was drafted from. FUNCTION-WRITTEN and in no client allowlist; the adjustment's invoiceIds is derived from it.
jobIdoptional
Id
The work record this invoice belongs to, when it does. Set with jobRef, which stays the legacy anchor; the two always name the same record. FUNCTION-WRITTEN.
recordAnswersoptional
InvoiceRecordAnswer[]
The work record's answers its type marks printOnCustomerCopy (client-visible questions only), in the type's question order, copied when the invoice is drafted from the record and frozen at send, so the customer's copy…
Object
PdfPreview
PdfPreview is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobSummary
JobSummary is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
Job
A work record: one tow, matter, policy or project from intake to billing, the unit a business tracks work by. FUNCTION-WRITTEN ONLY — the record callables take every actor from the verified token, validate answers against the pinned type revision at each transition, and audit.
Job fields
Field
Type
Notes
idrequired
Id
Server-minted. A backfilled record takes the legacy jobRef.id.
tenantrequired
TenantRef
—
numberoptional
string
Minted at activation inside the metering transaction (JOB-{YYYY}-{seq} by default). Absent on a draft.
typeIdrequired
Id
—
typeRevisionrequired
integer
The job type revision this record's answers are validated against. Pinned at creation; a type change is an explicit, previewed act.
typeLabelrequired
string
Untrusted textThe type's singular label when the record was created, so a list renders without reading the type.
labelrequired
string
Untrusted text—
statusrequired
JobStatus
—
clientIdoptional
Id
The one client this record is for. A record has at most one client and one currency.
currencyrequired
Currency
Locked at activation: every invoice, bill allocation and total on the record is in it.
parentJobIdoptional
Id
The record this one sits under (a sub-job). Absent at the top level. Same client, business and, once either is activated, currency as the parent. Function-written.
ancestorIdsoptional
Id[]
The records above this one, root first and parent last, at most 3. Empty or absent at the top level. Complete, so a cycle is one read of the new parent. Function-written.
depthoptional
integer
0 at the top level, up to 3: four levels in all. Function-written.
childCountoptional
integer
Live (not discarded) sub-jobs directly under this record, so a list can show "4 sub-jobs" without a query. Function-written.
childNumberCounteroptional
integer
The last suffix handed to a sub-job at activation, so the next one is numbered <this number>.<n+1> inside the activation transaction. Never returned.
estimatedValueoptional
Money
An informational budget, shown as "Not set" when absent. Never the progress basis.
contractTotaloptional
Money
The progress basis the deposit and progress maths run against. Locked while a live deposit or progress invoice exists.
fieldsrequired
JobFieldValues
—
fieldListsoptional
JobFieldLists
—
fieldAddressesoptional
JobFieldAddresses
—
fieldsOrphanedoptional
JobFieldsOrphaned
—
fieldListsOrphanedoptional
JobFieldListsOrphaned
—
fieldAddressesOrphanedoptional
JobFieldAddressesOrphaned
—
stageoptional
JobStageState
—
intakeStepoptional
enum
. The intake step a DRAFT was last on, so it resumes there on any device. Written by updateJobFields; meaningless once the record is active.
stageHistoryoptional
JobStageChange[]
The most recent stage moves, newest last. The full trail is the record's events. At most 100 entries; the callables enforce it.
assigneeUidoptional
Id
The member responsible. Set by a callable that checks the member may see this record's business.
nextActionoptional
JobNextAction
—
sharingoptional
JobSharing
—
publicLinkoptional
JobPublicLink
—
notesInternaloptional
string
Untrusted textStaff-only free text on the record. Never returned by REST or MCP and never in a public snapshot.
searchoptional
JobSearch
—
answerClassesoptional
JobAnswerClasses
—
searchKeysPiioptional
string[]
Untrusted textSearch tokens built from the answers in answerClasses (email, phone, address), kept apart from searchKeys and queried only by a caller holding contacts:read. Function-written, never returned by REST or MCP.
searchKeysoptional
string[]
Untrusted textBounded, function-written keys built from the type's searchable fields. Sensitive answers never contribute. At most 40 keys; the callables enforce it.
billingoptional
JobBillingState
—
legacyoptional
JobLegacy
—
adjustmentCountersoptional
JobAdjustmentCounters
—
meteredPeriodoptional
string
The tenant-local month (YYYY-MM) in which activation was counted as a send. Absent on a draft and on a backfilled record, which is never metered.
activatedAtoptional
IsoTimestamp
—
activatedByoptional
Id
—
completedAtoptional
IsoTimestamp
—
completedByoptional
Id
—
cancelledAtoptional
IsoTimestamp
—
cancelledByoptional
Id
—
discardedAtoptional
IsoTimestamp
Soft discard of a record that was never activated.
discardedByoptional
Id
—
createdAtrequired
IsoTimestamp
—
createdByoptional
Id
From the verified token. INTERNAL: never returned by REST v1 or MCP, as on an invoice.
updatedAtrequired
IsoTimestamp
—
lastWriteActoroptional
WriteActor
—
Object
JobIntakeValidation
JobIntakeValidation is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobOverview
JobOverview is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobPayables
JobPayables is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobBilling
JobBilling is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobDeletePlan
JobDeletePlan is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobLogEntry
One line of a work record's own timeline: created, stage moved, assigned, document added, message sent. Readable by the staff who can read the record — `listActivity` is owner and admin only, so the record keeps its own. FUNCTION-WRITTEN ONLY and append-only.
JobLogEntry fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
Copied from the record onto every event, so the rule needs no parent read.
jobIdrequired
Id
—
kindrequired
JobLogKind
—
keysoptional
string[]
For a fields or type change: the field KEYS touched. Never the values. At most 80 keys.
completedAnywayoptional
string[]
For a stageChanged into a done stage: the ids of the document categories whose required documents were missing when the record was finished with complete-anyway. At most 12, one per category.
fromoptional
string
The earlier stage id or member uid, for a move or an assignment.
tooptional
string
The later stage id or member uid.
messageIdoptional
Id
. For a message.* event: the message record it is about (tenants/{t}/messages/{id}). The message's body is never copied here.
channeloptional
enum
. For a message.* event: how the message travelled.
unverifiedoptional
boolean
. For message.sent: true when the email went to an address nobody has confirmed yet. Absent otherwise.
reasonoptional
string
. For message.blocked or message.failed: the catalogue code only, never the provider's text.
threadIdoptional
ContactKey
. For thread.linked and message.received: the text conversation (smsThreads/{id}). For thread.linked, from names the record it was moved from, if any.
actionIdoptional
Id
. For an activated event written by an approved or owner-rule activateJob action: that action's id, so the stranded-action sweep can tell the action's own activation from a person's direct one. Absent otherwise.
atrequired
IsoTimestamp
—
actorrequired
JobLogActor
—
Object
Message
One outbound customer message and its outcome. FUNCTION-WRITTEN ONLY: the delivery function decides whether a message may go — a verified phone with explicit smsConsent, a tenant with SMS enabled — and records what happened, including a refusal.
Message fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
—
channelrequired
enum
—
directionoptional
enum
inbound is a customer text received on the workspace number, filed by the signed webhook. Absent means outbound, so no record needs a migration. What a customer wrote is in body, which carries the marker.
kindrequired
enum
Why it was sent. custom is staff-written text from the console. refundConfirmation tells the customer a refund was issued, once per refund ledger entry.
clientIdoptional
Id
The client the message went to. OPTIONAL: a payment-link receipt has no client; it goes to the payment's receiptEmail.
contactKeyrequired
ContactKey
The verified, consenting contact it went to. Never the raw number.
toLast4optional
string
The only digits of the number persisted.
invoiceIdoptional
Id
—
estimateIdoptional
Id
For an estimateSent: the estimate it announces.
paymentIdoptional
Id
For a receipt or a refundConfirmation: the payment it acknowledges.
paymentLinkIdoptional
Id
For a paymentLinkSent: the payment link it delivers.
ledgerEntryIdoptional
Id
For a refundConfirmation: the refund ledger entry it confirms. One message per entry.
subjectoptional
string
Untrusted textEmail only: the subject line as sent.
attachmentStoragePathoptional
string
Email only, and ONLY for a document the tenant already owns: the Storage path of a file that travels with the message, such as a rendered statement.
attachmentsoptional
integer
Email only: how many files went with it (the invoice PDF is one). The files themselves are the tenant's pdfs/ objects, not copies.
bodyoptional
string
Untrusted textAs sent, footer included.
statusrequired
enum
blocked never reached the provider (no consent, no verified contact, the channel off, opted out); queued is written before the provider is called — for email the delivery trigger picks it up; sent is accepted by t…
reasonoptional
string
Why it was blocked or failed: a catalogue code (NO_SMS_CONSENT, NO_VERIFIED_PHONE, SMS_DISABLED, NO_VERIFIED_EMAIL, EMAIL_DISABLED, RATE_LIMITED, SANDBOX_RECIPIENT — a sandbox's recipient outside its team, the message k…
providerMessageIdoptional
string
—
segmentsoptional
integer
SMS segments the body needs (160 GSM / 70 UCS-2 per segment).
deliveryClaimedAtoptional
IsoTimestamp
SMS only: when one delivery took this record, in a transaction. A request that sends at once writes it at birth; a queued text gets it from the delivery trigger, so a trigger retry never sends twice.
smsAccountoptional
enum
SMS only: whose Twilio account carried it — the platform's shared number, or the workspace's own account and sender.
consentAtoptional
IsoTimestamp
When the consent in force at send time was recorded, snapshotted here.
unverifiedoptional
boolean
Set when the email went to an address nobody has proved yet. Its personal link shows only the one document, and one confirm tap by the recipient verifies the address. Absent on every other message.
sentByoptional
Id
The staff uid, from the verified token; absent for a system send.
createdAtrequired
IsoTimestamp
—
sentAtoptional
IsoTimestamp
—
updatedAtoptional
IsoTimestamp
—
threadIdoptional
ContactKey
SMS only: the smsThreads/{id} this message belongs to. Equal to contactKey: the thread id is the contact key, so both directions join.
mediaoptional
object[]
inbound MMS only: the attachments, copied by the server into tenants/{t}/sms-media/. A refused item is recorded with status refused and never stored.
providerStatusAtoptional
IsoTimestamp
When the provider last reported this outbound message's status. A status only moves forward.
jobIdoptional
Id
The work record this belongs to, when it does. FUNCTION-WRITTEN: set by the callables that check the record is in the same business, never by a payload alone.
earlyStatusoptional
object
FUNCTION-WRITTEN. A signed delivery-status callback that arrived before the send wrote providerMessageId.
Object
JobDocument
Metadata of one file attached to a work record: a photo, a signed form, a policy PDF. FUNCTION-WRITTEN ONLY: a function mints the signed upload, checks the file's first bytes against its declared type when the upload is finalised, and is the only reader of `storagePath`.
JobDocument fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
Copied from the record, so a member confined to some businesses reads a document exactly when they can read its record.
jobIdrequired
Id
—
staterequired
JobDocumentState
—
categoryIdoptional
string
One of the record's type revision's document category ids, or other. An archived category keeps rendering on the documents that already use it.
nameoptional
string
Untrusted textThe file name as the person gave it.
contentTypeoptional
string
The type the server verified from the file's first bytes, not the one the browser declared.
sizeoptional
integer
Bytes.
storagePathoptional
string
Where the file is stored. Never returned by REST or MCP and never in a public snapshot.
clientVisibleoptional
boolean
Whether the client may see this document on the shared record page and the portal, subject to the record's sharing.
scanoptional
JobDocumentScan
—
uploadedByoptional
Id
From the verified token.
createdAtrequired
IsoTimestamp
—
attachedAtoptional
IsoTimestamp
—
removedAtoptional
IsoTimestamp
Soft removal: the metadata stays for the audit trail and the file is no longer offered.
removedByoptional
Id
Who removed it, from the verified token.
failureoptional
JobDocumentFailure
—
sensitiveoptional
boolean
Set by an owner or an admin: a sensitive document can never be client-visible.
expiresAtoptional
IsoTimestamp
While pending: when the upload window closes and the sweep expires it.
updatedAtoptional
IsoTimestamp
—
lastWriteActoroptional
WriteActor
—
Object
JobDocumentUrl
JobDocumentUrl is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
JobAdjustment
A change to a work record after it started: a change order, an endorsement. Its kind comes from the record's job type; it is numbered per record when issued, carries a SIGNED amount that adjusts the record's total value basis (the contract total when set, else the estimated value), and may be drafted into an invoice.
JobAdjustment fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
Copied from the record, so a member confined to some businesses reads an adjustment exactly when they can read its record.
jobIdrequired
Id
—
kindIdrequired
string
The adjustment kind's id in the job type revision this record pinned.
typeIdoptional
Id
The job type the kind was read from, beside typeRevision: a revision number means nothing without its type, and a record can change type. Absent on an adjustment drafted before this field.
typeRevisionrequired
integer
The job type revision the kind was read from.
numberoptional
string
The per-record number, such as CO-2. Minted when issued; absent on a draft.
statusrequired
JobAdjustmentStatus
—
titlerequired
string
Untrusted text—
descriptionoptional
string
Untrusted text—
fieldsoptional
JobAdjustmentFieldValues
—
amountDeltaoptional
Money
The signed change to the record's value, in the record's currency; zero is allowed. Absent when the kind carries no amount.
effectiveDateoptional
DateOnly
—
invoiceIdsoptional
Id[]
DERIVED from the invoices whose jobAdjustmentId names this adjustment; never written by a client.
issuedAtoptional
IsoTimestamp
—
issuedByoptional
Id
The uid or credential that issued it.
voidedAtoptional
IsoTimestamp
—
voidedByoptional
Id
The uid or credential that voided it.
voidReasonoptional
string
Untrusted text—
clientApprovaloptional
JobAdjustmentApproval
The client's approval of this change, recorded on a draft by an owner or admin. Required before a kind marked requiresApproval issues. Cleared when the amount or the title changes.
meteredPeriodoptional
string
The tenant-local month (YYYY-MM) in which issuing it was counted. Absent on a draft.
createdAtrequired
IsoTimestamp
—
createdByrequired
Id
The uid or credential that created it.
updatedAtrequired
IsoTimestamp
—
lastWriteActoroptional
Id
Who wrote it last, from the verified token.
Object
UnbilledPreview
UnbilledPreview is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Object
LedgerEntry
One append-only money movement. NEVER UPDATED, NEVER DELETED — a mistake is corrected by a compensating entry that names the entry it reverses. Written only by Cloud Functions. The ledger is what lets an ACH return arriving days later take an invoice out of `paid` without rewriting history.
LedgerEntry fields
Field
Type
Notes
idrequired
Id
—
tenantrequired
TenantRef
—
typerequired
LedgerEntryType
—
amountrequired
Money
Signed. A refund, a write-off and an ACH return are negative movements; the sign is part of the entry, not of the reader's interpretation.
invoiceIdoptional
Id
—
paymentIdoptional
Id
—
paymentLinkIdoptional
Id
The payment link a payment-link entry belongs to (it has no invoiceId).
clientIdoptional
Id
—
billIdoptional
Id
—
reversesEntryIdoptional
Id
The entry this one compensates. Set on every correction, because the corrected entry itself is immutable.
occurredAtrequired
IsoTimestamp
When the movement happened at the gateway or in the world. Distinct from createdAt, which is when we learned of it — an ACH return can be days apart.
reportingRateoptional
ReportingRate
—
methodoptional
PaymentMethod
PR B: the method of the payment this entry moves, on an achReturn (and a chargeback).
memooptional
string
Untrusted text—
actorUidoptional
Id
Taken from the verified ID token, never from the payload. Absent for system-generated entries.
createdAtrequired
IsoTimestamp
—
Object
PayLinkStatus
PayLinkStatus is computed by the server for this request, not a stored document, so no contract describes it. Its fields are the TypeScript type of the same name that the handler returns.
Next section
Estimates
Quotes before the work, converted into an invoice once the client accepts.
We use Google Analytics on our website by default to understand visits. Choose Decline to turn it off; you can change this at any time on the Cookies page. We never send Google your name, email or account. Cookie policy