DevelopersAPI reference

Customers API

REST endpoints for customers in ServFlo: 19 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 customersGET /organizations/{organizationId}/customersread
Create a customerPOST /organizations/{organizationId}/customerswrite
All customers that are not archived, for pickersGET /organizations/{organizationId}/customers/optionsread
Get a customerGET /organizations/{organizationId}/customers/{customerId}read
Update a customerPATCH /organizations/{organizationId}/customers/{customerId}write
Delete a customerDELETE /organizations/{organizationId}/customers/{customerId}sensitive
List a customer's contactsGET /organizations/{organizationId}/customers/{customerId}/contactsread
Add a contactPOST /organizations/{organizationId}/customers/{customerId}/contactswrite
Update a contactPATCH /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}write
Delete a contactDELETE /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}sensitive
List notes on a customer or propertyGET /organizations/{organizationId}/{ownerType}/{ownerId}/notesread
Add a notePOST /organizations/{organizationId}/{ownerType}/{ownerId}/noteswrite
Edit or pin a notePATCH /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}write
Delete a noteDELETE /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}sensitive
List files on a customer or propertyGET /organizations/{organizationId}/{ownerType}/{ownerId}/attachmentsread
Start a file uploadPOST /organizations/{organizationId}/{ownerType}/{ownerId}/attachmentswrite
Confirm a finished uploadPOST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}/confirmwrite
Delete a fileDELETE /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}sensitive
Attach a file (content in the request)POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/contentwrite

List customers

GET /organizations/{organizationId}/customers

Customers by name, filtered by text (name, company, email, phone), status, type, tag or lead source; paged with a total. Archived customers are left out unless status=ARCHIVED. A technician sees only customers they have a job for. Read-only. Use GET /customers/options for a picker list.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolcustomers_list (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
queryquerystringNoFree-text search over the list's names, numbers and contact details. Up to 200 characters.
statusqueryenumNoOne of LEAD, ACTIVE, INACTIVE, ARCHIVED.
typequeryenumNoOne of RESIDENTIAL, COMMERCIAL.
tagquerystringNoUp to 200 characters.
leadSourceIdquerystringNo
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/customers" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
itemsarray of object
items[].idstringCustomer id.
items[].organizationIdstring
items[].typeenumOne of RESIDENTIAL, COMMERCIAL.
items[].titlestring or nullSalutation as typed or imported (Mr., Mrs., Ms., Miss, Dr., Mx.); null = none. The AI receptionist's title + last name greeting uses it, never a guess.
items[].firstNamestring or null
items[].lastNamestring or null
items[].companyNamestring or null
items[].namestringDisplay name, derived from the person or company name.
items[].statusenumLEAD until the first job, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED.
items[].emailstring or nullThe primary contact's main email.
items[].phonestring or nullThe primary contact's main phone.
items[].addressstring or nullBilling address (street).
items[].address2string or null
items[].citystring or null
items[].statestring or null
items[].zipstring or null
items[].countrystring or null
items[].leadSourceIdstring or nullFrom GET /lead-sources.
items[].tagsarray of string
items[].paymentTermsenumInvoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
items[].paymentTermsDaysinteger or nullDays to pay, for CUSTOM terms.
items[].taxExemptbooleanInvoices and quotes carry no tax.
items[].taxExemptIdstring or null
items[].creditLimitCentsinteger or nullCredit limit in integer cents; null = none.
items[].doNotServicebooleanBooking screens warn before scheduling work.
items[].doNotServiceReasonstring or null
items[].preferredTechnicianIdstring or null
items[].allowEmailboolean
items[].allowSmsboolean
items[].allowMarketingboolean
items[].notesstring or null
items[].customFieldsanyCustom field values by key (definitions: GET /custom-fields).
items[].createdAtstring (date-time)
items[].updatedAtstring (date-time)
items[].contactsarray of objectContacts, primary first.
items[].contacts[].idstringContact id.
items[].contacts[].customerIdstring
items[].contacts[].firstNamestring or null
items[].contacts[].lastNamestring or null
items[].contacts[].jobTitlestring or null
items[].contacts[].rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
items[].contacts[].isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
items[].contacts[].notesstring or null
items[].contacts[].createdAtstring (date-time)
items[].contacts[].updatedAtstring (date-time)
items[].contacts[].methodsarray of objectPhones and emails, primary first.
items[].leadSourceobject or null
items[].leadSource.idstring
items[].leadSource.namestring
items[].balanceCentsintegerOpen balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents.
items[].lifetimeValueCentsintegerEvery payment collected, net of refunds, tips excluded, in integer cents.
items[].overCreditLimitbooleanThe balance is above creditLimitCents.
items[].jobCountinteger
totalintegerHow many match the filters, across all pages.

Create a customer

POST /organizations/{organizationId}/customers

Creates a customer, optionally with contacts (else one primary contact from the names, email and phone) and a service property the customer owns. Custom field values are validated against GET /custom-fields. Search GET /customers first to avoid duplicates. Nothing is sent to the customer. Returns the customer.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolcustomers_create (toolset customers, in the default set)

Request body

FieldTypeRequiredDescription
typeenumNoOne of RESIDENTIAL, COMMERCIAL.
titlestring or nullNoSalutation: Mr., Mrs., Ms., Miss, Dr. or Mx. (free text is kept; null or blank = none). Part of the display name, and what the AI receptionist says with the last name when its greeting is TITLE_LAST_NAME. Only set it from what the customer or office said: never infer it from a first name.
firstNamestring or nullNo
lastNamestring or nullNo
companyNamestring or nullNo
namestringNo
statusenumNoOne of LEAD, ACTIVE, INACTIVE, ARCHIVED.
emailempty string or string (email) or nullNo
phonestring or nullNo
addressstring or nullNo
address2string or nullNo
citystring or nullNo
statestring or nullNo
zipstring or nullNo
countrystring or nullNo
leadSourceIdstring or nullNo
tagsarray of stringNoUp to 100 items.
paymentTermsenumNoOne of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or nullNo0–3650.
taxExemptbooleanNo
taxExemptIdstring or nullNo
creditLimitCentsinteger or nullNo0–1000000000.
doNotServicebooleanNo
doNotServiceReasonstring or nullNo
preferredTechnicianIdstring or nullNo
allowEmailbooleanNo
allowSmsbooleanNo
allowMarketingbooleanNo
notesstring or nullNo
customFieldsobjectNo
contactsarray of objectNoUp to 50 items.
contacts[].firstNamestring or nullNo
contacts[].lastNamestring or nullNo
contacts[].jobTitlestring or nullNo
contacts[].rolesarray of enumNoUp to 7 items.
contacts[].isPrimarybooleanNo
contacts[].notesstring or nullNo
contacts[].methodsarray of objectNoUp to 50 items.
contacts[].methods[].kindenumYesOne of PHONE, EMAIL.
contacts[].methods[].labelenumNoOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
contacts[].methods[].valuestringYesUp to 500 characters.
contacts[].methods[].isPrimarybooleanNo
contacts[].methods[].smsConsentenumNoOne of UNKNOWN, GRANTED, DENIED.
propertyobject or nullNo
property.namestring or nullNoUp to 500 characters.
property.addressstring or nullNoUp to 500 characters.
property.address2string or nullNoUp to 500 characters.
property.citystring or nullNoUp to 500 characters.
property.statestring or nullNoUp to 500 characters.
property.zipstring or nullNoUp to 500 characters.
property.countrystring or nullNoUp to 500 characters.
property.propertyTypeenumNoOne of RESIDENTIAL, COMMERCIAL, MULTI_FAMILY, INDUSTRIAL.
property.contactNamestring or nullNoUp to 500 characters.
property.contactPhonestring or nullNoUp to 500 characters.
property.contactEmailempty string or string (email) or nullNo
property.timezonestringNoAn IANA timezone, e.g. America/Chicago. Up to 100 characters.
property.accessInstructionsstring or nullNoUp to 5000 characters.
property.safetyNotesstring or nullNoUp to 5000 characters.
property.gateCodestring or nullNoUp to 500 characters.
property.petsOnSitebooleanNo
property.petNotesstring or nullNoUp to 5000 characters.
property.yearBuiltinteger or nullNo1000–3000.
property.squareFeetinteger or nullNo0–100000000.
property.tagsarray of stringNoUp to 100 items.
property.taxRatenumber or nullNo-1000–1000.
property.latitudenumber or nullNoGeocode (WGS84), pinned by hand. Leave out to have the address geocoded after saving; the map, route planning and My day's distances use it. -90–90.
property.longitudenumber or nullNo-180–180.
property.customFieldsobjectNo
property.roleenumNoOne of OWNER, TENANT, LANDLORD, PROPERTY_MANAGER.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/customers" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"RESIDENTIAL","title":"string","firstName":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstringCustomer id.
organizationIdstring
typeenumOne of RESIDENTIAL, COMMERCIAL.
titlestring or nullSalutation as typed or imported (Mr., Mrs., Ms., Miss, Dr., Mx.); null = none. The AI receptionist's title + last name greeting uses it, never a guess.
firstNamestring or null
lastNamestring or null
companyNamestring or null
namestringDisplay name, derived from the person or company name.
statusenumLEAD until the first job, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED.
emailstring or nullThe primary contact's main email.
phonestring or nullThe primary contact's main phone.
addressstring or nullBilling address (street).
address2string or null
citystring or null
statestring or null
zipstring or null
countrystring or null
leadSourceIdstring or nullFrom GET /lead-sources.
tagsarray of string
paymentTermsenumInvoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or nullDays to pay, for CUSTOM terms.
taxExemptbooleanInvoices and quotes carry no tax.
taxExemptIdstring or null
creditLimitCentsinteger or nullCredit limit in integer cents; null = none.
doNotServicebooleanBooking screens warn before scheduling work.
doNotServiceReasonstring or null
preferredTechnicianIdstring or null
allowEmailboolean
allowSmsboolean
allowMarketingboolean
notesstring or null
customFieldsanyCustom field values by key (definitions: GET /custom-fields).
createdAtstring (date-time)
updatedAtstring (date-time)
contactsarray of object
contacts[].idstringContact id.
contacts[].customerIdstring
contacts[].firstNamestring or null
contacts[].lastNamestring or null
contacts[].jobTitlestring or null
contacts[].rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
contacts[].isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
contacts[].notesstring or null
contacts[].createdAtstring (date-time)
contacts[].updatedAtstring (date-time)
contacts[].methodsarray of objectPhones and emails, primary first.
contacts[].methods[].idstring
contacts[].methods[].contactIdstring
contacts[].methods[].kindenumOne of PHONE, EMAIL.
contacts[].methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
contacts[].methods[].valuestringThe phone number or email address.
contacts[].methods[].isPrimarybooleanThe contact's main phone or email.
contacts[].methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
contacts[].methods[].sortOrderinteger

All customers that are not archived, for pickers

GET /organizations/{organizationId}/customers/options

Every customer that is not archived, as id and display name only, for pickers and to find a customer's id by name. Read-only.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolcustomers_options (toolset customers)

Example

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

Response

200: the answer's fields

An array of objects:

FieldTypeDescription
idstringCustomer id.
namestring
addressstring or null
citystring or null
doNotServiceboolean
doNotServiceReasonstring or null
preferredTechnicianIdstring or null
creditLimitCentsinteger or null
taxExemptboolean
paymentTermsenumInvoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or null
balanceCentsintegerOpen balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents.
lifetimeValueCentsintegerEvery payment collected, net of refunds, tips excluded, in integer cents.
overCreditLimitbooleanThe balance is above creditLimitCents.

Get a customer

GET /organizations/{organizationId}/customers/{customerId}

One customer with contacts, current and past properties, the 50 most recent jobs (jobCount for all), open balance (balanceCents, negative = in credit) and lifetime value. A technician gets 404 unless they have a job for the customer, and sees only their own jobs in it. Read-only.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolcustomers_get (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes

Example

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

Response

200: the answer's fields
FieldTypeDescription
idstringCustomer id.
organizationIdstring
typeenumOne of RESIDENTIAL, COMMERCIAL.
titlestring or nullSalutation as typed or imported (Mr., Mrs., Ms., Miss, Dr., Mx.); null = none. The AI receptionist's title + last name greeting uses it, never a guess.
firstNamestring or null
lastNamestring or null
companyNamestring or null
namestringDisplay name, derived from the person or company name.
statusenumLEAD until the first job, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED.
emailstring or nullThe primary contact's main email.
phonestring or nullThe primary contact's main phone.
addressstring or nullBilling address (street).
address2string or null
citystring or null
statestring or null
zipstring or null
countrystring or null
leadSourceIdstring or nullFrom GET /lead-sources.
tagsarray of string
paymentTermsenumInvoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or nullDays to pay, for CUSTOM terms.
taxExemptbooleanInvoices and quotes carry no tax.
taxExemptIdstring or null
creditLimitCentsinteger or nullCredit limit in integer cents; null = none.
doNotServicebooleanBooking screens warn before scheduling work.
doNotServiceReasonstring or null
preferredTechnicianIdstring or null
allowEmailboolean
allowSmsboolean
allowMarketingboolean
notesstring or null
customFieldsanyCustom field values by key (definitions: GET /custom-fields).
createdAtstring (date-time)
updatedAtstring (date-time)
contactsarray of objectContacts, primary first.
contacts[].idstringContact id.
contacts[].customerIdstring
contacts[].firstNamestring or null
contacts[].lastNamestring or null
contacts[].jobTitlestring or null
contacts[].rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
contacts[].isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
contacts[].notesstring or null
contacts[].createdAtstring (date-time)
contacts[].updatedAtstring (date-time)
contacts[].methodsarray of objectPhones and emails, primary first.
contacts[].methods[].idstring
contacts[].methods[].contactIdstring
contacts[].methods[].kindenumOne of PHONE, EMAIL.
contacts[].methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
contacts[].methods[].valuestringThe phone number or email address.
contacts[].methods[].isPrimarybooleanThe contact's main phone or email.
contacts[].methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
contacts[].methods[].sortOrderinteger
leadSourceobject or null
leadSource.idstring
leadSource.namestring
preferredTechnicianobject or null
preferredTechnician.idstringTechnician id (GET /technicians).
preferredTechnician.namestring
preferredTechnician.colorstringCalendar colour, #rrggbb.
occupanciesarray of objectCurrent and past properties (current first), with the property.
occupancies[].idstringOccupancy id (for PATCH …/occupancies/{occupancyId}).
occupancies[].propertyIdstring
occupancies[].customerIdstring
occupancies[].roleenumOne of OWNER, TENANT, LANDLORD, PROPERTY_MANAGER.
occupancies[].isPrimarybooleanThe property's primary current occupant.
occupancies[].startedOnstringFirst day of the link, YYYY-MM-DD.
occupancies[].endedOnstring or nullLast day, YYYY-MM-DD; null = current.
occupancies[].createdAtstring (date-time)
occupancies[].propertyobject
occupancies[].property.idstringProperty id.
occupancies[].property.organizationIdstring
occupancies[].property.namestring or null
occupancies[].property.addressstring or null
occupancies[].property.address2string or null
occupancies[].property.citystring or null
occupancies[].property.statestring or null
occupancies[].property.zipstring or null
occupancies[].property.countrystring or null
occupancies[].property.propertyTypeenumOne of RESIDENTIAL, COMMERCIAL, MULTI_FAMILY, INDUSTRIAL.
occupancies[].property.contactNamestring or nullWho to ask for on site.
occupancies[].property.contactPhonestring or null
occupancies[].property.contactEmailstring or null
occupancies[].property.timezonestringIANA timezone of the property.
occupancies[].property.accessInstructionsstring or null
occupancies[].property.safetyNotesstring or null
occupancies[].property.gateCodestring or null
occupancies[].property.petsOnSiteboolean
occupancies[].property.petNotesstring or null
occupancies[].property.yearBuiltinteger or null
occupancies[].property.squareFeetinteger or null
occupancies[].property.tagsarray of string
occupancies[].property.latitudenumber or null
occupancies[].property.longitudenumber or null
occupancies[].property.geocodeSourcestring or nullWhere the coordinates came from: GEOCODER (the address), GEOCODER_STREET / GEOCODER_AREA (approximate: only its street or town is on the map; visit safety then searches the ZIP), MANUAL (pinned by hand).
occupancies[].property.geocodedAtstring (date-time) or null
occupancies[].property.taxRatenumber or nullTax rate in percent for work here; null = the business default.
occupancies[].property.customFieldsanyCustom field values by key (definitions: GET /custom-fields).
occupancies[].property.createdAtstring (date-time)
occupancies[].property.updatedAtstring (date-time)
jobsarray of objectThe 50 most recent jobs (a technician sees only their own).
jobs[].idstringJob id.
jobs[].organizationIdstring
jobs[].identifierstringJOB-n, unique per organization.
jobs[].customerIdstring
jobs[].propertyIdstring or null
jobs[].assetIdstring or nullThe equipment worked on.
jobs[].technicianIdstring or nullThe lead technician of the next open visit.
jobs[].serviceTypeIdstring or null
jobs[].statusenumSCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED or CANCELLED. A job's status is its next open visit's. One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED.
jobs[].priorityenumOne of LOW, NORMAL, HIGH, URGENT.
jobs[].scheduledDatestringThe next open visit's day, YYYY-MM-DD.
jobs[].scheduledTimestring or nullHh: MM
jobs[].durationMinutesinteger
jobs[].priceCentsintegerJob price before tax, in integer cents.
jobs[].addressstring or null
jobs[].notesstring or null
jobs[].completionNotesstring or nullWhat was done.
jobs[].recurrenceIntervalenum or nullOne of WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, YEARLY.
jobs[].recurrenceParentIdstring or null
jobs[].onMyWayAtstring (date-time) or null
jobs[].arrivedAtstring (date-time) or null
jobs[].startedAtstring (date-time) or null
jobs[].completedAtstring (date-time) or null
jobs[].signedBystring or nullWho signed the work off on site.
jobs[].signedAtstring (date-time) or null
jobs[].businessUnitIdstring or null
jobs[].customFieldsanyCustom field values by key (definitions: GET /custom-fields?entity=JOB).
jobs[].weatherSensitiveboolean or nullPlans around the weather: overrides the service type; null follows it.
jobs[].createdAtstring (date-time)
jobs[].updatedAtstring (date-time)
jobs[].technicianobject or null
jobs[].technician.idstringTechnician id (GET /technicians).
jobs[].technician.namestring
jobs[].technician.colorstringCalendar colour, #rrggbb.
jobs[].serviceTypeobject or null
jobs[].serviceType.idstring
jobs[].serviceType.namestring
jobs[].serviceType.colorstring
jobCountintegerAll jobs for the customer.
balanceCentsintegerOpen balance: due on SENT/OVERDUE invoices less unapplied deposits; negative = in credit, in integer cents.
lifetimeValueCentsintegerEvery payment collected, net of refunds, tips excluded, in integer cents.
overCreditLimitbooleanThe balance is above creditLimitCents.

Update a customer

PATCH /organizations/{organizationId}/customers/{customerId}

Changes the fields sent (status, names, billing terms, tax exemption, tags, custom fields …). Email and phone mirror the primary contact; change contacts with the /contacts calls. Returns the customer.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolcustomers_update (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes

Request body

FieldTypeRequiredDescription
typeenumNoOne of RESIDENTIAL, COMMERCIAL.
titlestring or nullNoSalutation: Mr., Mrs., Ms., Miss, Dr. or Mx. (free text is kept; null or blank = none). Part of the display name, and what the AI receptionist says with the last name when its greeting is TITLE_LAST_NAME. Only set it from what the customer or office said: never infer it from a first name.
firstNamestring or nullNo
lastNamestring or nullNo
companyNamestring or nullNo
namestringNo
statusenumNoOne of LEAD, ACTIVE, INACTIVE, ARCHIVED.
emailempty string or string (email) or nullNo
phonestring or nullNo
addressstring or nullNo
address2string or nullNo
citystring or nullNo
statestring or nullNo
zipstring or nullNo
countrystring or nullNo
leadSourceIdstring or nullNo
tagsarray of stringNoUp to 100 items.
paymentTermsenumNoOne of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or nullNo0–3650.
taxExemptbooleanNo
taxExemptIdstring or nullNo
creditLimitCentsinteger or nullNo0–1000000000.
doNotServicebooleanNo
doNotServiceReasonstring or nullNo
preferredTechnicianIdstring or nullNo
allowEmailbooleanNo
allowSmsbooleanNo
allowMarketingbooleanNo
notesstring or nullNo
customFieldsobjectNo

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"RESIDENTIAL","title":"string","firstName":"string"}'

Response

200: the answer's fields
FieldTypeDescription
idstringCustomer id.
organizationIdstring
typeenumOne of RESIDENTIAL, COMMERCIAL.
titlestring or nullSalutation as typed or imported (Mr., Mrs., Ms., Miss, Dr., Mx.); null = none. The AI receptionist's title + last name greeting uses it, never a guess.
firstNamestring or null
lastNamestring or null
companyNamestring or null
namestringDisplay name, derived from the person or company name.
statusenumLEAD until the first job, then ACTIVE; ARCHIVED customers leave the lists. One of LEAD, ACTIVE, INACTIVE, ARCHIVED.
emailstring or nullThe primary contact's main email.
phonestring or nullThe primary contact's main phone.
addressstring or nullBilling address (street).
address2string or null
citystring or null
statestring or null
zipstring or null
countrystring or null
leadSourceIdstring or nullFrom GET /lead-sources.
tagsarray of string
paymentTermsenumInvoice due date rule; CUSTOM uses paymentTermsDays. One of DUE_ON_RECEIPT, NET_7, NET_15, NET_30, NET_45, NET_60, CUSTOM.
paymentTermsDaysinteger or nullDays to pay, for CUSTOM terms.
taxExemptbooleanInvoices and quotes carry no tax.
taxExemptIdstring or null
creditLimitCentsinteger or nullCredit limit in integer cents; null = none.
doNotServicebooleanBooking screens warn before scheduling work.
doNotServiceReasonstring or null
preferredTechnicianIdstring or null
allowEmailboolean
allowSmsboolean
allowMarketingboolean
notesstring or null
customFieldsanyCustom field values by key (definitions: GET /custom-fields).
createdAtstring (date-time)
updatedAtstring (date-time)
contactsarray of object
contacts[].idstringContact id.
contacts[].customerIdstring
contacts[].firstNamestring or null
contacts[].lastNamestring or null
contacts[].jobTitlestring or null
contacts[].rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
contacts[].isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
contacts[].notesstring or null
contacts[].createdAtstring (date-time)
contacts[].updatedAtstring (date-time)
contacts[].methodsarray of objectPhones and emails, primary first.
contacts[].methods[].idstring
contacts[].methods[].contactIdstring
contacts[].methods[].kindenumOne of PHONE, EMAIL.
contacts[].methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
contacts[].methods[].valuestringThe phone number or email address.
contacts[].methods[].isPrimarybooleanThe contact's main phone or email.
contacts[].methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
contacts[].methods[].sortOrderinteger

Delete a customer

DELETE /organizations/{organizationId}/customers/{customerId}

Destructive: deletes the customer with their contacts, notes, jobs, invoices and payments. Refused (409) while the customer has or had a property (end or transfer it, or archive the customer with PATCH status: ARCHIVED, which keeps everything).

Permissioncustomers.delete (Owner, Admin)
Risksensitive, OAuth scope servflo.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access.
MCP toolcustomers_delete (toolset customers)
ApprovalOver MCP, this tool always waits for your approval in ServFlo. See Approvals.

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes

Example

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

Response

200: the answer's fields
FieldTypeDescription
oktrue

List a customer's contacts

GET /organizations/{organizationId}/customers/{customerId}/contacts

The customer's contacts with their roles (primary, billing, site …) and phones and emails. Read-only.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolcontacts_list (toolset customers)

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes

Example

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

Response

200: the answer's fields

An array of objects:

FieldTypeDescription
idstringContact id.
customerIdstring
firstNamestring or null
lastNamestring or null
jobTitlestring or null
rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
notesstring or null
createdAtstring (date-time)
updatedAtstring (date-time)
methodsarray of objectPhones and emails, primary first.
methods[].idstring
methods[].contactIdstring
methods[].kindenumOne of PHONE, EMAIL.
methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
methods[].valuestringThe phone number or email address.
methods[].isPrimarybooleanThe contact's main phone or email.
methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
methods[].sortOrderinteger

Add a contact

POST /organizations/{organizationId}/customers/{customerId}/contacts

Adds a contact to the customer with roles and phone/email methods. The first contact, or one marked primary, becomes the primary contact and its email/phone mirror onto the customer. Returns the contact.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolcontacts_create (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes

Request body

FieldTypeRequiredDescription
firstNamestring or nullNo
lastNamestring or nullNo
jobTitlestring or nullNo
rolesarray of enumNoUp to 7 items.
isPrimarybooleanNo
notesstring or nullNo
methodsarray of objectNoUp to 50 items.
methods[].kindenumYesOne of PHONE, EMAIL.
methods[].labelenumNoOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
methods[].valuestringYesUp to 500 characters.
methods[].isPrimarybooleanNo
methods[].smsConsentenumNoOne of UNKNOWN, GRANTED, DENIED.

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"firstName":"string","lastName":"string","jobTitle":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstringContact id.
customerIdstring
firstNamestring or null
lastNamestring or null
jobTitlestring or null
rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
notesstring or null
createdAtstring (date-time)
updatedAtstring (date-time)
methodsarray of objectPhones and emails, primary first.
methods[].idstring
methods[].contactIdstring
methods[].kindenumOne of PHONE, EMAIL.
methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
methods[].valuestringThe phone number or email address.
methods[].isPrimarybooleanThe contact's main phone or email.
methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
methods[].sortOrderinteger

Update a contact

PATCH /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}

Changes the fields sent. methods, when given, replaces the contact's phones and emails (send the full list). Returns the contact.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolcontacts_update (toolset customers)

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes
contactIdpathstringYes

Request body

FieldTypeRequiredDescription
firstNamestring or nullNo
lastNamestring or nullNo
jobTitlestring or nullNo
rolesarray of enumNoUp to 7 items.
isPrimarybooleanNo
notesstring or nullNo
methodsarray of objectNoUp to 50 items.
methods[].kindenumYesOne of PHONE, EMAIL.
methods[].labelenumNoOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
methods[].valuestringYesUp to 500 characters.
methods[].isPrimarybooleanNo
methods[].smsConsentenumNoOne of UNKNOWN, GRANTED, DENIED.

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts/{contactId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"firstName":"string","lastName":"string","jobTitle":"string"}'

Response

200: the answer's fields
FieldTypeDescription
idstringContact id.
customerIdstring
firstNamestring or null
lastNamestring or null
jobTitlestring or null
rolesarray of enumWhat the contact is for; the primary contact has PRIMARY.
isPrimarybooleanThe primary contact; its main email and phone mirror onto the customer.
notesstring or null
createdAtstring (date-time)
updatedAtstring (date-time)
methodsarray of objectPhones and emails, primary first.
methods[].idstring
methods[].contactIdstring
methods[].kindenumOne of PHONE, EMAIL.
methods[].labelenumOne of MAIN, MOBILE, WORK, HOME, FAX, OTHER.
methods[].valuestringThe phone number or email address.
methods[].isPrimarybooleanThe contact's main phone or email.
methods[].smsConsentenumOK to text this phone (DENIED after the number replied STOP). One of UNKNOWN, GRANTED, DENIED.
methods[].sortOrderinteger

Delete a contact

DELETE /organizations/{organizationId}/customers/{customerId}/contacts/{contactId}

Destructive: deletes the contact and its phones and emails. Deleting the primary contact promotes the next one.

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

Parameters

NameInTypeRequiredDescription
customerIdpathstringYes
contactIdpathstringYes

Example

curl -X DELETE "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/customers/{customerId}/contacts/{contactId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
oktrue

List notes on a customer or property

GET /organizations/{organizationId}/{ownerType}/{ownerId}/notes

Internal notes on a customer or property (ownerType is customers or properties, ownerId its id). Pinned first, then newest first. Read-only.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolrecordNotes_list (toolset customers)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes

Example

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

Response

200: the answer's fields

An array of objects:

FieldTypeDescription
idstringNote id.
customerIdstring or null
propertyIdstring or null
contentstringInternal note text; never shown to the customer.
pinnedboolean
authorUserIdstring or null
createdAtstring (date-time)

Add a note

POST /organizations/{organizationId}/{ownerType}/{ownerId}/notes

Adds an internal note (never shown to the customer) to a customer or property, authored by the caller. Returns the note.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolrecordNotes_create (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes

Request body

FieldTypeRequiredDescription
contentstringYes
pinnedbooleanNo

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"string"}'

Response

201: the answer's fields
FieldTypeDescription
idstringNote id.
customerIdstring or null
propertyIdstring or null
contentstringInternal note text; never shown to the customer.
pinnedboolean
authorUserIdstring or null
createdAtstring (date-time)

Edit or pin a note

PATCH /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}

Changes a note's text and/or pins it to the top. Note ids come from the list call.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolrecordNotes_update (toolset customers)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes
noteIdpathstringYes

Request body

FieldTypeRequiredDescription
contentstringNo
pinnedbooleanNo

Example

curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes/{noteId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"string","pinned":true}'

Response

200: the answer's fields
FieldTypeDescription
idstringNote id.
customerIdstring or null
propertyIdstring or null
contentstringInternal note text; never shown to the customer.
pinnedboolean
authorUserIdstring or null
createdAtstring (date-time)

Delete a note

DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/notes/{noteId}

Destructive: deletes the note from the customer or property. Cannot be undone.

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

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes
noteIdpathstringYes

Example

curl -X DELETE "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/notes/{noteId}" \
  -H "Authorization: Bearer $SERVFLO_API_KEY"

Response

200: the answer's fields
FieldTypeDescription
oktrue

List files on a customer or property

GET /organizations/{organizationId}/{ownerType}/{ownerId}/attachments

Files on a customer or property, each with a download URL valid for 10 minutes. Read-only.

Permissioncustomers.read (Owner, Admin, Dispatcher, Technician, Member)
Riskread, OAuth scope servflo.read. Read: reads only.
MCP toolattachments_list (toolset customers)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes

Example

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

Response

200: the answer's fields

An array of objects:

FieldTypeDescription
idstringAttachment id.
organizationIdstring
customerIdstring or null
propertyIdstring or null
jobIdstring or null
tagenum or nullOne of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE.
fileNamestring
contentTypestring
sizeBytesinteger
uploadedByUserIdstring or null
uploadedAtstring (date-time) or nullSet once the upload is confirmed; null before.
createdAtstring (date-time)
downloadUrlstringDownload URL, valid for 10 minutes.

Start a file upload

POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments

Step 1 of 2 of attaching a file: returns uploadUrl; PUT the file there (with the same Content-Type) within 5 minutes, then call POST …/attachments/{attachmentId}/confirm. Unconfirmed uploads are not listed.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolattachments_createUpload (toolset customers)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes

Request body

FieldTypeRequiredDescription
fileNamestringYesUp to 500 characters.
contentTypestringYesUp to 200 characters.
sizeBytesintegerYes

Example

curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/{ownerType}/{ownerId}/attachments" \
  -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.idstringAttachment id.
attachment.organizationIdstring
attachment.customerIdstring or null
attachment.propertyIdstring or null
attachment.jobIdstring or null
attachment.tagenum or nullOne of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE.
attachment.fileNamestring
attachment.contentTypestring
attachment.sizeBytesinteger
attachment.uploadedByUserIdstring or null
attachment.uploadedAtstring (date-time) or nullSet once the upload is confirmed; null before.
attachment.createdAtstring (date-time)
uploadUrlstringPUT the file here (same Content-Type) within 5 minutes, then confirm.

Confirm a finished upload

POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}/confirm

Step 2 of 2: marks the uploaded file as present so it is listed. Call after the PUT to uploadUrl from POST …/attachments succeeded.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolattachments_confirm (toolset customers)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes
attachmentIdpathstringYes

Example

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

Response

200: the answer's fields
FieldTypeDescription
idstringAttachment id.
organizationIdstring
customerIdstring or null
propertyIdstring or null
jobIdstring or null
tagenum or nullOne of BEFORE, AFTER, GENERAL, ISSUE, SIGNATURE.
fileNamestring
contentTypestring
sizeBytesinteger
uploadedByUserIdstring or null
uploadedAtstring (date-time) or nullSet once the upload is confirmed; null before.
createdAtstring (date-time)

Delete a file

DELETE /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/{attachmentId}

Destructive: deletes the file and its stored object.

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

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes
attachmentIdpathstringYes

Example

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

Response

200: the answer's fields
FieldTypeDescription
oktrue

Attach a file (content in the request)

POST /organizations/{organizationId}/{ownerType}/{ownerId}/attachments/content

Attaches a file to a customer or property in one call (ownerType customers or properties, ownerId its id): send contentBase64 or a public sourceUrl (up to 10 MB; any type). The file is stored privately and listed with the record's files (GET …/attachments, with a 10-minute download URL). The browser uploads with POST …/attachments and confirm instead. Returns the file.

Permissioncustomers.write (Owner, Admin, Dispatcher)
Riskwrite, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP toolattachments_upload (toolset customers, in the default set)

Parameters

NameInTypeRequiredDescription
ownerTypepathenumYesOne of customers, properties.
ownerIdpathstringYes

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/{ownerType}/{ownerId}/attachments/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)

On this page