DevelopersAPI reference

Expenses API

REST endpoints for expenses in ServFlo: 9 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.

OperationEndpointRisk
List expensesGET /organizations/{organizationId}/expensesread
Record an expensePOST /organizations/{organizationId}/expenseswrite
Get an expenseGET /organizations/{organizationId}/expenses/{expenseId}read
Update an expensePATCH /organizations/{organizationId}/expenses/{expenseId}write
Delete an expenseDELETE /organizations/{organizationId}/expenses/{expenseId}sensitive
Start a receipt uploadPOST /organizations/{organizationId}/expenses/{expenseId}/receiptwrite
Confirm a receipt uploadPOST /organizations/{organizationId}/expenses/{expenseId}/receipt/{attachmentId}/confirmwrite
Remove a receiptDELETE /organizations/{organizationId}/expenses/{expenseId}/receipt/{attachmentId}sensitive
Attach a receipt (content in the request)POST /organizations/{organizationId}/expenses/{expenseId}/receipt/contentwrite

List expenses

GET /organizations/{organizationId}/expenses

Expenses newest first, filtered by job, vendor, category, date range (from/to, YYYY-MM-DD) or text, with totalAmountCents for the filter and suggested categories. Paged with a total. Read-only.

Permission: expenses.read (Owner, Admin, Dispatcher, Technician (own jobs)); risk read (OAuth scope servflo.read).

Permissionexpenses.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolexpenses_list (toolset pricebook)

Parameters

NameInTypeRequiredDescription
jobIdquerystringNo
vendorIdquerystringNo
categoryquerystringNoUp to 100 characters.
fromquerystringNoA calendar date in the business's own calendar, YYYY-MM-DD.
toquerystringNoA calendar date in the business's own calendar, YYYY-MM-DD.
queryquerystringNoFree-text search over the list's names, numbers and contact details. Up to 200 characters.
limitqueryintegerNoPage size, 1–200. Default 50.
offsetqueryintegerNoRows to skip; the answer's total counts every match. Default 0.

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].idstring
items[].organizationIdstring
items[].datestring
items[].amountCentsintegerInteger cents.
items[].categorystring
items[].vendorIdstring or null
items[].descriptionstring or null
items[].jobIdstring or null
items[].recordedByUserIdstring or null
items[].reimbursablebooleanPaid back to the recorder through payroll.
items[].createdAtstring (date-time)
items[].updatedAtstring (date-time)
items[].vendorobject or null
items[].vendor.idstring
items[].vendor.namestring
items[].jobobject or null
items[].job.idstring
items[].job.identifierstring
items[].job.customerobject
items[].receiptobject or nullThe receipt, with a 10-minute signed URL.
items[].receipt.idstring
items[].receipt.fileNamestring
items[].receipt.contentTypestring
items[].receipt.urlstring
totalinteger
totalAmountCentsintegerInteger cents.
categoriesarray of string

Record an expense

POST /organizations/{organizationId}/expenses

Records money spent, optionally on a job (it counts in the job's costing) and with a vendor. Attach the receipt with POST /expenses/{expenseId}/receipt. Returns it.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionexpenses.write
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolexpenses_create (toolset pricebook)

Request body

FieldTypeRequiredDescription
datestringYesA calendar date in the business's own calendar, YYYY-MM-DD.
amountCentsintegerYesInteger cents; negative for a refund or credit. -100000000–100000000.
categorystringYesFuel, Permits, Materials, Subcontractor… (suggestions in GET /expenses categories). Up to 100 characters.
vendorIdstring or nullNoFrom GET /vendors.
descriptionstring or nullNoUp to 5000 characters.
jobIdstring or nullNoThe job it was spent on: it then counts in the job's costing.
reimbursablebooleanNoPaid out of the recorder's own pocket: the next payroll run pays it back to them. Defaults to true when a technician records it, else false.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-11-03","amountCents":12500,"category":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
datestring
amountCentsintegerInteger cents.
categorystring
vendorIdstring or null
descriptionstring or null
jobIdstring or null
recordedByUserIdstring or null
reimbursablebooleanPaid back to the recorder through payroll.
createdAtstring (date-time)
updatedAtstring (date-time)
vendorobject or null
vendor.idstring
vendor.namestring
jobobject or null
job.idstring
job.identifierstring
job.customerobject
job.customer.idstring
job.customer.namestring
receiptobject or nullThe receipt, with a 10-minute signed URL.
receipt.idstring
receipt.fileNamestring
receipt.contentTypestring
receipt.urlstring

Get an expense

GET /organizations/{organizationId}/expenses/{expenseId}

One expense with its vendor, job and receipt. Read-only.

Permission: expenses.read (Owner, Admin, Dispatcher, Technician (own jobs)); risk read (OAuth scope servflo.read).

Permissionexpenses.read
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolexpenses_get (toolset pricebook)

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes

Example

curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
datestring
amountCentsintegerInteger cents.
categorystring
vendorIdstring or null
descriptionstring or null
jobIdstring or null
recordedByUserIdstring or null
reimbursablebooleanPaid back to the recorder through payroll.
createdAtstring (date-time)
updatedAtstring (date-time)
vendorobject or null
vendor.idstring
vendor.namestring
jobobject or null
job.idstring
job.identifierstring
job.customerobject
job.customer.idstring
job.customer.namestring
receiptobject or nullThe receipt, with a 10-minute signed URL.
receipt.idstring
receipt.fileNamestring
receipt.contentTypestring
receipt.urlstring

Update an expense

PATCH /organizations/{organizationId}/expenses/{expenseId}

Changes the fields sent (moving it to another job moves its cost there). Returns it.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionexpenses.write
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolexpenses_update (toolset pricebook)

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes

Request body

FieldTypeRequiredDescription
datestringNoA calendar date in the business's own calendar, YYYY-MM-DD.
amountCentsintegerNoInteger cents; negative for a refund or credit. -100000000–100000000.
categorystringNoFuel, Permits, Materials, Subcontractor… (suggestions in GET /expenses categories). Up to 100 characters.
vendorIdstring or nullNoFrom GET /vendors.
descriptionstring or nullNoUp to 5000 characters.
jobIdstring or nullNoThe job it was spent on: it then counts in the job's costing.
reimbursablebooleanNoPaid out of the recorder's own pocket: the next payroll run pays it back to them. Defaults to true when a technician records it, else false.

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-11-03","amountCents":12500,"category":"string"}'

Response

200: the answer's fields
FieldTypeDescription
idstring
organizationIdstring
datestring
amountCentsintegerInteger cents.
categorystring
vendorIdstring or null
descriptionstring or null
jobIdstring or null
recordedByUserIdstring or null
reimbursablebooleanPaid back to the recorder through payroll.
createdAtstring (date-time)
updatedAtstring (date-time)
vendorobject or null
vendor.idstring
vendor.namestring
jobobject or null
job.idstring
job.identifierstring
job.customerobject
job.customer.idstring
job.customer.namestring
receiptobject or nullThe receipt, with a 10-minute signed URL.
receipt.idstring
receipt.fileNamestring
receipt.contentTypestring
receipt.urlstring

Delete an expense

DELETE /organizations/{organizationId}/expenses/{expenseId}

Destructive: deletes the expense and its receipt; the job's costing drops it.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk sensitive (OAuth scope servflo.sensitive).

Permissionexpenses.write
Risksensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolexpenses_delete (toolset pricebook)
ApprovalOver MCP, this tool always waits for your approval in ServFlo. See Approvals.

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes

Example

curl -X DELETE "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
oktrue

Start a receipt upload

POST /organizations/{organizationId}/expenses/{expenseId}/receipt

Step 1 of 2: returns uploadUrl; PUT the image or PDF there within 5 minutes, then POST …/receipt/{attachmentId}/confirm. Up to 25 MB.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionexpenses.write
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolexpenses_uploadReceipt (toolset pricebook)

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes

Request body

FieldTypeRequiredDescription
fileNamestringYesUp to 500 characters.
contentTypestringYesimage/* or application/pdf. Up to 200 characters.
sizeBytesintegerYes

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}/receipt" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"string","contentType":"string","sizeBytes":1}'

Response

201: the answer's fields
FieldTypeDescription
attachmentobject
attachment.idstring
attachment.fileNamestring
attachment.contentTypestring
attachment.sizeBytesinteger
attachment.uploadedAtstring (date-time) or null
attachment.createdAtstring (date-time)
uploadUrlstring

Confirm a receipt upload

POST /organizations/{organizationId}/expenses/{expenseId}/receipt/{attachmentId}/confirm

Step 2 of 2: the uploaded file is the expense's receipt. Returns it with a 10-minute URL.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionexpenses.write
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolexpenses_confirmReceipt (toolset pricebook)

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes
attachmentIdpathstringYes

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}/receipt/{attachmentId}/confirm" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
idstring
fileNamestring
contentTypestring
sizeBytesinteger
uploadedAtstring (date-time) or null
createdAtstring (date-time)
urlstring

Remove a receipt

DELETE /organizations/{organizationId}/expenses/{expenseId}/receipt/{attachmentId}

Destructive: deletes the receipt file from the expense.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk sensitive (OAuth scope servflo.sensitive).

Permissionexpenses.write
Risksensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolexpenses_deleteReceipt (toolset pricebook)
ApprovalOver MCP, this tool always waits for your approval in ServFlo. See Approvals.

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes
attachmentIdpathstringYes

Example

curl -X DELETE "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}/receipt/{attachmentId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
oktrue

Attach a receipt (content in the request)

POST /organizations/{organizationId}/expenses/{expenseId}/receipt/content

Attaches a receipt image or PDF to an expense in one call: send contentBase64 or a public sourceUrl (up to 10 MB). Returns the receipt with a 10-minute URL.

Permission: expenses.write (Owner, Admin, Dispatcher, Technician (own jobs)); risk write (OAuth scope servflo.write).

Permissionexpenses.write
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolexpenses_attachReceipt (toolset pricebook)

Parameters

NameInTypeRequiredDescription
expenseIdpathstringYes

Request body

FieldTypeRequiredDescription
fileNamestringNoThe file's name, e.g. receipt.jpg (default: from the URL, else 'file'). Up to 500 characters.
contentTypestringNoMIME type, e.g. image/jpeg or application/pdf. Optional: taken from a data: URL, the URL's answer or the file's first bytes. Up to 200 characters.
contentBase64stringNoThe file's bytes as base64 (a data: URL is accepted too). Up to 10 MB. Give this or sourceUrl.
sourceUrlstring (uri)NoA public http(s) URL ServFlo downloads the file from (up to 10 MB, 20 s, 3 redirects; private and internal addresses are refused). Give this or contentBase64. Up to 2000 characters.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/expenses/{expenseId}/receipt/content" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"string","contentType":"string","contentBase64":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstringThe attachment id.
fileNamestring
contentTypestring
sizeBytesinteger
uploadedAtstring (date-time) or null
createdAtstring (date-time)
urlstringView URL, valid for 10 minutes.

On this page