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 .
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).
Permission expenses.readRisk read, OAuth scope servflo.read. Read: reads only.MCP tool expenses_list (toolset pricebook)
Name In Type Required Description jobIdquery string No vendorIdquery string No categoryquery string No Up to 100 characters. fromquery string No A calendar date in the business's own calendar, YYYY-MM-DD. toquery string No A calendar date in the business's own calendar, YYYY-MM-DD. queryquery string No Free-text search over the list's names, numbers and contact details. Up to 200 characters. limitquery integer No Page size, 1–200. Default 50. offsetquery integer No Rows to skip; the answer's total counts every match. Default 0.
curl "https://app.servflo.ai/api/organizations/ $ORGANIZATION_ID /expenses" \
-H "Authorization: Bearer $SERVFLO_API_KEY "
200: the answer's fields Field Type Description itemsarray of object items[].idstring items[].organizationIdstring items[].datestring items[].amountCentsinteger Integer cents. items[].categorystring items[].vendorIdstring or null items[].descriptionstring or null items[].jobIdstring or null items[].recordedByUserIdstring or null items[].reimbursableboolean Paid 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 null The receipt, with a 10-minute signed URL. items[].receipt.idstring items[].receipt.fileNamestring items[].receipt.contentTypestring items[].receipt.urlstring totalinteger totalAmountCentsinteger Integer cents. categoriesarray of string
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).
Permission expenses.writeRisk write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.MCP tool expenses_create (toolset pricebook)
Field Type Required Description datestring Yes A calendar date in the business's own calendar, YYYY-MM-DD. amountCentsinteger Yes Integer cents; negative for a refund or credit. -100000000–100000000. categorystring Yes Fuel, Permits, Materials, Subcontractor… (suggestions in GET /expenses categories). Up to 100 characters. vendorIdstring or null No From GET /vendors. descriptionstring or null No Up to 5000 characters. jobIdstring or null No The job it was spent on: it then counts in the job's costing. reimbursableboolean No Paid 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.
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"}'
201: the answer's fields Field Type Description idstring organizationIdstring datestring amountCentsinteger Integer cents. categorystring vendorIdstring or null descriptionstring or null jobIdstring or null recordedByUserIdstring or null reimbursableboolean Paid 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 null The receipt, with a 10-minute signed URL. receipt.idstring receipt.fileNamestring receipt.contentTypestring receipt.urlstring
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).
Permission expenses.readRisk read, OAuth scope servflo.read. Read: reads only.MCP tool expenses_get (toolset pricebook)
Name In Type Required Description expenseIdpath string Yes
curl "https://app.servflo.ai/api/organizations/ $ORGANIZATION_ID /expenses/{expenseId}" \
-H "Authorization: Bearer $SERVFLO_API_KEY "
200: the answer's fields Field Type Description idstring organizationIdstring datestring amountCentsinteger Integer cents. categorystring vendorIdstring or null descriptionstring or null jobIdstring or null recordedByUserIdstring or null reimbursableboolean Paid 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 null The receipt, with a 10-minute signed URL. receipt.idstring receipt.fileNamestring receipt.contentTypestring receipt.urlstring
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).
Permission expenses.writeRisk write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.MCP tool expenses_update (toolset pricebook)
Name In Type Required Description expenseIdpath string Yes
Field Type Required Description datestring No A calendar date in the business's own calendar, YYYY-MM-DD. amountCentsinteger No Integer cents; negative for a refund or credit. -100000000–100000000. categorystring No Fuel, Permits, Materials, Subcontractor… (suggestions in GET /expenses categories). Up to 100 characters. vendorIdstring or null No From GET /vendors. descriptionstring or null No Up to 5000 characters. jobIdstring or null No The job it was spent on: it then counts in the job's costing. reimbursableboolean No Paid 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.
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"}'
200: the answer's fields Field Type Description idstring organizationIdstring datestring amountCentsinteger Integer cents. categorystring vendorIdstring or null descriptionstring or null jobIdstring or null recordedByUserIdstring or null reimbursableboolean Paid 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 null The receipt, with a 10-minute signed URL. receipt.idstring receipt.fileNamestring receipt.contentTypestring receipt.urlstring
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).
Permission expenses.writeRisk sensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.MCP tool expenses_delete (toolset pricebook)Approval Over MCP, this tool always waits for your approval in ServFlo. See Approvals .
Name In Type Required Description expenseIdpath string Yes
curl -X DELETE "https://app.servflo.ai/api/organizations/ $ORGANIZATION_ID /expenses/{expenseId}" \
-H "Authorization: Bearer $SERVFLO_API_KEY "
200: the answer's fields Field Type Description oktrue
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).
Permission expenses.writeRisk write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.MCP tool expenses_uploadReceipt (toolset pricebook)
Name In Type Required Description expenseIdpath string Yes
Field Type Required Description fileNamestring Yes Up to 500 characters. contentTypestring Yes image/* or application/pdf. Up to 200 characters. sizeBytesinteger Yes
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}'
201: the answer's fields Field Type Description attachmentobject attachment.idstring attachment.fileNamestring attachment.contentTypestring attachment.sizeBytesinteger attachment.uploadedAtstring (date-time) or null attachment.createdAtstring (date-time) uploadUrlstring
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).
Permission expenses.writeRisk write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.MCP tool expenses_confirmReceipt (toolset pricebook)
Name In Type Required Description expenseIdpath string Yes attachmentIdpath string Yes
curl -X POST "https://app.servflo.ai/api/organizations/ $ORGANIZATION_ID /expenses/{expenseId}/receipt/{attachmentId}/confirm" \
-H "Authorization: Bearer $SERVFLO_API_KEY "
200: the answer's fields Field Type Description idstring fileNamestring contentTypestring sizeBytesinteger uploadedAtstring (date-time) or null createdAtstring (date-time) urlstring
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).
Permission expenses.writeRisk sensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.MCP tool expenses_deleteReceipt (toolset pricebook)Approval Over MCP, this tool always waits for your approval in ServFlo. See Approvals .
Name In Type Required Description expenseIdpath string Yes attachmentIdpath string Yes
curl -X DELETE "https://app.servflo.ai/api/organizations/ $ORGANIZATION_ID /expenses/{expenseId}/receipt/{attachmentId}" \
-H "Authorization: Bearer $SERVFLO_API_KEY "
200: the answer's fields Field Type Description oktrue
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).
Permission expenses.writeRisk write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.MCP tool expenses_attachReceipt (toolset pricebook)
Name In Type Required Description expenseIdpath string Yes
Field Type Required Description fileNamestring No The file's name, e.g. receipt.jpg (default: from the URL, else 'file'). Up to 500 characters. contentTypestring No MIME 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. contentBase64string No The file's bytes as base64 (a data: URL is accepted too). Up to 10 MB. Give this or sourceUrl. sourceUrlstring (uri) No A 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.
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"}'
201: the answer's fields Field Type Description idstring The attachment id. fileNamestring contentTypestring sizeBytesinteger uploadedAtstring (date-time) or null createdAtstring (date-time) urlstring View URL, valid for 10 minutes.