Dispatch API
REST endpoints for dispatch in ServFlo: 6 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.
| Operation | Endpoint | Risk |
|---|---|---|
| The dispatch board for a day | GET /organizations/{organizationId}/dispatch | read |
| Plan a technician's route (nearest-next) | GET /organizations/{organizationId}/dispatch/route | read |
| Save a technician's route order | POST /organizations/{organizationId}/dispatch/route | write |
| Business address and travel speed | GET /organizations/{organizationId}/dispatch-settings | read |
| Change the business address or travel speed | PATCH /organizations/{organizationId}/dispatch-settings | write |
| Where the technicians are | GET /organizations/{organizationId}/dispatch/locations | read |
The dispatch board for a day
GET /organizations/{organizationId}/dispatch
Everything the dispatch board draws for days days from date: active technicians (working hours, home base), every visit on those days with status, field times, customer, service, property coordinates and crew; unscheduled (open visits without a date, or without a crew after the shown days) and requests (NEW requests awaiting an assessment, only for roles that may read requests); and the business address and travel speed. A technician gets only their own row (ownRowOnly), with no unscheduled work or requests. Read-only. Move visits with POST /jobs/{jobId}/visits/{visitId}/move.
| Permission | schedule.read (Owner, Admin, Dispatcher, Technician, Member) |
| Risk | read, OAuth scope servflo.read. Read: reads only. |
| MCP tool | dispatch_board (toolset scheduling) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
date | query | string | No | The first day shown, YYYY-MM-DD. Defaults to today in the organization's timezone. |
days | query | integer | No | How many days, 1–7. Default 1. |
businessUnitId | query | string | No | Only this business unit's work and the technicians who may work for it (GET /business-units). |
Example
curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch" \
-H "Authorization: Bearer $SERVFLO_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
dates | array of string | |
organization | object | |
organization.address | string or null | |
organization.city | string or null | |
organization.latitude | number or null | |
organization.longitude | number or null | |
organization.travelSpeedKmh | integer | |
ownRowOnly | boolean | True for a technician: only their own row is included. |
technicians | array of object | |
technicians[].id | string | |
technicians[].name | string | |
technicians[].color | string | |
technicians[].workStartTime | string | |
technicians[].workEndTime | string | |
technicians[].homeAddress | string or null | |
technicians[].homeLatitude | number or null | |
technicians[].homeLongitude | number or null | |
technicians[].homeBusinessUnitId | string or null | Their home business unit. |
visits | array of object | |
visits[].id | string | |
visits[].jobId | string | |
visits[].scheduledDate | string or null | YYYY-MM-DD; null = not scheduled yet. |
visits[].scheduledTime | string or null | |
visits[].durationMinutes | integer | |
visits[].status | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
visits[].notes | string or null | |
visits[].sortOrder | integer | |
visits[].routeOrder | integer or null | Position in the technician's route for the day (Optimize route); null = by time. |
visits[].onMyWayAt | string (date-time) or null | |
visits[].arrivedAt | string (date-time) or null | |
visits[].startedAt | string (date-time) or null | |
visits[].completedAt | string (date-time) or null | |
visits[].createdAt | string (date-time) | |
visits[].updatedAt | string (date-time) | |
visits[].crew | array of object | Technicians on the visit, lead first. |
visits[].crew[].technicianId | string | |
visits[].crew[].isLead | boolean | The lead runs the visit; the job's technician is its lead. |
visits[].crew[].name | string | |
visits[].crew[].color | string | |
visits[].identifier | string | |
visits[].jobStatus | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
visits[].priority | enum | One of LOW, NORMAL, HIGH, URGENT. |
visits[].address | string or null | |
visits[].customer | object | |
visits[].customer.id | string | |
visits[].customer.name | string | |
visits[].customer.phone | string or null | |
visits[].serviceType | object or null | |
visits[].serviceType.id | string | |
visits[].serviceType.name | string | |
visits[].serviceType.color | string | |
visits[].businessUnit | object or null | The job's business unit. |
visits[].businessUnit.id | string | |
visits[].businessUnit.name | string | |
visits[].property | object or null | |
visits[].property.id | string | |
visits[].property.name | string or null | |
visits[].property.address | string or null | |
visits[].property.city | string or null | |
visits[].property.latitude | number or null | |
visits[].property.longitude | number or null | |
visits[].visitCount | integer | How many visits the job has. |
visits[].membership | object or null | Set when the customer is a member: the membership covering this job, with dueMonth when the job is an included member visit. |
visits[].membership.membershipId | string | |
visits[].membership.identifier | string | MEM-n |
visits[].membership.planName | string | |
visits[].membership.priority | boolean | The plan flags its members' work as priority on the board. |
visits[].membership.dueMonth | string or null | YYYY-MM when the job is an included member visit; null for other work for a member. |
visits[].membership.dueDate | string or null | YYYY-MM-DD when the member visit is on a week cadence (every N weeks); null for month-based visits. |
visits[].technicianId | string or null | The visit's lead technician. |
unscheduled | array of object | |
unscheduled[].id | string | |
unscheduled[].jobId | string | |
unscheduled[].scheduledDate | string or null | YYYY-MM-DD; null = not scheduled yet. |
unscheduled[].scheduledTime | string or null | |
unscheduled[].durationMinutes | integer | |
unscheduled[].status | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
unscheduled[].notes | string or null | |
unscheduled[].sortOrder | integer | |
unscheduled[].routeOrder | integer or null | Position in the technician's route for the day (Optimize route); null = by time. |
unscheduled[].onMyWayAt | string (date-time) or null | |
unscheduled[].arrivedAt | string (date-time) or null | |
unscheduled[].startedAt | string (date-time) or null | |
unscheduled[].completedAt | string (date-time) or null | |
unscheduled[].createdAt | string (date-time) | |
unscheduled[].updatedAt | string (date-time) | |
unscheduled[].crew | array of object | Technicians on the visit, lead first. |
unscheduled[].crew[].technicianId | string | |
unscheduled[].crew[].isLead | boolean | The lead runs the visit; the job's technician is its lead. |
unscheduled[].crew[].name | string | |
unscheduled[].crew[].color | string | |
unscheduled[].identifier | string | |
unscheduled[].jobStatus | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
unscheduled[].priority | enum | One of LOW, NORMAL, HIGH, URGENT. |
unscheduled[].address | string or null | |
unscheduled[].customer | object | |
unscheduled[].customer.id | string | |
unscheduled[].customer.name | string | |
unscheduled[].customer.phone | string or null | |
unscheduled[].serviceType | object or null | |
unscheduled[].serviceType.id | string | |
unscheduled[].serviceType.name | string | |
unscheduled[].serviceType.color | string | |
unscheduled[].businessUnit | object or null | The job's business unit. |
unscheduled[].businessUnit.id | string | |
unscheduled[].businessUnit.name | string | |
unscheduled[].property | object or null | |
unscheduled[].property.id | string | |
unscheduled[].property.name | string or null | |
unscheduled[].property.address | string or null | |
unscheduled[].property.city | string or null | |
unscheduled[].property.latitude | number or null | |
unscheduled[].property.longitude | number or null | |
unscheduled[].visitCount | integer | How many visits the job has. |
unscheduled[].membership | object or null | Set when the customer is a member: the membership covering this job, with dueMonth when the job is an included member visit. |
unscheduled[].membership.membershipId | string | |
unscheduled[].membership.identifier | string | MEM-n |
unscheduled[].membership.planName | string | |
unscheduled[].membership.priority | boolean | The plan flags its members' work as priority on the board. |
unscheduled[].membership.dueMonth | string or null | YYYY-MM when the job is an included member visit; null for other work for a member. |
unscheduled[].membership.dueDate | string or null | YYYY-MM-DD when the member visit is on a week cadence (every N weeks); null for month-based visits. |
unscheduled[].technicianId | string or null | The visit's lead technician. |
requests | array of object | |
requests[].id | string | |
requests[].identifier | string | |
requests[].contactName | string or null | |
requests[].description | string | |
requests[].preferredDate | string or null | |
requests[].preferredTimeWindow | string | |
requests[].address | string or null | |
requests[].createdAt | string (date-time) | |
requests[].customer | object or null | |
requests[].customer.id | string | |
requests[].customer.name | string | |
requests[].serviceType | object or null | |
requests[].serviceType.id | string | |
requests[].serviceType.name | string | |
requests[].serviceType.color | string | |
requests[].property | object or null | |
requests[].property.id | string | |
requests[].property.name | string or null | |
requests[].property.address | string or null | |
requests[].property.city | string or null | |
requests[].property.latitude | number or null | |
requests[].property.longitude | number or null |
Plan a technician's route (nearest-next)
GET /organizations/{organizationId}/dispatch/route
Proposes a driving order for a technician's visits on date without saving anything: nearest-next from their home base (else the business address), with the straight-line km of the current and the proposed order and the start times the proposed order would get (leaving at startTime, default their working-hours start, travelling at the business's average speed). Visits without coordinates go last. Save it with POST /dispatch/route. A technician may plan only their own day. Read-only.
| Permission | schedule.read (Owner, Admin, Dispatcher, Technician, Member) |
| Risk | read, OAuth scope servflo.read. Read: reads only. |
| MCP tool | dispatch_planRoute (toolset scheduling) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
technicianId | query | string | Yes | Whose day (GET /technicians/options). |
date | query | string | Yes | A calendar date in the business's own calendar, YYYY-MM-DD. |
startTime | query | string | No | When the day starts; defaults to their working hours. |
Example
curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch/route?technicianId=technician_id&date=2026-11-03" \
-H "Authorization: Bearer $SERVFLO_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
technicianId | string | |
date | string | |
start | object or null | Where the route starts; null when nothing is geocoded. |
start.latitude | number | |
start.longitude | number | |
startSource | enum or null | One of HOME, BUSINESS. |
speedKmh | number | |
startTime | string | |
stops | array of object | |
stops[].visitId | string | |
stops[].jobId | string | |
stops[].identifier | string | |
stops[].customerName | string | |
stops[].status | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
stops[].scheduledTime | string or null | |
stops[].durationMinutes | integer | |
stops[].routeOrder | integer or null | |
stops[].latitude | number or null | |
stops[].longitude | number or null | |
current | object | |
current.visitIds | array of string | |
current.totalKm | number | |
optimized | object | |
optimized.visitIds | array of string | |
optimized.totalKm | number | |
optimized.times | array of object | |
optimized.times[].id | string | |
optimized.times[].scheduledTime | string | |
optimized.times[].travelMinutes | integer | |
missingCoordinates | integer | Visits without coordinates (placed last). |
Save a technician's route order
POST /organizations/{organizationId}/dispatch/route
Saves the driving order of a technician's visits on date (visitIds: all of that day's visits, in order) as routeOrder 1..n, which My day follows. With retime, scheduled and confirmed visits also get sequential start times (durations kept, travel buffers at the business's average speed from GET /dispatch-settings). 409 when the day's visits changed since GET /dispatch/route. 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. |
| MCP tool | dispatch_applyRoute (toolset scheduling) |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
technicianId | string | Yes | |
date | string | Yes | A calendar date in the business's own calendar, YYYY-MM-DD. |
visitIds | array of string | Yes | Every visit of that day, in driving order. Up to 100 items. |
retime | boolean | No | Also re-time the visits sequentially. |
startTime | string | No | When the day starts; defaults to their working hours. |
Example
curl -X POST "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch/route" \
-H "Authorization: Bearer $SERVFLO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"technicianId":"technician_id","date":"2026-11-03","visitIds":["visit_id"]}'Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
id | string | |
jobId | string | |
scheduledDate | string or null | YYYY-MM-DD; null = not scheduled yet. |
scheduledTime | string or null | |
durationMinutes | integer | |
status | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
notes | string or null | |
sortOrder | integer | |
routeOrder | integer or null | Position in the technician's route for the day (Optimize route); null = by time. |
onMyWayAt | string (date-time) or null | |
arrivedAt | string (date-time) or null | |
startedAt | string (date-time) or null | |
completedAt | string (date-time) or null | |
createdAt | string (date-time) | |
updatedAt | string (date-time) | |
crew | array of object | Technicians on the visit, lead first. |
crew[].technicianId | string | |
crew[].isLead | boolean | The lead runs the visit; the job's technician is its lead. |
crew[].name | string | |
crew[].color | string | |
identifier | string | |
jobStatus | enum | One of SCHEDULED, CONFIRMED, IN_PROGRESS, COMPLETED, CANCELLED. |
priority | enum | One of LOW, NORMAL, HIGH, URGENT. |
address | string or null | |
customer | object | |
customer.id | string | |
customer.name | string | |
customer.phone | string or null | |
serviceType | object or null | |
serviceType.id | string | |
serviceType.name | string | |
serviceType.color | string | |
businessUnit | object or null | The job's business unit. |
businessUnit.id | string | |
businessUnit.name | string | |
property | object or null | |
property.id | string | |
property.name | string or null | |
property.address | string or null | |
property.city | string or null | |
property.latitude | number or null | |
property.longitude | number or null | |
visitCount | integer | How many visits the job has. |
membership | object or null | Set when the customer is a member: the membership covering this job, with dueMonth when the job is an included member visit. |
membership.membershipId | string | |
membership.identifier | string | MEM-n |
membership.planName | string | |
membership.priority | boolean | The plan flags its members' work as priority on the board. |
membership.dueMonth | string or null | YYYY-MM when the job is an included member visit; null for other work for a member. |
membership.dueDate | string or null | YYYY-MM-DD when the member visit is on a week cadence (every N weeks); null for month-based visits. |
technicianId | string or null | The visit's lead technician. |
Business address and travel speed
GET /organizations/{organizationId}/dispatch-settings
The business address (every technician's route starts here unless they have a home base; the weather forecast point) with its coordinates and how close they are (locationPrecision), and the average travel speed in km/h used for travel buffers when re-timing a route. Read-only.
| Permission | settings.read (Owner, Admin, Dispatcher) |
| Risk | read, OAuth scope servflo.read. Read: reads only. |
| MCP tool | dispatchSettings_get (toolset settings) |
Example
curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch-settings" \
-H "Authorization: Bearer $SERVFLO_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
address | string or null | |
city | string or null | |
state | string or null | |
zip | string or null | |
country | string or null | |
latitude | number or null | |
longitude | number or null | |
locationPrecision | string or null | How close the coordinates are: ADDRESS (the address itself), STREET (its street: the map has no house number there), AREA (its town or ZIP), PINNED (sent by hand); null when not on the map or located before this was recorded. |
travelSpeedKmh | integer |
Change the business address or travel speed
PATCH /organizations/{organizationId}/dispatch-settings
Sets the business address (Settings → Company profile; every route's default start and the weather forecast point) and the average travel speed (5–150 km/h). The address is geocoded after saving unless latitude/longitude are sent with it (then locationPrecision is PINNED); when the exact address is not on the map the street, then the town or ZIP, is used (locationPrecision says which). Any save while the address has no coordinates looks it up again. The configured geocoder may send the address to OpenStreetMap Nominatim. Send only what changes. Nothing is shown to customers.
| Permission | settings.manage (Owner, Admin) |
| Risk | write, OAuth scope servflo.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | dispatchSettings_update (toolset settings) |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
address | string or null | No | Up to 500 characters. |
city | string or null | No | Up to 200 characters. |
state | string or null | No | Up to 200 characters. |
zip | string or null | No | Up to 50 characters. |
country | string or null | No | Up to 200 characters. |
latitude | number or null | No | Pin by hand (WGS84). -90–90. |
longitude | number or null | No | -180–180. |
travelSpeedKmh | integer | No | 5–150. |
Example
curl -X PATCH "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch-settings" \
-H "Authorization: Bearer $SERVFLO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"string","city":"string","state":"string"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
address | string or null | |
city | string or null | |
state | string or null | |
zip | string or null | |
country | string or null | |
latitude | number or null | |
longitude | number or null | |
locationPrecision | string or null | How close the coordinates are: ADDRESS (the address itself), STREET (its street: the map has no house number there), AREA (its town or ZIP), PINNED (sent by hand); null when not on the map or located before this was recorded. |
travelSpeedKmh | integer |
Where the technicians are
GET /organizations/{organizationId}/dispatch/locations
Each technician's last shared position (only while they were on the clock or on the way), with when and whether it is live (sent in the last 10 minutes). For the dispatch map. Read-only.
| Permission | jobs.assign (Owner, Admin, Dispatcher) |
| Risk | read, OAuth scope servflo.read. Read: reads only. |
| MCP tool | tracking_team (toolset scheduling) |
Example
curl "https://app.servflo.ai/api/organizations/$ORGANIZATION_ID/dispatch/locations" \
-H "Authorization: Bearer $SERVFLO_API_KEY"Response
200: the answer's fields
An array of objects:
| Field | Type | Description |
|---|---|---|
technicianId | string | |
latitude | number | |
longitude | number | |
accuracyM | number or null | Accuracy in metres. |
headingDeg | number or null | |
recordedAt | string (date-time) | When the phone took the position. |
technician | object | |
technician.name | string | |
technician.color | string | |
live | boolean | Sent in the last 10 minutes. |