REST endpoints for invoices in ServFlo: 22 operations with parameters, request and response fields, permissions and curl examples.
Base URL: https://app.servflo.ai/api. Every call needs Authorization: Bearer sfk_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.
Invoices newest first, filtered by text (INV-n, customer), status or customer, paged with a total. Each item carries amountPaidCents, balanceDueCents and partiallyPaid. Read-only.
Creates a DRAFT invoice (INV-n) for a customer with the given lines; amounts are fixed once created. Tax is 0 for tax-exempt customers, else taxRate. MEMBER PRICING: when the customer has an active membership covering the job's property, the plan's discount is added as separate negative lines (services and materials apart) unless memberPricing: false. Nothing is sent: share it with POST /invoices/{invoiceId}/link and mark it SENT with PATCH. To bill a job's work use POST /jobs/{jobId}/invoice instead.
Percent, e.g. 8.25. Ignored for tax-exempt customers. -1000–1000.
notes
string or null
No
Printed on the invoice; the customer sees it.
dueDate
empty string or string or null
No
Defaults from the customer's payment terms.
lines
array of object
Yes
Up to 500 items.
lines[].description
string
Yes
lines[].quantity
number
Yes
-1000000–1000000.
lines[].unitPriceCents
integer
Yes
Money in integer cents; negative for a discount or credit line. -100000000–100000000.
lines[].kind
enum or null
No
For member pricing: SERVICE (labour, the default) or MATERIAL get the member discount for their kind; MEMBERSHIP (a plan fee) is never discounted. Discount lines are added by the server. One of SERVICE, MATERIAL, MEMBERSHIP.
lines[].pricebookItemId
string or null
No
The pricebook item the line was picked from (GET /pricebook/items): the server snapshots its cost; pricebook edits never change the invoice.
lines[].memberPriceApplied
boolean or null
No
Priced at the item's member price: the plan's member discount skips this line. Kept only for items with a member price.
memberPricing
boolean
No
Default true: a member customer's discount lines are added. false leaves them off (staff removed member pricing).
businessUnitId
string or null
No
Business unit (GET /business-units): its branding shows on the invoice and its accounting class or tracking applies. Omitted: the job's unit; null = none. Up to 100 characters.
customFields
object
No
Invoice custom field values (GET /custom-fields?entity=INVOICE); those marked visibleToCustomer print on the invoice, its emails and pages. Required ones are checked here.
GET /organizations/{organizationId}/invoices/{invoiceId}
Includes payments (with refunds), amountPaidCents (net of refunds, tips excluded), balanceDueCents, tipsCents, partiallyPaid and publicToken (the /i/{token} link, null until made). Read-only.
DRAFT (not issued), SENT (issued, money due), PAID, OVERDUE (past dueDate) or CANCELLED. One of DRAFT, SENT, PAID, OVERDUE, CANCELLED.
subtotalCents
integer
Lines total before tax, in integer cents.
taxRate
number
Tax rate in percent.
taxCents
integer
Tax, in integer cents.
totalCents
integer
Total with tax, in integer cents.
notes
string or null
dueDate
string or null
YYYY-MM-DD
paidDate
string or null
YYYY-MM-DD
amountPaidCents
integer
Paid so far, net of refunds, tips excluded, in integer cents.
membershipId
string or null
Set on a membership's billing invoice.
memberPricingRemoved
boolean
financingDisabled
boolean
progressBillingId
string or null
progressRole
enum or null
Its place in a progress billing. One of PROGRESS, FINAL, RETAINAGE_RELEASE.
progressMilestoneId
string or null
progressBilledCents
integer
Billed towards the contract on a progress invoice, in integer cents.
retainageCents
integer
Retainage held on a progress invoice, in integer cents.
businessUnitId
string or null
customFields
any
Custom field values by key (definitions: GET /custom-fields?entity=INVOICE).
createdAt
string (date-time)
updatedAt
string (date-time)
customer
object
customer.id
string
customer.name
string
customer.email
string or null
customer.phone
string or null
customer.address
string or null
customer.city
string or null
customer.state
string or null
customer.zip
string or null
businessUnit
object or null
The business unit.
businessUnit.id
string
businessUnit.name
string
job
object or null
job.id
string
job.identifier
string
JOB-n
job.signedBy
string or null
Work accepted by (the job's sign-off).
job.signedAt
string (date-time) or null
balanceDueCents
integer
Still due, in integer cents.
partiallyPaid
boolean
lines
array of object
lines[].id
string
lines[].invoiceId
string
lines[].description
string
lines[].quantity
number
lines[].unitPriceCents
integer
Unit price, in integer cents.
lines[].totalCents
integer
Line total, in integer cents.
lines[].sortOrder
integer
lines[].kind
enum or null
SERVICE or MATERIAL (member discount by kind), MEMBER_DISCOUNT (the automatic member pricing line) or MEMBERSHIP (a plan fee). One of SERVICE, MATERIAL, MEMBER_DISCOUNT, MEMBERSHIP.
lines[].pricebookItemId
string or null
lines[].costCents
integer or null
Our unit cost in cents (costing.read only).
lines[].memberPriceApplied
boolean
quotes
array of object
Quotes invoiced here (Q-n).
quotes[].id
string
quotes[].identifier
string
Q-n
quotes[].status
enum
DRAFT → SENT (customer can answer at /q/{publicToken}) → APPROVED / CHANGES_REQUESTED / DECLINED; APPROVED → CONVERTED (job or invoice made). SENT past validUntil becomes EXPIRED. One of DRAFT, SENT, APPROVED, CHANGES_REQUESTED, DECLINED, CONVERTED, EXPIRED, ARCHIVED.
payments
array of object
Payments on the invoice, with their refunds.
payments[].id
string
payments[].identifier
string
PAY-n
payments[].amountCents
integer
payments[].tipCents
integer
payments[].refundedCents
integer
payments[].method
enum
One of CASH, CHECK, CARD, BANK_TRANSFER, OTHER.
payments[].paidOn
string
YYYY-MM-DD
payments[].status
enum
One of SUCCEEDED, PENDING, FAILED, REFUNDED, PARTIALLY_REFUNDED.
tipsCents
integer
Tips collected, net of refunds, in integer cents.
memberPricing
object
memberPricing.applied
boolean
Member discount lines are on the document.
memberPricing.removed
boolean
Staff removed the member pricing from this document.
memberPricing.membership
object or null
The customer's active membership that would apply; null when not a member.
memberPricing.membership.membershipId
string
memberPricing.membership.identifier
string
MEM-n
memberPricing.membership.planName
string
memberPricing.membership.serviceDiscountPercent
number
memberPricing.membership.materialDiscountPercent
number
memberPricing.membership.priority
boolean
publicToken
string or null
The customer link token (/i/{token}); null until one is made.
Amounts are fixed once created. MONEY: setting status: PAID while a balance is due records that balance as a payment (method OTHER, dated paidDate or today) so payments and status agree; prefer POST /invoices/{invoiceId}/payments to record the real method. An invoice paid in full cannot be set back to DRAFT/SENT/OVERDUE (refund a payment instead, 409), and one holding payments cannot be CANCELLED (409). paidDate is only for paid invoices. Marking SENT does not email anything.
Move the invoice to another business unit (GET /business-units), or null for none. Up to 100 characters.
financingDisabled
boolean
No
Exposes to customers: true hides Affirm and Klarna on this invoice's pay page even when the business offers financing.
today
string
No
The business's today (YYYY-MM-DD), for the paid date and reopened-overdue checks.
customFields
object
No
Custom field values by key (definitions: GET /custom-fields?entity=INVOICE); merged into the saved values on an update, null clears one. Checked against each field's type and options; unknown keys are refused.
POST /organizations/{organizationId}/invoices/{invoiceId}/member-pricing
Member pricing: applied: false removes the automatic member discount lines and recomputes the totals; applied: true adds them back from the customer's active membership (409 when there is none). Only for DRAFT invoices without payments (amounts are fixed once issued; 409 otherwise). Read the state from GET /invoices/{invoiceId} (memberPricing).
DRAFT (not issued), SENT (issued, money due), PAID, OVERDUE (past dueDate) or CANCELLED. One of DRAFT, SENT, PAID, OVERDUE, CANCELLED.
subtotalCents
integer
Lines total before tax, in integer cents.
taxRate
number
Tax rate in percent.
taxCents
integer
Tax, in integer cents.
totalCents
integer
Total with tax, in integer cents.
notes
string or null
dueDate
string or null
YYYY-MM-DD
paidDate
string or null
YYYY-MM-DD
amountPaidCents
integer
Paid so far, net of refunds, tips excluded, in integer cents.
membershipId
string or null
Set on a membership's billing invoice.
memberPricingRemoved
boolean
financingDisabled
boolean
progressBillingId
string or null
progressRole
enum or null
Its place in a progress billing. One of PROGRESS, FINAL, RETAINAGE_RELEASE.
progressMilestoneId
string or null
progressBilledCents
integer
Billed towards the contract on a progress invoice, in integer cents.
retainageCents
integer
Retainage held on a progress invoice, in integer cents.
businessUnitId
string or null
customFields
any
Custom field values by key (definitions: GET /custom-fields?entity=INVOICE).
createdAt
string (date-time)
updatedAt
string (date-time)
customer
object
customer.id
string
customer.name
string
customer.email
string or null
customer.phone
string or null
customer.address
string or null
customer.city
string or null
customer.state
string or null
customer.zip
string or null
businessUnit
object or null
The business unit.
businessUnit.id
string
businessUnit.name
string
job
object or null
job.id
string
job.identifier
string
JOB-n
job.signedBy
string or null
Work accepted by (the job's sign-off).
job.signedAt
string (date-time) or null
balanceDueCents
integer
Still due, in integer cents.
partiallyPaid
boolean
lines
array of object
lines[].id
string
lines[].invoiceId
string
lines[].description
string
lines[].quantity
number
lines[].unitPriceCents
integer
Unit price, in integer cents.
lines[].totalCents
integer
Line total, in integer cents.
lines[].sortOrder
integer
lines[].kind
enum or null
SERVICE or MATERIAL (member discount by kind), MEMBER_DISCOUNT (the automatic member pricing line) or MEMBERSHIP (a plan fee). One of SERVICE, MATERIAL, MEMBER_DISCOUNT, MEMBERSHIP.
lines[].pricebookItemId
string or null
lines[].costCents
integer or null
Our unit cost in cents (costing.read only).
lines[].memberPriceApplied
boolean
quotes
array of object
Quotes invoiced here (Q-n).
quotes[].id
string
quotes[].identifier
string
Q-n
quotes[].status
enum
DRAFT → SENT (customer can answer at /q/{publicToken}) → APPROVED / CHANGES_REQUESTED / DECLINED; APPROVED → CONVERTED (job or invoice made). SENT past validUntil becomes EXPIRED. One of DRAFT, SENT, APPROVED, CHANGES_REQUESTED, DECLINED, CONVERTED, EXPIRED, ARCHIVED.
payments
array of object
Payments on the invoice, with their refunds.
payments[].id
string
payments[].identifier
string
PAY-n
payments[].amountCents
integer
payments[].tipCents
integer
payments[].refundedCents
integer
payments[].method
enum
One of CASH, CHECK, CARD, BANK_TRANSFER, OTHER.
payments[].paidOn
string
YYYY-MM-DD
payments[].status
enum
One of SUCCEEDED, PENDING, FAILED, REFUNDED, PARTIALLY_REFUNDED.
tipsCents
integer
Tips collected, net of refunds, in integer cents.
memberPricing
object
memberPricing.applied
boolean
Member discount lines are on the document.
memberPricing.removed
boolean
Staff removed the member pricing from this document.
memberPricing.membership
object or null
The customer's active membership that would apply; null when not a member.
memberPricing.membership.membershipId
string
memberPricing.membership.identifier
string
MEM-n
memberPricing.membership.planName
string
memberPricing.membership.serviceDiscountPercent
number
memberPricing.membership.materialDiscountPercent
number
memberPricing.membership.priority
boolean
publicToken
string or null
The customer link token (/i/{token}); null until one is made.
GET /organizations/{organizationId}/invoice-batches/jobs
Batch invoicing, step 1: jobs not on any live invoice and not billed in progress invoices, oldest first. By default only COMPLETED jobs (ready to invoice); readyOnly: false adds scheduled and in-progress ones. Filter by the job's date (from/to, YYYY-MM-DD), customer, service type, technician (lead or crew) or text. Each row carries its subtotal before member discount and tax, and warnings. Read-only. Next: GET /invoice-batches/preview, then POST /invoice-batches.
GET /organizations/{organizationId}/invoice-batches/preview
Batch invoicing, step 2: the invoices POST /invoice-batches would create for these jobs (one per job, or with perCustomer one per customer and tax rate combining their jobs): lines, member discount, tax, total and deposits that would move onto each; plus the jobs it would skip and why. Creates nothing.
POST /organizations/{organizationId}/invoice-batches
Batch invoicing, step 3: creates DRAFT invoices (INV-n) for the given jobs, one per job or (perCustomer) one per customer and tax rate, priced as POST /jobs/{jobId}/invoice would (member pricing, the job property's tax, deposits on the job's quote moved on). Answers one row per job: CREATED with the invoice, SKIPPED (already invoiced, progress-billed, cancelled, not found) or FAILED with the reason; one failing never stops the others. Safe to re-run: an invoiced job is skipped, under a lock per job. Nothing is sent: then POST /invoice-batches/send. Emits invoice.created per invoice and invoice_batch.completed.
Your key for this batch (e.g. a UUID). Running a batch again with the same key never invoices a job twice nor sends a message twice; omit it and one is made. Up to 100 characters.
POST /organizations/{organizationId}/invoice-batches/send
Customer-facing: emails or texts each invoice to its billing contact with the business's invoice template and link, exactly as the Send dialog would (POST /messages/send); a draft is issued (SENT). channel EMAIL, SMS, or EMAIL_OR_SMS (email, else text). Consent applies: no text without OK-to-text or after STOP, opted-out emails are skipped, quiet hours hold texts. One row per invoice: SENT, QUEUED (held for quiet hours) or SKIPPED/FAILED with the reason (paid, cancelled, no address, no consent). Re-running with the same batchKey never sends twice. In test mode messages are logged, not sent.
Permission
messages.send (Owner, Admin, Dispatcher)
Risk
sensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP tool
invoices_batchSend (toolset invoices)
Approval
Over MCP, this tool always waits for your approval in ServFlo. See Approvals.
Invoice ids from GET /invoices, at most 200. Up to 200 items.
channel
enum
No
One of EMAIL, SMS, EMAIL_OR_SMS. Default EMAIL.
batchKey
string
No
Your key for this batch (e.g. a UUID). Running a batch again with the same key never invoices a job twice nor sends a message twice; omit it and one is made. Up to 100 characters.
POST /organizations/{organizationId}/invoice-batches/remind
Customer-facing: a payment reminder (the business's overdue template) for each SENT or OVERDUE invoice with a balance due; drafts, paid and cancelled invoices are skipped with the reason. Same channels, consent rules, per-row answer and batchKey replay safety as POST /invoice-batches/send.
Permission
messages.send (Owner, Admin, Dispatcher)
Risk
sensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP tool
invoices_batchRemind (toolset invoices)
Approval
Over MCP, this tool always waits for your approval in ServFlo. See Approvals.
Invoice ids from GET /invoices, at most 200. Up to 200 items.
channel
enum
No
One of EMAIL, SMS, EMAIL_OR_SMS. Default EMAIL.
batchKey
string
No
Your key for this batch (e.g. a UUID). Running a batch again with the same key never invoices a job twice nor sends a message twice; omit it and one is made. Up to 100 characters.
POST /organizations/{organizationId}/invoice-batches/mark-sent
Issues each DRAFT invoice (status SENT, invoice.sent emitted) without contacting the customer, e.g. after mailing or handing them over. Invoices already issued are skipped. One row per invoice.
Invoice ids from GET /invoices, at most 200. Up to 200 items.
batchKey
string
No
Your key for this batch (e.g. a UUID). Running a batch again with the same key never invoices a job twice nor sends a message twice; omit it and one is made. Up to 100 characters.
GET /organizations/{organizationId}/progress-billings
Quotes and jobs billed in progress invoices, newest first, with contract, billed to date, remaining and retainage. Filter by customer, quote, job or status (ACTIVE / COMPLETED). Read-only.
POST /organizations/{organizationId}/progress-billings
Freezes the contract from an APPROVED (or converted) quote not yet invoiced (its chosen lines and tax) or from a job not yet invoiced (service and materials, member-priced; a job sold through a quote uses the quote). method MILESTONES (milestones adding to 100%) or PERCENT_COMPLETE (bill each line as it progresses). retainagePercent (0–50) is held back on each invoice until the retainage release. Afterwards the quote/job can no longer be invoiced in one go (409): bill it through POST /progress-billings/{id}/invoices. Deposits on the quote move onto the first invoice. Creates no invoice. Emits progress_billing.created.
GET /organizations/{organizationId}/progress-billings/{progressBillingId}
The contract (lines, milestones, retainage), its invoices and the summary: billed to date, remaining, retainage held and released, and whether a final or retainage-release invoice can be made (with the reason when not). Read-only.
The title any time; the method, retainage and milestones only while nothing has been invoiced (409 otherwise; cancelled invoices do not count). The contract itself never changes.
Destructive: removes a progress billing that has no live invoices (409 otherwise: cancel them first), so the quote or job can be invoiced normally again.
POST /organizations/{organizationId}/progress-billings/{progressBillingId}/invoices
Creates a DRAFT progress invoice for part of the contract. Give exactly one of: milestoneId (MILESTONES: that milestone's share; 409 when already invoiced), percentOfContract or amountCents (MILESTONES: an extra partial invoice), or lines (PERCENT_COMPLETE: each line's new cumulative percent complete; the difference is billed; percents cannot go down). Never bills beyond the contract (409 with what is left). Retainage at the plan's percent is held back as a negative line; tax at the contract's rate. The first invoice receives the quote's deposits (it must be at least that much). Nothing is sent. Emits invoice.created and progress_billing.invoiced.
POST /organizations/{organizationId}/progress-billings/{progressBillingId}/final-invoice
Creates the DRAFT final invoice: every contract line in full, less a credit line per earlier progress invoice, so it bills exactly what is left. Retainage is held on that remainder, unless releaseRetainage: true releases every earlier retainage on it instead. 409 when nothing is left or a final exists. Emits progress_billing.invoiced (and .completed when nothing remains).
POST /organizations/{organizationId}/progress-billings/{progressBillingId}/retainage-release
Creates a DRAFT retainage-release invoice for all retainage held and not yet released. Only once the whole contract is billed (after the final invoice; 409 otherwise). Emits progress_billing.invoiced and progress_billing.completed.
POST /organizations/{organizationId}/invoices/{invoiceId}/link
Exposes to the customer: returns the token for /i/{token}, the customer's page for this one invoice (no sign-in; anyone with the link can read it). Made on first call and then reused, so calling it again is safe. Drafts and cancelled invoices are not shown on that page until issued. Nothing is emailed.