Endpoints
Invoices
List invoices, generate a draft or bill a set amount, and record payments
List invoices
GET /api/v1/invoices
Any role. Returns the organization's invoices, newest first, each with its
client ({ id, name }). Owner and admin keys get every invoice. A member's
key doesn't get invoices with a line on a confidential project the member
isn't on.
statusstringqueryOnly DRAFT, SENT, PAID, or VOID invoices. Any other value is
ignored. A VOID invoice keeps its lines and total, but the time
entries, milestones and expenses on it were released when it was voided
and can be generated onto a new invoice.
curl "https://<your-instance>/api/v1/invoices?status=SENT" \
-H "Authorization: Bearer ch_live_..."{
"invoices": [
{
"id": "cm9...",
"number": "INV-0042",
"status": "SENT",
"issueDate": "2026-08-01T00:00:00.000Z",
"dueDate": "2026-08-31T00:00:00.000Z",
"paymentTerms": "NET30",
"subtotal": "5250",
"taxAmount": "0",
"total": "5250",
"currency": "USD",
"kind": "STANDARD",
"amountPaid": "2000.00",
"creditApplied": "0.00",
"balanceDue": "3250.00",
"paidAt": null,
"firstViewedAt": "2026-08-02T16:40:12.000Z",
"viewCount": 3,
"client": { "id": "cm9...", "name": "Fintra Labs" },
"...": "..."
}
]
}amountPaid is the sum of the payments recorded on the invoice and
creditApplied the client credit applied to
it; balanceDue is total minus both (never below 0.00, and 0.00 for a
VOID invoice). A partly paid invoice stays SENT until balanceDue
reaches 0.00. kind is STANDARD, or DEPOSIT for a
deposit invoice. paidAt is when
the invoice became paid in full (by hand, or when an online payment went
through), or null. firstViewedAt, lastViewedAt and
viewCount track when the client opened the invoice. To find overdue invoices, look for SENT invoices whose
dueDate has passed (the MCP list_invoices tool returns isOverdue and
daysOverdue for you).
Generate an invoice
POST /api/v1/invoices/generate
Owner/admin only. Creates a draft from unbilled time entries, completed
milestones, and/or approved expenses, the same as Generate invoice in
the app. Returns 201 with { "invoice": { ... } }, including its id,
number and computed subtotal, taxAmount and total. Review and send the draft in the app, or with the MCP
email_invoice or mark_invoice_sent tools.
clientIdstringrequiredtimeEntryIdsstring[]Billable time entries not yet on an invoice, on this client's projects. If your organization requires timesheet approval, the time must be approved.
milestoneIdsstring[]Completed milestones not yet on an invoice.
expenseIdsstring[]Approved expenses not yet on an invoice.
issueDatestringrequiredYYYY-MM-DD.
dueDatestringYYYY-MM-DD. Defaults to issueDate plus the payment terms. Required when
paymentTerms is CUSTOM.
paymentTermsstringDUE_ON_RECEIPT, NET15, NET30, NET45, NET60, NET90, or CUSTOM.
Defaults to the project's terms when every item is from one project that
sets them, otherwise the client's, otherwise the organization's default.
poNumberstringtaxRatenumberdefault: 0Percent, 0 to 100.
notesstringAt least one of timeEntryIds, milestoneIds, or expenseIds is required.
IDs that don't qualify (already invoiced, not billable, on another client's
project, and so on) are skipped. The invoice uses the organization's default
currency.
curl -X POST https://<your-instance>/api/v1/invoices/generate \
-H "Authorization: Bearer ch_live_..." \
-H "Content-Type: application/json" \
-d '{
"clientId": "cm9...",
"timeEntryIds": ["cm9...", "cm9..."],
"issueDate": "2026-08-01",
"paymentTerms": "NET30"
}'Errors
| Status | Message | Fix |
|---|---|---|
403 | Only owners and admins can generate invoices. | Use an owner or admin key. |
422 | { "timeEntryIds": ["Select at least one time entry, milestone, or expense"] } | Send at least one ID. |
422 | Client not found. | Check clientId. |
422 | No eligible unbilled time entries, milestones, or expenses were selected. | None of the IDs qualified. Check they're on this client's projects, billable, approved, and not already invoiced. |
422 | Custom payment terms need a due date. | Send dueDate with CUSTOM terms. |
Bill a set amount
POST /api/v1/invoices/custom
Owner/admin only. Creates a draft for amounts you name rather than logged
work: a set fee, a retainer or a deposit, the same as Set amount in the
app. Nothing is marked billed. Returns 201 with { "invoice": { ... } }.
clientIdstringrequiredlinesobject[]required1 to 100 lines, each { "description": string, "amount": number }. The
amount is to the cent and greater than zero.
depositbooleandefault: falseA deposit invoice: untaxed, and its payments become client credit.
projectIdstringOne of the client's projects. Its payment terms are the default, and a confidential project's invoice is hidden like its others.
issueDatestringrequiredYYYY-MM-DD.dueDatestringYYYY-MM-DD. Defaults to issueDate plus the payment terms.
paymentTermsstringAs for generate. Defaults to the project's terms, else the client's, else the organization's.
poNumberstringtaxRatenumberdefault: 0Percent, 0 to 100. Ignored for a deposit.
notesstringcurl -X POST https://<your-instance>/api/v1/invoices/custom \
-H "Authorization: Bearer ch_live_..." \
-H "Content-Type: application/json" \
-d '{
"clientId": "cm9...",
"lines": [{ "description": "Initial deposit", "amount": 500 }],
"deposit": true,
"issueDate": "2026-10-19",
"paymentTerms": "DUE_ON_RECEIPT"
}'Errors
| Status | Message | Fix |
|---|---|---|
403 | Only owners and admins can create invoices. | Use an owner or admin key. |
422 | { "lines": [...] } | Send at least one line with a description and a positive amount. |
422 | Client not found. | Check clientId. |
422 | Project not found for this client. | Use one of the client's projects, or leave projectId out. |
422 | Custom payment terms need a due date. | Send dueDate with CUSTOM terms. |
List an invoice's payments
GET /api/v1/invoices/{id}/payments
Any role that can see the invoice. Returns its amountPaid, creditApplied
and balanceDue, plus:
payments: each withamount,currency,receivedAt,source(MANUAL,STRIPE,MERCURYorQUICKBOOKS),method,reference,noteandrecordedBy(a name, ornullfor payments recorded by an integration), newest first.creditApplications: client credit applied to the invoice.creditNotes: credit notes issued against it.
Returns 404 with Invoice not found. for an invoice in another
organization or one the key can't see.
Record a payment
POST /api/v1/invoices/{id}/payments
Owner/admin only. Records money received against a sent invoice, the same as
Record payment in the app. The invoice becomes PAID when its balance
reaches zero (with the invoice paid alert); a part payment leaves it SENT
and sends the part payment received alert. Returns 201 with
{ "payment": { ... }, "invoice": { ... } }; invoice includes the new
amountPaid and balanceDue.
amountnumberrequiredIn the invoice's currency, greater than zero, to the cent. Can't be more
than balanceDue.
receivedAtstringYYYY-MM-DD. Defaults to today.
methodstringWire. Up to 100 characters.referencestringnotestringcurl -X POST https://<your-instance>/api/v1/invoices/cm9.../payments \
-H "Authorization: Bearer ch_live_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 2000, "receivedAt": "2026-08-20", "method": "Wire" }'Errors
| Status | Message | Fix |
|---|---|---|
403 | Only owners and admins can record payments. | Use an owner or admin key. |
404 | Invoice not found. | Check the id, and that the key can see the invoice. |
422 | { "amount": ["Amount must be greater than zero"] } | Send a positive amount. |
422 | That's more than the $3,250.00 still due on this invoice. | Record at most balanceDue. |
422 | Send the invoice before recording a payment. | The invoice is a draft. |
422 | This invoice is already paid in full. | Nothing is due. |
422 | This invoice is void. | Void invoices can't be paid. |
422 | Enter an amount greater than zero, to the cent. | Use at most two decimal places. |
To delete a payment recorded by mistake, use the invoice's Payments card in the app.