Skip to content

Invoices

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.

invoices

List

GET/v1/invoices

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
NameInTypeNotes
limitoptionalqueryintegermin 1 · max 50
cursoroptionalquerystringup to 256 chars
statusoptionalqueryenumOne of draft, sent, viewed, partiallyPaid, paid, overdue, void, uncollectible
clientIdoptionalqueryid—
discardedoptionalqueryboolean—

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
FieldTypeNotes
businessIdrequiredid—
clientIdrequiredid—
currencyoptionalCurrency—
languageoptionalstringup to 8 chars
lineItems[]optionalobject[]up to 200 items
lineItems[].idoptionalid—
lineItems[].descriptionoptionalstringWhat the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptionalintegerQuantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptionalenumHow 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[].unitPriceoptionalobjectPrice per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequiredinteger—
lineItems[].unitPrice.currencyrequiredCurrency—
lineItems[].rateBasisPointsoptionalinteger—
lineItems[].taxTreatmentoptionalstring—
lineItems[].taxRateBasisPointsoptionalinteger—
lineItems[].discountBasisPointsoptionalinteger—
lineItems[].presetKeyoptionalstringThe 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
customFieldsoptionalobject—
notesoptionalstringup to 4000 chars
internalNotesoptionalstringup to 4000 chars
termsoptionalstringup to 4000 chars
poNumberoptionalstringup to 64 chars
issueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
dueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
templateIdoptionalid—
feeOverrideoptionalobject | null—
merchantAccountIdoptionalid | null—

Returns

data: object

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"businessId":"<businessId>","clientId":"<clientId>"}'

invoices

Get

GET/v1/invoices/{invoiceId}

One invoice with lines and totals.

Scope
invoices:read
Effect
Read only
MCP tool
get_invoice
Retries
Safe to repeat

Parameters

Get parameters
NameInTypeNotes
invoiceIdrequiredpathid—

Returns

data: Invoice

Example requestSample
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
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Update draft request body
FieldTypeNotes
expectedUpdatedAtoptionalstringup to 40 chars
lineItems[]optionalobject[]up to 200 items
lineItems[].idoptionalid—
lineItems[].descriptionoptionalstringWhat the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptionalintegerQuantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptionalenumHow 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[].unitPriceoptionalobjectPrice per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequiredinteger—
lineItems[].unitPrice.currencyrequiredCurrency—
lineItems[].rateBasisPointsoptionalinteger—
lineItems[].taxTreatmentoptionalstring—
lineItems[].taxRateBasisPointsoptionalinteger—
lineItems[].discountBasisPointsoptionalinteger—
lineItems[].presetKeyoptionalstringThe 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
customFieldsoptionalobject—
notesoptionalstringup to 4000 chars
internalNotesoptionalstringup to 4000 chars
termsoptionalstringup to 4000 chars
poNumberoptionalstringup to 64 chars
issueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
dueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
templateIdoptionalid | null—
feeOverrideoptionalobject | null—
merchantAccountIdoptionalid | null—

Returns

data: Invoice

Errors this route can answer besides the shared ones: ACTION_PENDING_ON_TARGET, NOT_A_DRAFT.

Example requestSample
curl -X PATCH "https://api.billingmart.com/v1/invoices/{invoiceId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

invoices

Create from job

POST/v1/jobs/{jobId}/invoices

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Create from job request body
FieldTypeNotes
lineItems[]optionalobject[]up to 200 items
lineItems[].idoptionalid—
lineItems[].descriptionoptionalstringWhat the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptionalintegerQuantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptionalenumHow 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[].unitPriceoptionalobjectPrice per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequiredinteger—
lineItems[].unitPrice.currencyrequiredCurrency—
lineItems[].rateBasisPointsoptionalinteger—
lineItems[].taxTreatmentoptionalstring—
lineItems[].taxRateBasisPointsoptionalinteger—
lineItems[].discountBasisPointsoptionalinteger—
lineItems[].presetKeyoptionalstringThe 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
customFieldsoptionalobject—
notesoptionalstringup to 4000 chars
internalNotesoptionalstringup to 4000 chars
termsoptionalstringup to 4000 chars
poNumberoptionalstringup to 64 chars
issueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
dueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
templateIdoptionalid—
merchantAccountIdoptionalid | null—
notesTemplateoptionalstringup to 4000 chars
languageoptionalstringup to 8 chars
kindoptionalenumOne of unbilled
throughoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
fromAdjustmentIdoptionalid—

Returns

data: object

Errors this route can answer besides the shared ones: FIELD_FORBIDDEN, JOB_CLOSED, INVALID_TRANSITION, NOT_FOUND.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/invoices" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

invoices

Set job

POST/v1/invoices/{invoiceId}/job

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
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Set job request body
FieldTypeNotes
jobIdrequiredid | null—
expectedUpdatedAtoptionalstringup 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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/job" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"<jobId>"}'

reminders

Draft

POST/v1/invoices/{invoiceId}/reminder-draft

Draft the subject and body of a payment reminder for an open invoice. Text only — nothing is sent; a person sends it or proposes it.

Scope
invoices:write
Effect
Read only
MCP tool
draft_reminder_text
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours

Parameters

Draft parameters
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Draft request body
FieldTypeNotes
toneoptionalenumOne of friendly, neutral, firm

Returns

data: object

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/reminder-draft" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

invoices

Add line

POST/v1/invoices/{invoiceId}/lines

Append one line to a draft.

Scope
invoices:write
Effect
Writes a draft or record
MCP tool
add_line_item
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours

Parameters

Add line parameters
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Add line request body
FieldTypeNotes
linerequiredobject—
line.idoptionalid—
line.descriptionoptionalstringWhat the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
line.quantityMillioptionalintegerQuantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
line.rateBasisoptionalenumHow 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.unitPriceoptionalobjectPrice per unit in integer minor units of the document currency ($5,000.00 is 500000).
line.unitPrice.minorUnitsrequiredinteger—
line.unitPrice.currencyrequiredCurrency—
line.rateBasisPointsoptionalinteger—
line.taxTreatmentoptionalstring—
line.taxRateBasisPointsoptionalinteger—
line.discountBasisPointsoptionalinteger—
line.presetKeyoptionalstringThe 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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/lines" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"line":{"unitPrice":{"minorUnits":0,"currency":"USD"}}}'

invoices

Remove line

DELETE/v1/invoices/{invoiceId}/lines/{lineId}

Remove one line from a draft.

Scope
invoices:write
Effect
Writes a draft or record
MCP tool
remove_line_item
Retries
No Idempotency-Key on this route

Parameters

Remove line parameters
NameInTypeNotes
invoiceIdrequiredpathid—
lineIdrequiredpathid—

Returns

data: Invoice

Errors this route can answer besides the shared ones: ACTION_PENDING_ON_TARGET, NOT_A_DRAFT.

Example requestSample
curl -X DELETE "https://api.billingmart.com/v1/invoices/{invoiceId}/lines/{lineId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

invoices

Add sources

POST/v1/invoices/{invoiceId}/sources

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
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Add sources request body
FieldTypeNotes
sources[]requiredobject[]up to 200 items · at least 1
sources[].kindrequiredenumOne of timeEntry, expense, mileage, estimate
sources[].idrequiredid—

Returns

data: object

Errors this route can answer besides the shared ones: ACTION_PENDING_ON_TARGET, NOT_A_DRAFT.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/sources" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

invoices

Pdf

GET/v1/invoices/{invoiceId}/pdf

Render (or reuse) the invoice PDF and return a short-lived signed URL. Needs contacts:read as well: the PDF prints the client’s address and email.

Scope
invoices:read
Effect
Read only
MCP tool
preview_invoice_pdf
Retries
Safe to repeat

Parameters

Pdf parameters
NameInTypeNotes
invoiceIdrequiredpathid—

Returns

data: PdfPreview

Example requestSample
curl -X GET "https://api.billingmart.com/v1/invoices/{invoiceId}/pdf" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

invoices

Create deposit

POST/v1/invoices/deposit

Draft a deposit invoice for a job: depositBasisPoints of jobRef.total (default from the business’s terms). One per job.

Scope
invoices:write
Effect
Writes a draft or record
MCP tool
create_deposit_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 deposit request body
FieldTypeNotes
businessIdrequiredid—
clientIdrequiredid—
jobIdoptionalid—
jobRefoptionalobject—
jobRef.idrequiredid—
jobRef.labelrequiredstringup to 200 chars
jobRef.totalrequiredobject—
jobRef.total.minorUnitsrequiredinteger—
jobRef.total.currencyrequiredCurrency—
depositBasisPointsoptionalintegermin 0 · max 10000
currencyoptionalCurrency—
languageoptionalstringup to 8 chars
notesoptionalstringup to 4000 chars
customFieldsoptionalobject—

Returns

data: object

Errors this route can answer besides the shared ones: INVALID_TRANSITION.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/deposit" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"businessId":"<businessId>","clientId":"<clientId>","jobRef":{"id":"<id>","label":"<label>","total":{"minorUnits":0,"currency":"USD"}}}'

invoices

Create progress

POST/v1/invoices/progress

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
FieldTypeNotes
businessIdrequiredid—
clientIdrequiredid—
jobIdoptionalid—
jobRefoptionalobject—
jobRef.idrequiredid—
jobRef.labelrequiredstringup to 200 chars
jobRef.totalrequiredobject—
jobRef.total.minorUnitsrequiredinteger—
jobRef.total.currencyrequiredCurrency—
percentCompleteBasisPointsoptionalintegermin 0 · max 10000
retainageBasisPointsoptionalintegermin 0 · max 10000
lineItems[]optionalobject[]up to 200 items
lineItems[].idoptionalid—
lineItems[].descriptionoptionalstringWhat the line is for. Optional with a presetKey (the item’s name is used). — up to 2000 chars
lineItems[].quantityMillioptionalintegerQuantity in thousandths: 1000 is one unit, 1500 is 1.5 hours. Defaults to 1000.
lineItems[].rateBasisoptionalenumHow 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[].unitPriceoptionalobjectPrice per unit in integer minor units of the document currency ($5,000.00 is 500000).
lineItems[].unitPrice.minorUnitsrequiredinteger—
lineItems[].unitPrice.currencyrequiredCurrency—
lineItems[].rateBasisPointsoptionalinteger—
lineItems[].taxTreatmentoptionalstring—
lineItems[].taxRateBasisPointsoptionalinteger—
lineItems[].discountBasisPointsoptionalinteger—
lineItems[].presetKeyoptionalstringThe 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
currencyoptionalCurrency—
languageoptionalstringup to 8 chars
notesoptionalstringup to 4000 chars
customFieldsoptionalobject—

Returns

data: object

Errors this route can answer besides the shared ones: INVALID_TRANSITION.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/progress" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"businessId":"<businessId>","clientId":"<clientId>","jobRef":{"id":"<id>","label":"<label>","total":{"minorUnits":0,"currency":"USD"}}}'

invoices

Create retainage release

POST/v1/invoices/retainage-release

Draft the retainage release for a job: exactly what its progress invoices withheld. Once per job.

Scope
invoices:write
Effect
Writes a draft or record
MCP tool
create_retainage_release
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 retainage release request body
FieldTypeNotes
businessIdrequiredid—
clientIdrequiredid—
jobIdoptionalid—
jobRefoptionalobject—
jobRef.idrequiredid—
jobRef.labelrequiredstringup to 200 chars
jobRef.totalrequiredobject—
jobRef.total.minorUnitsrequiredinteger—
jobRef.total.currencyrequiredCurrency—
currencyoptionalCurrency—
languageoptionalstringup to 8 chars
notesoptionalstringup to 4000 chars
customFieldsoptionalobject—

Returns

data: object

Errors this route can answer besides the shared ones: INVALID_TRANSITION.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/retainage-release" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"businessId":"<businessId>","clientId":"<clientId>","jobRef":{"id":"<id>","label":"<label>","total":{"minorUnits":0,"currency":"USD"}}}'

jobs

List

GET/v1/jobs

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
NameInTypeNotes
businessIdoptionalqueryid—
clientIdoptionalqueryid—
parentJobIdoptionalqueryid—
rootJobIdoptionalqueryid—
topLevelOnlyoptionalqueryboolean—
limitoptionalqueryintegermin 1 · max 1000
cursoroptionalquerystringup 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
FieldTypeNotes
businessIdrequiredid—
typeIdrequiredid—
labeloptionalstringup to 200 chars
clientIdoptionalid—
currencyoptionalCurrency—
fieldsoptionalobject—
fieldListsoptionalobject—
fieldAddressesoptionalobject—
assigneeUidoptionalid—
estimatedValueoptionalobject—
estimatedValue.minorUnitsrequiredinteger—
estimatedValue.currencyrequiredCurrency—
parentJobIdoptionalid—

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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"businessId":"<businessId>","typeId":"<typeId>","estimatedValue":{"minorUnits":0,"currency":"USD"}}'

jobs

Get

GET/v1/jobs/{jobId}

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.

Scope
jobs:read
Effect
Read only
MCP tool
get_job
Retries
Safe to repeat

Parameters

Get parameters
NameInTypeNotes
jobIdrequiredpathid—

Returns

data: Job

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Update request body
FieldTypeNotes
expectedUpdatedAtrequiredstringup to 40 chars
labeloptionalstringup to 200 chars
clientIdoptionalid | null—
assigneeUidoptionalid | null—
currencyoptionalCurrency—
estimatedValueoptionalobject | null—
fieldsoptionalobject—
fieldListsoptionalobject—
fieldAddressesoptionalobject—

Returns

data: Job

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, TARGET_CHANGED, FIELD_FORBIDDEN, VALIDATION_FAILED.

Example requestSample
curl -X PATCH "https://api.billingmart.com/v1/jobs/{jobId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expectedUpdatedAt":"<expectedUpdatedAt>"}'

jobs

Validate intake

GET/v1/jobs/{jobId}/validate-intake

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.

Scope
jobs:read
Effect
Read only
MCP tool
validate_job_intake
Retries
Safe to repeat

Parameters

Validate intake parameters
NameInTypeNotes
jobIdrequiredpathid—
atrequiredqueryenumOne of draft, activate, complete, bill

Returns

data: JobIntakeValidation

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.

Scope
jobs:read
Effect
Read only
MCP tool
get_job_overview
Retries
Safe to repeat

Parameters

Get overview parameters
NameInTypeNotes
jobIdrequiredpathid—
includeSubJobsoptionalqueryboolean—

Returns

data: JobOverview

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.

Scope
bills:read
Effect
Read only
MCP tool
get_job_payables
Retries
Safe to repeat

Parameters

Get payables parameters
NameInTypeNotes
jobIdrequiredpathid—

Returns

data: JobPayables

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`.

Scope
jobs:read
Effect
Read only
MCP tool
list_job_billing
Retries
Safe to repeat

Parameters

List billing parameters
NameInTypeNotes
jobIdrequiredpathid—

Returns

data: JobBilling

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.

Scope
jobs:read
Effect
Read only
MCP tool
preview_delete_job
Retries
Safe to repeat

Parameters

Preview delete parameters
NameInTypeNotes
jobIdrequiredpathid—
withSubJobsoptionalqueryboolean—

Returns

data: JobDeletePlan

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
NameInTypeNotes
rootJobIdrequiredpathid—

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Move request body
FieldTypeNotes
newParentIdrequiredid | null—
expectedUpdatedAtoptionalstringup 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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/move" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"newParentId":"<newParentId>"}'

jobs

Set contract total

POST/v1/jobs/{jobId}/contract-total

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Set contract total request body
FieldTypeNotes
contractTotaloptionalobject—
contractTotal.minorUnitsrequiredinteger—
contractTotal.currencyrequiredCurrency—
fromEstimateIdoptionalid—
expectedUpdatedAtoptionalstringup 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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/contract-total" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"contractTotal":{"minorUnits":0,"currency":"USD"}}'

jobs

Set stage

POST/v1/jobs/{jobId}/stage

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Set stage request body
FieldTypeNotes
fromStageIdrequiredstringmatches ^[a-z][a-zA-Z0-9]{0,31}$
toStageIdrequiredstringmatches ^[a-z][a-zA-Z0-9]{0,31}$
noteoptionalstringup to 500 chars
completeAnywayoptionalboolean—

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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/stage" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"fromStageId":"<fromStageId>","toStageId":"<toStageId>"}'

jobs

Assign

POST/v1/jobs/{jobId}/assign

Assign a work record to an active member who can see its business, or clear the assignee with null.

Scope
jobs:write
Effect
Writes a draft or record
MCP tool
assign_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours

Parameters

Assign parameters
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Assign request body
FieldTypeNotes
assigneeUidrequiredid | null—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, JOB_CLOSED, VALIDATION_FAILED.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/assign" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"assigneeUid":"<assigneeUid>"}'

jobs

Set next action

POST/v1/jobs/{jobId}/next-action

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Set next action request body
FieldTypeNotes
textoptionalstringup to 200 chars
dueDateoptionalstringmatches ^\d{4}-\d{2}-\d{2}$
dueAtoptionalstringup to 40 chars
assigneeUidoptionalid—
doneoptionalboolean—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, JOB_CLOSED, VALIDATION_FAILED.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/next-action" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

jobs

Close

POST/v1/jobs/{jobId}/close

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Close request body
FieldTypeNotes
fromStageIdoptionalstringmatches ^[a-z][a-zA-Z0-9]{0,31}$
completeAnywayoptionalboolean—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, MISSING_ANSWER, TARGET_CHANGED, JOB_NOT_ACTIVE, JOB_CLOSED.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/close" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

jobs

Reopen

POST/v1/jobs/{jobId}/reopen

Reopen a completed work record into the type’s first open stage.

Scope
jobs:write
Effect
Writes a draft or record
MCP tool
reopen_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours

Parameters

Reopen parameters
NameInTypeNotes
jobIdrequiredpathid—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, INVALID_TRANSITION.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/reopen" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

jobs

Discard

POST/v1/jobs/{jobId}/discard

Discard a draft work record that was never activated. It is hidden from lists, never counted, and an activated record is refused.

Scope
jobs:write
Effect
Writes a draft or record
MCP tool
discard_job
Retries
Send an Idempotency-Key; a repeat replays the stored answer for 24 hours

Parameters

Discard parameters
NameInTypeNotes
jobIdrequiredpathid—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_A_DRAFT.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/discard" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

jobs

Activity

GET/v1/jobs/{jobId}/activity

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
NameInTypeNotes
jobIdrequiredpathid—
limitoptionalqueryintegermin 1 · max 50
cursoroptionalquerystringup 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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Notes request body
FieldTypeNotes
textrequiredstringup to 2000 chars

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/notes" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"text":"<text>"}'

jobs

Messages

GET/v1/jobs/{jobId}/messages

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
NameInTypeNotes
jobIdrequiredpathid—
limitoptionalqueryintegermin 1 · max 50
cursoroptionalquerystringup 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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Messages request body
FieldTypeNotes
channelrequiredenumOne of email, sms
subjectoptionalstringup to 200 chars
bodyrequiredstringup to 1600 chars

Returns

data: object

Errors this route can answer besides the shared ones: NOT_FOUND, VALIDATION_FAILED, INSUFFICIENT_SCOPE.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/messages" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel":"email","body":"<body>"}'

jobs

Documents

POST/v1/jobs/{jobId}/documents

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Documents request body
FieldTypeNotes
fileNamerequiredstringup to 500 chars
contentTyperequiredenumOne of image/png, image/jpeg, image/webp, application/pdf
sizerequiredintegermin 1 · max 26214400
categoryIdoptionalstringmatches ^[a-z][a-zA-Z0-9]{0,31}$
createRequestIdrequiredid—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, JOB_CLOSED, PLAN_LIMIT.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/documents" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"<fileName>","contentType":"image/png","size":0,"createRequestId":"<createRequestId>"}'

jobs

Documents

GET/v1/jobs/{jobId}/documents

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
NameInTypeNotes
jobIdrequiredpathid—

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
NameInTypeNotes
jobIdrequiredpathid—
docIdrequiredpathid—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/documents/{docId}/complete" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

jobs

Documents

GET/v1/jobs/{jobId}/documents/{docId}/url

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.

Scope
jobs:read
Effect
Read only
MCP tool
get_job_document_url
Retries
Safe to repeat

Parameters

Documents parameters
NameInTypeNotes
jobIdrequiredpathid—
docIdrequiredpathid—

Returns

data: JobDocumentUrl

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
NameInTypeNotes
jobIdrequiredpathid—
docIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Documents request body
FieldTypeNotes
clientVisibleoptionalconstantAlways false
categoryIdoptionalstringmatches ^[a-z][a-zA-Z0-9]{0,31}$

Returns

data: JobDocument

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.

Example requestSample
curl -X PATCH "https://api.billingmart.com/v1/jobs/{jobId}/documents/{docId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

jobs

Documents

DELETE/v1/jobs/{jobId}/documents/{docId}

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.

Scope
jobs:write
Effect
Writes a draft or record
MCP tool
remove_job_document
Retries
No Idempotency-Key on this route

Parameters

Documents parameters
NameInTypeNotes
jobIdrequiredpathid—
docIdrequiredpathid—

Returns

data: JobDocument

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND.

Example requestSample
curl -X DELETE "https://api.billingmart.com/v1/jobs/{jobId}/documents/{docId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

jobs

Adjustments

GET/v1/jobs/{jobId}/adjustments

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
NameInTypeNotes
jobIdrequiredpathid—

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
NameInTypeNotes
jobIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Adjustments request body
FieldTypeNotes
kindIdrequiredstringmatches ^[a-z][a-zA-Z0-9]{0,31}$
titlerequiredstringup to 200 chars
descriptionoptionalstringup to 4000 chars
fieldsoptionalobject—
amountDeltaoptionalobject—
amountDelta.minorUnitsrequiredinteger—
amountDelta.currencyrequiredCurrency—
effectiveDateoptionalstringmatches ^\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.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/jobs/{jobId}/adjustments" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"kindId":"<kindId>","title":"<title>","amountDelta":{"minorUnits":0,"currency":"USD"}}'

jobs

Adjustments

GET/v1/jobs/{jobId}/adjustments/{adjustmentId}

One adjustment of a work record, as jobs.adjustments.list returns it. An adjustment of another record, or one that does not exist, is NOT_FOUND.

Scope
jobs:read
Effect
Read only
MCP tool
get_job_adjustment
Retries
Safe to repeat

Parameters

Adjustments parameters
NameInTypeNotes
jobIdrequiredpathid—
adjustmentIdrequiredpathid—

Returns

data: JobAdjustment

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
NameInTypeNotes
jobIdrequiredpathid—
adjustmentIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Adjustments request body
FieldTypeNotes
expectedUpdatedAtrequiredstringup to 64 chars
titleoptionalstringup to 200 chars
descriptionoptionalstring | null—
fieldsoptionalobject—
amountDeltaoptionalobject | null—
effectiveDateoptionalstring | null—

Returns

data: JobAdjustment

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, NOT_A_DRAFT, TARGET_CHANGED, VALIDATION_FAILED.

Example requestSample
curl -X PATCH "https://api.billingmart.com/v1/jobs/{jobId}/adjustments/{adjustmentId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expectedUpdatedAt":"<expectedUpdatedAt>"}'

jobs

Adjustments

DELETE/v1/jobs/{jobId}/adjustments/{adjustmentId}

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.

Scope
jobs:write
Effect
Writes a draft or record
MCP tool
discard_job_adjustment
Retries
No Idempotency-Key on this route

Parameters

Adjustments parameters
NameInTypeNotes
jobIdrequiredpathid—
adjustmentIdrequiredpathid—

Returns

data: JobAdjustment

Errors this route can answer besides the shared ones: NOT_AVAILABLE, NOT_FOUND, NOT_A_DRAFT.

Example requestSample
curl -X DELETE "https://api.billingmart.com/v1/jobs/{jobId}/adjustments/{adjustmentId}" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY"

unbilled

Preview

GET/v1/clients/{clientId}/unbilled

The lines and totals a rebill would produce right now for a client: unbilled time, expenses and mileage in the business currency. Nothing is written.

Scope
invoices:read
Effect
Read only
MCP tool
preview_unbilled
Retries
Safe to repeat

Parameters

Preview parameters
NameInTypeNotes
clientIdrequiredpathid—
businessIdrequiredqueryid—
kindsoptionalqueryenum[]Each one of timeEntry, expense, mileage · up to 3 items · at least 1
throughoptionalquerystringmatches ^\d{4}-\d{2}-\d{2}$
jobIdoptionalqueryid—
invoiceIdoptionalqueryid—

Returns

data: UnbilledPreview

Example requestSample
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
NameInTypeNotes
invoiceIdrequiredpathid—

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.

Scope
payments:read
Effect
Read only
MCP tool
get_pay_link_status
Retries
Safe to repeat

Parameters

Pay link status parameters
NameInTypeNotes
invoiceIdrequiredpathid—

Returns

data: PayLinkStatus

Example requestSample
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
NameInTypeNotes
invoiceIdrequiredpathid—

Returns

data: object

Errors this route can answer besides the shared ones: NOT_A_DRAFT, ACTION_PENDING_ON_TARGET.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/discard" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

invoices

Set dunning paused

POST/v1/invoices/{invoiceId}/dunning

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
NameInTypeNotes
invoiceIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Set dunning paused request body
FieldTypeNotes
pausedrequiredboolean—

Returns

data: object

Example requestSample
curl -X POST "https://api.billingmart.com/v1/invoices/{invoiceId}/dunning" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"paused":false}'

reminders

Propose ladder

POST/v1/businesses/{businessId}/dunning-ladder-proposal

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
NameInTypeNotes
businessIdrequiredpathid—

Request body

JSON. Money is an integer count of minor units beside its currency; quantities are thousandths.

Propose ladder request body
FieldTypeNotes
toneoptionalenumOne of friendly, neutral, firm

Returns

data: object

Errors this route can answer besides the shared ones: AI_DISABLED, AI_CAP_REACHED, AI_CREDITS_EXHAUSTED.

Example requestSample
curl -X POST "https://api.billingmart.com/v1/businesses/{businessId}/dunning-ladder-proposal" \
  -H "Authorization: Bearer $BILLINGMART_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Object

Invoice

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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRef—
merchantAccountIdoptionalIdWHICH TranzPay merchant account this invoice is charged through.
numberoptionalstringThe 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.
clientIdrequiredId—
clientrequiredInvoiceClientSnapshot—
statusrequiredInvoiceStatus—
currencyrequiredCurrencyThe invoice's own currency: every amount on it is in this currency. Nothing is converted between currencies; reports keep one row per currency.
languagerequiredLanguageThe language the customer-facing artefacts render in.
issueDateoptionalDateOnly—
dueDateoptionalDateOnlyA 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.
lineItemsrequiredLineItem[]—
totalsrequiredInvoiceTotals—
customFieldsoptionalobjectUntrusted 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.
notesoptionalstringUntrusted textPUBLIC. Prints on the customer's copy.
taxCategoryoptionalTaxCategoryCodeThe tax category a person confirmed (a code of the taxonomy). Never set by a suggestion alone.
taxCategoryVersionoptionalstringThe taxonomy edition taxCategory is a code of, e.g. us-sch-c-2026.
taxCategorySourceoptionalTaxCategorySource—
taxScheduleCodeoptionalTaxCategoryCodeThe 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.
taxScheduleLineoptionalstringThe form line of taxScheduleCode at confirmation, e.g. Line 8; absent where the category is not a line of the form.
internalNotesoptionalstringUntrusted textINTERNAL. Never crosses to the public pay link.
termsoptionalstringUntrusted text—
poNumberoptionalstringUntrusted text—
templateIdoptionalId—
statusHistoryoptionalStatusChange[]INTERNAL. Never crosses to the public pay link.
sentAtoptionalIsoTimestamp—
paidAtoptionalIsoTimestamp—
voidedAtoptionalIsoTimestamp—
payLinkoptionalInvoicePayLinkThe 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.
feeOverrideoptionalInvoiceFeeOverrideWhether 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.
recurringIdoptionalIdThe schedule that issued this invoice, if any.
estimateIdoptionalIdThe estimate this was converted from, if any.
jobRefoptionalJobRefThe job this invoice belongs to, if any.
jobRoleoptionalJobInvoiceRoleDeposit, progress or retainage release — set with jobRef by the job callables.
retainageBasisPointsoptionalBasisPointsRetainage withheld from this invoice as basis points of the subtotal; totals.retainageWithheld is derived from it at every re-derivation, including send.
createdAtrequiredIsoTimestamp—
updatedAtoptionalIsoTimestamp—
createdByoptionalIdINTERNAL. Never crosses to the public pay link.
dunningoptionalInvoiceDunningReminder 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.
discardedAtoptionalIsoTimestamp. 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).
discardedByoptionalId. The uid or credential that discarded it.
lastWriteActoroptionalWriteActor—
openDisputesoptionalintegerPR 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.
cardOnlyoptionalbooleanServer-written: this invoice can be paid by CARD ONLY — an ACH debit, direct or from a saved method, is refused before the gateway.
creditOrderoptionalCreditOrderRef—
revisionoptionalintegerHow 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.
revisedAtoptionalIsoTimestampWhen the invoice was last revised. FUNCTION-WRITTEN with revision. INTERNAL: never crosses to the pay link, the PDF or a customer view.
autopayHeldoptionalInvoiceAutopayHold—
jobAdjustmentIdoptionalIdThe job adjustment this invoice was drafted from. FUNCTION-WRITTEN and in no client allowlist; the adjustment's invoiceIds is derived from it.
jobIdoptionalIdThe 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.
recordAnswersoptionalInvoiceRecordAnswer[]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
FieldTypeNotes
idrequiredIdServer-minted. A backfilled record takes the legacy jobRef.id.
tenantrequiredTenantRef—
numberoptionalstringMinted at activation inside the metering transaction (JOB-{YYYY}-{seq} by default). Absent on a draft.
typeIdrequiredId—
typeRevisionrequiredintegerThe job type revision this record's answers are validated against. Pinned at creation; a type change is an explicit, previewed act.
typeLabelrequiredstringUntrusted textThe type's singular label when the record was created, so a list renders without reading the type.
labelrequiredstringUntrusted text—
statusrequiredJobStatus—
clientIdoptionalIdThe one client this record is for. A record has at most one client and one currency.
currencyrequiredCurrencyLocked at activation: every invoice, bill allocation and total on the record is in it.
parentJobIdoptionalIdThe 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.
ancestorIdsoptionalId[]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.
depthoptionalinteger0 at the top level, up to 3: four levels in all. Function-written.
childCountoptionalintegerLive (not discarded) sub-jobs directly under this record, so a list can show "4 sub-jobs" without a query. Function-written.
childNumberCounteroptionalintegerThe 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.
estimatedValueoptionalMoneyAn informational budget, shown as "Not set" when absent. Never the progress basis.
contractTotaloptionalMoneyThe progress basis the deposit and progress maths run against. Locked while a live deposit or progress invoice exists.
fieldsrequiredJobFieldValues—
fieldListsoptionalJobFieldLists—
fieldAddressesoptionalJobFieldAddresses—
fieldsOrphanedoptionalJobFieldsOrphaned—
fieldListsOrphanedoptionalJobFieldListsOrphaned—
fieldAddressesOrphanedoptionalJobFieldAddressesOrphaned—
stageoptionalJobStageState—
intakeStepoptionalenum. The intake step a DRAFT was last on, so it resumes there on any device. Written by updateJobFields; meaningless once the record is active.
stageHistoryoptionalJobStageChange[]The most recent stage moves, newest last. The full trail is the record's events. At most 100 entries; the callables enforce it.
assigneeUidoptionalIdThe member responsible. Set by a callable that checks the member may see this record's business.
nextActionoptionalJobNextAction—
sharingoptionalJobSharing—
publicLinkoptionalJobPublicLink—
notesInternaloptionalstringUntrusted textStaff-only free text on the record. Never returned by REST or MCP and never in a public snapshot.
searchoptionalJobSearch—
answerClassesoptionalJobAnswerClasses—
searchKeysPiioptionalstring[]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.
searchKeysoptionalstring[]Untrusted textBounded, function-written keys built from the type's searchable fields. Sensitive answers never contribute. At most 40 keys; the callables enforce it.
billingoptionalJobBillingState—
legacyoptionalJobLegacy—
adjustmentCountersoptionalJobAdjustmentCounters—
meteredPeriodoptionalstringThe 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.
activatedAtoptionalIsoTimestamp—
activatedByoptionalId—
completedAtoptionalIsoTimestamp—
completedByoptionalId—
cancelledAtoptionalIsoTimestamp—
cancelledByoptionalId—
discardedAtoptionalIsoTimestampSoft discard of a record that was never activated.
discardedByoptionalId—
createdAtrequiredIsoTimestamp—
createdByoptionalIdFrom the verified token. INTERNAL: never returned by REST v1 or MCP, as on an invoice.
updatedAtrequiredIsoTimestamp—
lastWriteActoroptionalWriteActor—

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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRefCopied from the record onto every event, so the rule needs no parent read.
jobIdrequiredId—
kindrequiredJobLogKind—
keysoptionalstring[]For a fields or type change: the field KEYS touched. Never the values. At most 80 keys.
completedAnywayoptionalstring[]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.
fromoptionalstringThe earlier stage id or member uid, for a move or an assignment.
tooptionalstringThe later stage id or member uid.
messageIdoptionalId. For a message.* event: the message record it is about (tenants/{t}/messages/{id}). The message's body is never copied here.
channeloptionalenum. For a message.* event: how the message travelled.
unverifiedoptionalboolean. For message.sent: true when the email went to an address nobody has confirmed yet. Absent otherwise.
reasonoptionalstring. For message.blocked or message.failed: the catalogue code only, never the provider's text.
threadIdoptionalContactKey. For thread.linked and message.received: the text conversation (smsThreads/{id}). For thread.linked, from names the record it was moved from, if any.
actionIdoptionalId. 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.
atrequiredIsoTimestamp—
actorrequiredJobLogActor—

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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRef—
channelrequiredenum—
directionoptionalenuminbound 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.
kindrequiredenumWhy it was sent. custom is staff-written text from the console. refundConfirmation tells the customer a refund was issued, once per refund ledger entry.
clientIdoptionalIdThe client the message went to. OPTIONAL: a payment-link receipt has no client; it goes to the payment's receiptEmail.
contactKeyrequiredContactKeyThe verified, consenting contact it went to. Never the raw number.
toLast4optionalstringThe only digits of the number persisted.
invoiceIdoptionalId—
estimateIdoptionalIdFor an estimateSent: the estimate it announces.
paymentIdoptionalIdFor a receipt or a refundConfirmation: the payment it acknowledges.
paymentLinkIdoptionalIdFor a paymentLinkSent: the payment link it delivers.
ledgerEntryIdoptionalIdFor a refundConfirmation: the refund ledger entry it confirms. One message per entry.
subjectoptionalstringUntrusted textEmail only: the subject line as sent.
attachmentStoragePathoptionalstringEmail 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.
attachmentsoptionalintegerEmail only: how many files went with it (the invoice PDF is one). The files themselves are the tenant's pdfs/ objects, not copies.
bodyoptionalstringUntrusted textAs sent, footer included.
statusrequiredenumblocked 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…
reasonoptionalstringWhy 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…
providerMessageIdoptionalstring—
segmentsoptionalintegerSMS segments the body needs (160 GSM / 70 UCS-2 per segment).
deliveryClaimedAtoptionalIsoTimestampSMS 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.
smsAccountoptionalenumSMS only: whose Twilio account carried it — the platform's shared number, or the workspace's own account and sender.
consentAtoptionalIsoTimestampWhen the consent in force at send time was recorded, snapshotted here.
unverifiedoptionalbooleanSet 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.
sentByoptionalIdThe staff uid, from the verified token; absent for a system send.
createdAtrequiredIsoTimestamp—
sentAtoptionalIsoTimestamp—
updatedAtoptionalIsoTimestamp—
threadIdoptionalContactKeySMS only: the smsThreads/{id} this message belongs to. Equal to contactKey: the thread id is the contact key, so both directions join.
mediaoptionalobject[]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.
providerStatusAtoptionalIsoTimestampWhen the provider last reported this outbound message's status. A status only moves forward.
jobIdoptionalIdThe 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.
earlyStatusoptionalobjectFUNCTION-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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRefCopied from the record, so a member confined to some businesses reads a document exactly when they can read its record.
jobIdrequiredId—
staterequiredJobDocumentState—
categoryIdoptionalstringOne of the record's type revision's document category ids, or other. An archived category keeps rendering on the documents that already use it.
nameoptionalstringUntrusted textThe file name as the person gave it.
contentTypeoptionalstringThe type the server verified from the file's first bytes, not the one the browser declared.
sizeoptionalintegerBytes.
storagePathoptionalstringWhere the file is stored. Never returned by REST or MCP and never in a public snapshot.
clientVisibleoptionalbooleanWhether the client may see this document on the shared record page and the portal, subject to the record's sharing.
scanoptionalJobDocumentScan—
uploadedByoptionalIdFrom the verified token.
createdAtrequiredIsoTimestamp—
attachedAtoptionalIsoTimestamp—
removedAtoptionalIsoTimestampSoft removal: the metadata stays for the audit trail and the file is no longer offered.
removedByoptionalIdWho removed it, from the verified token.
failureoptionalJobDocumentFailure—
sensitiveoptionalbooleanSet by an owner or an admin: a sensitive document can never be client-visible.
expiresAtoptionalIsoTimestampWhile pending: when the upload window closes and the sweep expires it.
updatedAtoptionalIsoTimestamp—
lastWriteActoroptionalWriteActor—

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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRefCopied from the record, so a member confined to some businesses reads an adjustment exactly when they can read its record.
jobIdrequiredId—
kindIdrequiredstringThe adjustment kind's id in the job type revision this record pinned.
typeIdoptionalIdThe 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.
typeRevisionrequiredintegerThe job type revision the kind was read from.
numberoptionalstringThe per-record number, such as CO-2. Minted when issued; absent on a draft.
statusrequiredJobAdjustmentStatus—
titlerequiredstringUntrusted text—
descriptionoptionalstringUntrusted text—
fieldsoptionalJobAdjustmentFieldValues—
amountDeltaoptionalMoneyThe signed change to the record's value, in the record's currency; zero is allowed. Absent when the kind carries no amount.
effectiveDateoptionalDateOnly—
invoiceIdsoptionalId[]DERIVED from the invoices whose jobAdjustmentId names this adjustment; never written by a client.
issuedAtoptionalIsoTimestamp—
issuedByoptionalIdThe uid or credential that issued it.
voidedAtoptionalIsoTimestamp—
voidedByoptionalIdThe uid or credential that voided it.
voidReasonoptionalstringUntrusted text—
clientApprovaloptionalJobAdjustmentApprovalThe 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.
meteredPeriodoptionalstringThe tenant-local month (YYYY-MM) in which issuing it was counted. Absent on a draft.
createdAtrequiredIsoTimestamp—
createdByrequiredIdThe uid or credential that created it.
updatedAtrequiredIsoTimestamp—
lastWriteActoroptionalIdWho 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
FieldTypeNotes
idrequiredId—
tenantrequiredTenantRef—
typerequiredLedgerEntryType—
amountrequiredMoneySigned. 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.
invoiceIdoptionalId—
paymentIdoptionalId—
paymentLinkIdoptionalIdThe payment link a payment-link entry belongs to (it has no invoiceId).
clientIdoptionalId—
billIdoptionalId—
reversesEntryIdoptionalIdThe entry this one compensates. Set on every correction, because the corrected entry itself is immutable.
occurredAtrequiredIsoTimestampWhen 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.
reportingRateoptionalReportingRate—
methodoptionalPaymentMethodPR B: the method of the payment this entry moves, on an achReturn (and a chargeback).
memooptionalstringUntrusted text—
actorUidoptionalIdTaken from the verified ID token, never from the payload. Absent for system-generated entries.
createdAtrequiredIsoTimestamp—

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.

Continue to Estimates →

Try it against your sandbox

Create a bmk_test_ key in the console. It can only reach your sandbox workspace.

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