REST endpoints for visits in ServFlo: 10 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}/jobs/{jobId}/visits
The job's visits in order (date, then time), each with its status, field times, route position and crew (lead first). GET /jobs/{jobId} carries the same list as visits. A technician gets 404 for a job they are not on the crew of. Read-only.
POST /organizations/{organizationId}/jobs/{jobId}/visits
Adds a visit (e.g. day 2 of an install) with an optional date, time, duration and crew. Without scheduledDate it waits on the dispatch board's 'Needs scheduling' list. The job then mirrors its next open visit (its date, time and technician may change). Nothing is sent to anyone. Get technician ids from GET /technicians/options.
Permission
jobs.write (Owner, Admin, Dispatcher)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
A local time of day, HH:MM (24-hour). Blank means 09:00 on new jobs.
durationMinutes
integer
No
Minutes; defaults to the job's duration. 5–1440.
notes
string or null
No
What this visit is for (shown to the crew).
customFields
object
No
Custom field values by key (definitions: GET /custom-fields?entity=VISIT); merged into the saved values on an update, null clears one. Checked against each field's type and options; unknown keys are refused.
technicianIds
array of string
No
Technicians on the visit's crew (GET /technicians/options). Empty = unassigned. Up to 20 items.
leadTechnicianId
string or null
No
Who leads the visit; defaults to the first technician listed.
Changes a visit's date (null takes it off the calendar), time, duration or notes, or sets its status to SCHEDULED, CONFIRMED or CANCELLED (reopening a completed visit clears its completion). Start and complete with POST …/start and …/complete instead. The job re-mirrors its next open visit: cancelling the last open visit completes or cancels the job. Nothing is sent to the customer.
Permission
jobs.write (Owner, Admin, Dispatcher)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
A local time of day, HH:MM (24-hour). Blank means 09:00 on new jobs.
durationMinutes
integer
No
Minutes on site. 5–1440.
notes
string or null
No
customFields
object
No
Custom field values by key (definitions: GET /custom-fields?entity=VISIT); merged into the saved values on an update, null clears one. Checked against each field's type and options; unknown keys are refused.
Destructive: removes the visit and its crew; time logged on it stays on the job. A job keeps at least one visit (409 for its only one: cancel it or delete the job). The job re-mirrors its next open visit.
Permission
jobs.delete (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
visits_delete (toolset scheduling)
Approval
Over MCP, this tool always waits for your approval in ServFlo. See Approvals.
Replaces the visit's crew: any number of technicians, one of them the lead (the job's technician mirrors its next open visit's lead). An empty list unassigns the visit. Technicians on any visit's crew see the job, its customer and property. Nothing is sent to anyone.
Permission
jobs.assign (Owner, Admin, Dispatcher)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
POST /organizations/{organizationId}/jobs/{jobId}/visits/{visitId}/move
The board's drag and drop: sets the visit's date, start time and (optionally, after a resize) duration, and moves it from one technician to another (fromTechnicianId leaves the crew, toTechnicianId joins, as lead if the one leaving led; toTechnicianId: null unassigns). Returns the visit and conflicts for the technician it landed on (overlapping visits, outside working hours): warnings only, the move is saved. Only open visits move (409 otherwise).
Permission
jobs.assign (Owner, Admin, Dispatcher)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
MCP tool
visits_move (toolset scheduling, in the default set)
POST /organizations/{organizationId}/jobs/{jobId}/visits/{visitId}/on-my-way
Marks a SCHEDULED or CONFIRMED visit 'On the way' (onMyWayAt, shown live on the dispatch board) and starts the caller's TRAVEL time (a linked technician; else the visit's lead). Emits technician.on_my_way; no text is sent to the customer yet. Technicians only for visits they are on the crew of (404 otherwise).
Permission
jobs.work (Owner, Admin, Dispatcher, Technician)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
POST /organizations/{organizationId}/jobs/{jobId}/visits/{visitId}/arrive
Stamps the visit's arrivedAt (the office sees 'Arrived') and stops the caller's TRAVEL time for the job. The visit is not started; call POST …/start for that.
Permission
jobs.work (Owner, Admin, Dispatcher, Technician)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
POST /organizations/{organizationId}/jobs/{jobId}/visits/{visitId}/start
Sets the visit IN_PROGRESS (startedAt; arriving too if not yet), stops travel and any other running job clock and starts JOB time on this visit. The job becomes IN_PROGRESS. Emits job.started. Nothing is sent to the customer.
Permission
jobs.work (Owner, Admin, Dispatcher, Technician)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.
POST /organizations/{organizationId}/jobs/{jobId}/visits/{visitId}/complete
Sets the visit COMPLETED and stops the job's JOB and TRAVEL time. When it was the job's last open visit the job completes too: then the job's required forms must be answered (400 naming the missing fields; fill them with PATCH /jobs/{jobId}/forms/{formId}), complete drafts are submitted, and a recurring job books its next occurrence (job.nextJobId, emits job.completed). With more visits scheduled, the job stays open on the next one.
Permission
jobs.work (Owner, Admin, Dispatcher, Technician)
Risk
write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it.