CHAPTER 04 OF 8
Reschedule, track, and cancel work
Use existing record IDs and current versions to answer follow-up calls without duplicating the original job.
1. Find the right job and visit
After verifying the caller, list their jobs and the selected job’s visits. Confirm which appointment they mean, then load the current record details. Keep the latest version from every response; another user may have changed the schedule since the original call.
GET/field_service/jobs
List jobs
| Name | Location | Type / schema |
|---|---|---|
search | query | string |
status | query | string |
customer_id | query | string |
property_id | query | string |
job_id | query | string |
limit | query | integer |
offset | query | integer |
created_after | query | string |
created_before | query | string |
Documented responses: 200 401 403
GET/field_service/jobs/{id}
Get job
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
Documented responses: 200 404
GET/field_service/visits
List visits
| Name | Location | Type / schema |
|---|---|---|
search | query | string |
status | query | string |
customer_id | query | string |
property_id | query | string |
job_id | query | string |
limit | query | integer |
offset | query | integer |
created_after | query | string |
created_before | query | string |
Documented responses: 200 401 403
GET/field_service/visits/{id}
Get visit
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
Documented responses: 200 404
const jobs = await api('GET', '/jobs?customer_id=' + customer.id);
// callerSelectedJobId is selected from jobs, not an unverified external ID.
job = await api('GET', '/jobs/' + callerSelectedJobId);
const visits = await api('GET', '/visits?job_id=' + job.id);
visit = await api('GET', '/visits/' + callerSelectedVisitId);JavaScript · server side2. Move the appointment
“Could we move that appointment?” does not specify a replacement time. Repeat the booking chapter’s conversation: ask for a new date and window, fetch matching availability, offer times, wait for a choice, read back the exact replacement, and wait for an explicit yes. Until then, leave the existing appointment unchanged. Use the server-held confirmed proposal as replacementSlot; do not select the first later slot automatically.
After that confirmation, patch the existing visit with the new slot and current version, then save relevant job context. The API releases the old time when the reschedule succeeds. A conflict requires a fresh choice and confirmation.
PATCH/field_service/visits/{id}
Update visit (PATCH)
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
recordrequired | body | models.FieldServiceRecord |
Documented responses: 200 400 409
PATCH/field_service/jobs/{id}
Update job (PATCH)
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
recordrequired | body | models.FieldServiceRecord |
Documented responses: 200 400 409
visit = await api('PATCH', '/visits/' + visit.id, {
version: visit.version,
starts_at: replacementSlot.starts_at, ends_at: replacementSlot.ends_at,
timezone: replacementSlot.timezone,
assigned_user_ids: [replacementSlot.eligible_staff_ids[0]],
notify_customer: false, notify_assigned_users: false,
});
job = await api('PATCH', '/jobs/' + job.id, {
version: job.version,
notes: 'Caller requested a later quote appointment.',
});JavaScript · server side
The integration verifies no appointment changes before the caller’s final yes, persistence of the second offered time the caller selected, and availability of the released slot.
Select the image to view it at full size.3. Report technician status
When the caller asks “Where is my technician?”, read the visit’s tracking response. Report only the status and ETA the business actually provides. An absent ETA should lead to a handoff or an honest “I don’t have an arrival estimate yet.”
The demo simulates an authorized technician progressing through en_route, arrive, start, and complete. In a live integration, those writes need an authorized dispatch or technician event; a status question from a caller is not permission to advance the work.
GET/field_service/visits/{id}/tracking
Read an appointment's dispatch status, arrival window and available ETA
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
Documented responses: 200
POST/field_service/visits/{id}/actions
Run visit action
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
actionrequired | body | gin.FieldServiceActionRequest |
Documented responses: 200 400 403 409
POST/field_service/jobs/{id}/actions
Run job action
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
actionrequired | body | gin.FieldServiceActionRequest |
Documented responses: 200 400 403 409
const tracking = await api('GET', '/visits/' + visit.id + '/tracking');
// Report tracking to the caller. The following is demo dispatch simulation:
for (const action of ['en_route', 'arrive', 'start', 'complete']) {
visit = await api('POST', '/visits/' + visit.id + '/actions', {
action, version: visit.version,
});
}
job = await api('POST', '/jobs/' + job.id + '/actions', {
action: 'complete', version: job.version,
});JavaScript · server side4. Cancel with an explicit scope
Confirm the job, reason, and which appointments should be cancelled. Supported scopes are job_only, future_appointments, and all_open_appointments. Completed visits are retained.
The demo first runs a separate “Book a follow-up” conversation, collecting the caller’s date, window, selected slot, and explicit confirmation before creating its job and visit. A later “Cancel a follow-up” step cancels those existing records. A cancellation request never creates a new appointment.
Cancellation processes at most 25 visits per call. If complete is false, follow the reference’s continuation contract using the same reason and scope. Do not announce that everything is cancelled until the operation is complete.
POST/field_service/jobs
Create job
| Name | Location | Type / schema |
|---|---|---|
recordrequired | body | models.FieldServiceRecord |
Documented responses: 201 400 409
POST/field_service/jobs/{id}/cancel
Cancel a job and optionally its open appointments in resumable batches
| Name | Location | Type / schema |
|---|---|---|
idrequired | path | string |
cancellationrequired | body | gin.fieldServiceCancelJobRequest |
Documented responses: 200 409
// Earlier call: ONLY after the caller confirms the follow-up's exact slot.
const followup = await api('POST', '/jobs', {
title: 'Optional follow-up', customer_id: customer.id, property_id: location.id,
});
// Save its visit with that confirmed slot using the previous chapter's flow.
// Persist both IDs and return the success to the caller.
// Later cancellation request: reload the existing follow-up and confirm scope.
// Do not create any new job or visit here.
const currentFollowup = await api('GET', '/jobs/' + followup.id);
const cancellation = await api('POST', '/jobs/' + followup.id + '/cancel', {
version: currentFollowup.version,
reason: 'Caller no longer needs the optional follow-up.',
scope: 'all_open_appointments',
});
// Inspect cancellation.complete and the returned job before confirming.JavaScript · server side
The job and its open appointment are cancelled together, with a reason attached.
Select the image to view it at full size.