Invoices & payments
List and read invoices, draft invoices from line items, edit them, email them to the customer, mark them sent, paid or void, or put them back to draft. Read payments.
View as MarkdownAn invoice bills a job's customer. TruTec has two kinds:
payment_method |
What it is | Through the API |
|---|---|---|
check |
A manual invoice the customer pays by check, ACH or another way you record. Statuses draft, sent, paid, void. |
Read, create, edit, send, mark sent, mark paid, void, back to draft. |
stripe |
An online invoice the customer pays by card or bank. Statuses open, paid, void, uncollectible, set only by the payment processor. |
Read, and create from an estimate with Request payment online. |
The API never charges a card. Invoice objects never carry the customer's payment link; the one place it is returned is the response of Request payment online.
Invoices you create start as drafts. A draft is not emailed and is not posted to accounting. Use /send to email it, or /mark-sent when you deliver it another way. When the workspace is connected to QuickBooks Online or another accounting system, the invoice and its payment sync on those steps exactly as in TruTec.
Money is in dollars with two decimals. The job invoice list (GET /v1/jobs/{id}/invoices, see Job costing) returns the same rows without the contents.
The invoice object#
| Field | Type | Description |
|---|---|---|
id, job_id |
integer | |
doc_number |
integer or null | The estimate's document number, for invoices billed from an estimate. |
status, payment_method, billing_type |
string | billing_type is full, deposit, milestone, custom, line_items or recurring. |
description |
string | First line item, or "N line items". |
line_items |
array | {description, amount}. |
subtotal, discount, tax, amount |
number | amount = subtotal − discount + tax. Each also comes in cents (amount_cents and so on). |
due_date, payment_terms |
For example Net 30 or Due on receipt. |
|
issued_at, paid_at, updated_at, created_at |
string | UTC. |
client_name, client_email, client_address |
Who is billed. | |
project_name, project_address, reference_number, memo |
||
remit_to_name, remit_to_address, payment_instructions |
Where and how to pay. | |
sender_name, sender_email, created_by |
Who it's from. | |
estimate_id |
integer or null | The estimate it bills. |
quickbooks |
object | has_invoice, has_payment, invoice_synced_at, sync_error. |
Invoices#
List invoices#
GET /v1/invoices
Permission: Read jobs
Newest first. With updated_after, sorted by updated_at, oldest first.
| Name | Type | Required | Description |
|---|---|---|---|
job_id |
integer | No | |
status |
string | No | |
payment_method |
string | No | check or stripe. |
updated_after |
string | No | ISO 8601. |
limit, offset |
integer | No | Default 50, maximum 200. |
curl "https://trutec.ai/api/v1/invoices?job_id=70412" \
-H "X-API-Key: sk_your_api_key_here"
{
"data": [
{
"id": 7790,
"job_id": 70412,
"doc_number": null,
"status": "draft",
"billing_type": "line_items",
"payment_method": "check",
"description": "2 line items",
"currency": "usd",
"amount_cents": 1405050,
"amount": 14050.5,
"subtotal_cents": 1395050,
"subtotal": 13950.5,
"discount_cents": 0,
"discount": 0.0,
"tax_cents": 10000,
"tax": 100.0,
"due_date": "2026-11-26T15:02:11Z",
"issued_at": "2026-11-11T15:02:11Z",
"paid_at": null,
"updated_at": "2026-11-11T15:02:11Z",
"quickbooks": { "has_invoice": false, "has_payment": false, "invoice_synced_at": null, "sync_error": null },
"line_items": [
{ "description": "Sealcoat, 2 coats", "amount": 12500.0 },
{ "description": "Striping", "amount": 1450.5 }
],
"client_name": "Northgate Property Group",
"client_email": "[email protected]",
"client_address": null,
"project_name": "Northgate Plaza reseal",
"project_address": "1200 Commerce Dr, Austin, TX 78701",
"reference_number": "PO-5521",
"memo": "Thank you for your business.",
"payment_terms": "Net 15",
"remit_to_name": "Northgate Paving",
"remit_to_address": "400 Industrial Way, Austin, TX 78702",
"payment_instructions": null,
"sender_name": "Northgate Paving",
"sender_email": "[email protected]",
"estimate_id": null,
"created_by": { "id": 12, "name": "Office Manager" },
"created_at": "2026-11-11T15:02:11Z"
}
],
"pagination": { "total": 1, "limit": 50, "offset": 0 }
}
Get an invoice#
GET /v1/invoices/{id}
Permission: Read jobs
Create a draft invoice#
POST /v1/invoices
Permission: Write jobs
Creates a manual (check) invoice on a job as a draft. The customer name and email default from the job's bill-to, then its company and contact. The remit-to address defaults to the last one the workspace used.
| Name | Type | Required | Description |
|---|---|---|---|
job_id |
integer | Yes | |
line_items |
array | Yes | [{"description": "...", "amount": 1250.00}], amounts in dollars, above 0. |
client_name, client_email, client_address |
string | No | Override the job's billing contact. |
billing_type |
string | No | line_items (default), full, deposit, milestone or custom. |
due_days |
integer | No | Days until due. Default 30; 0 is due on receipt. |
discount, tax |
number | No | Dollars. The discount can't exceed the subtotal. |
memo, reference_number, project_name, project_address |
string | No | |
remit_to_name, remit_to_address, payment_instructions |
string | No | A remit-to address is required if the workspace has none on file. |
estimate_id |
integer | No | The job's estimate this invoice bills. |
curl -X POST "https://trutec.ai/api/v1/invoices" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"job_id": 70412,
"line_items": [
{ "description": "Sealcoat, 2 coats", "amount": 12500 },
{ "description": "Striping", "amount": 1450.50 }
],
"tax": 100,
"due_days": 15,
"reference_number": "PO-5521"
}'
Returns 201 with the invoice.
Errors: 400 for missing or invalid line items, a missing remit-to address, or a total of zero. 404 when the job or estimate is not in your workspace.
Update an invoice#
PATCH /v1/invoices/{id}
Permission: Write jobs
Changes the fields you send (the same fields as Create, except job_id and estimate_id). line_items replaces the whole list. The due date stays anchored to the issue date. If the invoice is already in QuickBooks Online, the edit is pushed there too. Online invoices (stripe) return 409; invoices imported from another system keep their original figures (409).
Send an invoice#
POST /v1/invoices/{id}/send
Permission: Write jobs
This emails the customer (client_email) the invoice with a link to view it, from the workspace's designated invoice sender, or else from the key creator's connected mailbox. A draft becomes sent and, on first send, is created in the connected accounting system. You can send again to resend. Returns 200 with emailed: true.
Errors: 422 when the invoice has no client_email. 409 for a void or online invoice. 502 if the email could not be delivered.
Mark sent, mark paid, or void#
POST /v1/invoices/{id}/mark-sent
POST /v1/invoices/{id}/mark-paid
POST /v1/invoices/{id}/void
Permission: Write jobs
Nothing is emailed. mark-sent is for invoices you delivered yourself (it also moves a paid invoice back to unpaid). mark-paid records the payment today and posts it to the connected accounting system. void voids the invoice there too. Online invoices return 409: their status follows the payment processor.
Request payment online#
POST /v1/estimates/{id}/request-payment
Permission: Write jobs (the key's user needs pricing access)
Bills an estimate with an online invoice, exactly like Create invoice with the Online (card / bank) method on the estimate's Billing tab. The amount, bill-to, due date, accounting sync and double-billing guard are TruTec's own.
Warning
This emails the customer. The workspace's connected payments account emails them a page to pay by card or bank right away. Confirm the amount and the customer before calling it.
| Name | Type | Description |
|---|---|---|
kind |
string | full (the contract, the default), deposit (the proposal's deposit percent), progress (work done so far minus what's already billed) or itemized (one line per item). |
due_days |
integer | Days until due. Default: the proposal's payment terms. |
memo, reference_number |
string | reference_number is the customer's PO number. |
client_name, client_email |
string | Who is billed and where the invoice is emailed. Default: the job's bill-to, else the proposal's customer. |
confirm_duplicate |
boolean | Bill anyway after a 409 double-billing refusal. |
Needs online payments connected in TruTec (Settings › Payments; 409 Online payments not set up otherwise) and a prepared proposal (409 No proposal yet; see Proposals). The same invoice again within 10 minutes, or a full-contract invoice that would bill more than the contract, is refused with 409 and code duplicate_invoice or over_contract unless you send confirm_duplicate: true.
curl -X POST "https://trutec.ai/api/v1/estimates/9840/request-payment" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"kind": "deposit"}'
{
"data": {
"invoice": {"id": 9120, "job_id": 70412, "status": "open", "payment_method": "stripe", "billing_type": "deposit", "amount": 12125.0, "...": "..."},
"payment_url": "https://invoice.stripe.com/i/acct_example/test_example",
"emailed": true,
"message": "Invoice created and submitted to Stripe for email delivery."
}
}
Returns 201. emailed is false when the invoice was made but the payments account couldn't confirm the email: share payment_url yourself rather than requesting again. The invoice's status follows the payment processor (webhook payment_received when paid). Mailed-check invoices: Create a draft invoice.
Payments#
List payments#
GET /v1/payments
Permission: Read jobs
One row per paid invoice, most recent first. TruTec records a payment when an invoice is marked paid, or when the customer pays an online invoice. Partial payments are not tracked.
| Name | Type | Required | Description |
|---|---|---|---|
job_id |
integer | No | |
paid_after |
string | No | ISO 8601. |
limit, offset |
integer | No |
{
"data": [
{
"invoice_id": 7781,
"job_id": 70412,
"doc_number": 1412,
"amount": 14550.0,
"currency": "usd",
"paid_at": "2026-10-18T19:02:13Z",
"payment_method": "check",
"client_name": "Northgate Property Group",
"quickbooks": { "has_payment": true, "payment_synced_at": "2026-10-18T19:02:40Z" }
}
],
"pagination": { "total": 1, "limit": 50, "offset": 0 }
}
Back to draft#
Move an invoice back to draft#
POST /v1/invoices/{id}/mark-draft
Permission: Write jobs
The same as choosing Draft in the invoice's status menu in TruTec. A void invoice is no longer void, and a paid invoice loses its payment date (it drops out of GET /v1/payments). Nothing is emailed. Use it to undo a mark-sent, mark-paid or void made by mistake, then edit the invoice and send it again.
If the invoice is already in the connected accounting system, that copy stays there: marking the invoice sent again pushes your edits to it (a copy that was voided there is replaced with a new one). A payment already posted there is not removed; remove it in the accounting system.
curl -X POST "https://trutec.ai/api/v1/invoices/7790/mark-draft" \
-H "X-API-Key: sk_your_api_key_here"
Returns 200 with the invoice, status draft.
Errors: 400 when the invoice is linked to another invoicing system and was already sent or paid. 409 for an online invoice. 404 when the invoice is not in your workspace.
Deleting invoices#
Invoices can't be deleted, in TruTec or through the API. Void one instead (POST /v1/invoices/{id}/void): it stays on the job for your records and is voided in the connected accounting system too.