Visits | FieldCamp API
The FieldCamp Visits API schedules, updates, cancels, and tracks on-site appearances for jobs across an 8-state visit lifecycle.
The FieldCamp Visits API represents scheduled on-site appearances for a job. A single job may have one visit (a quick service call) or several visits chained together (a delivery followed by a collection, a diagnostic followed by a repair, or a multi-day install). The v1 endpoints expose the full visit lifecycle — from creation through dispatch, arrival, completion, and cancellation — and integrate with FieldCamp's AI Dispatcher and live team tracking.
When to create visits explicitly
For most simple jobs, FieldCamp auto-generates a visit from the job's time window when you POST /api/v1/jobs with visits: []. Create visits explicitly via POST /api/v1/visits when you need:
- More than one visit per job — e.g. a two-stop install, or a return trip for a part that was on backorder.
- Explicit control over
scheduledStart/scheduledEnd— instead of inheriting the job's window. - A different team or technician than the parent job — e.g. the estimator visits first, then the install crew.
- Unscheduled or anytime visits — placeholders that sit on the unscheduled queue until your dispatcher assigns a slot.
- Different
serviceDuration— useful when one stop is long (an install) and another is short (a follow-up walkthrough).
If you let FieldCamp auto-generate the visit, the AI Dispatcher can still optimize the assignment using skills, capacity, and travel time. You don't lose intelligence by skipping explicit visit creation.
Visit lifecycle and visitStatus
Every visit moves through a defined lifecycle. The visitStatus field is the single source of truth and powers the dispatch calendar, live tracking, and reporting. You can filter GET /api/v1/visits?visitStatus=... by any of the eight states below — and pass multiple values to fetch several at once.
visitStatus | Meaning |
|---|---|
scheduled | Assigned to a team with a fixed start and end time. |
unscheduled | Created without a time slot — sits on the unscheduled queue. |
in_transit | Technician is driving to the site (GPS confirmed). |
arrived | Technician has reached the location but hasn't started work. |
in_progress | Work is actively being performed. |
paused | Work was started then paused (lunch, awaiting a part, customer not ready). |
completed | The visit has been finished and submitted from the field. |
cancelled | The visit was cancelled before completion. |
Status transitions are driven by the mobile app, dispatch actions, and webhook automations. You can write any allowed status via PUT /api/v1/visits/:id, but the dispatcher will reject transitions that don't make sense (e.g. moving from completed back to scheduled requires re-opening the visit).
See Job and visit statuses for the parent-job mapping of these states.
Fields
jobId— the parent job, returned fromPOST /api/v1/jobs.scheduledStart/scheduledEnd— UTC ISO-8601 datetimes. Omit both withunscheduled: true.teamId[]— an array of user IDs fromGET /api/v1/team. The Visits API accepts multiple assignees per visit, so a two-person crew is a single visit, not two. Replaces the old singletechnicianIdfield.serviceDuration— the planned on-site duration in minutes. Used by the dispatcher to fit the visit into the team's capacity even whenscheduledEndis not yet pinned.unscheduled— boolean. Whentrue, the visit is created without a time slot and sits on the dispatch queue.anyTime— boolean. Marks the visit as flexible-within-day (no fixed hour). The dispatcher will slot it into the first open window.visitStatus— one of the eight values in the lifecycle table above.
teamId is always an array, even for a one-person visit (teamId: ["usr_..."]). Sending a bare string from the old technicianId field will return a 400 Bad Request.
Creating a visit
Create or look up the parent job
Every visit belongs to a job. Either POST /api/v1/jobs first or look up the jobId from your existing record. See the Jobs resource for the full job payload.
Choose a team
Fetch your active team via GET /api/v1/team and assemble a teamId[] array. For one-person jobs that's a single-element array; for two-person crews list both user IDs.
Decide on scheduling mode
Pick one: a fixed scheduledStart/scheduledEnd, anyTime: true with a date, or unscheduled: true. Mixing fixed times with unscheduled: true is not allowed.
POST the visit
Send POST /api/v1/visits with jobId, teamId[], scheduling fields, and an optional serviceDuration. The response includes the new visitId and a visitStatus of either scheduled or unscheduled.
Confirm on the dispatch calendar
New visits surface immediately on the dispatch calendar. Live tracking turns on automatically once a team member starts the visit on mobile.
Listing and filtering visits
GET /api/v1/visits supports filtering by visitStatus, teamId, jobId, and date range. Common patterns:
- Today's active work —
?visitStatus=in_transit,arrived,in_progress,paused. - Backlog for a dispatcher —
?visitStatus=unscheduled. - A crew's day —
?teamId=usr_abc&from=2026-05-29T00:00:00Z&to=2026-05-29T23:59:59Z. - What got cancelled last week —
?visitStatus=cancelled&from=...&to=...for churn analysis.
Pair these queries with FieldCamp's route optimization and AI Dispatcher to keep the day moving when statuses change in real time.
Updating and cancelling visits
PUT /api/v1/visits/:id lets you reschedule, reassign, change duration, or push a status forward. Common updates include:
- Reassigning a visit to a different
teamId[]after a call-out. - Bumping
scheduledStart/scheduledEndwhen a prior visit overruns. - Setting
visitStatus: "cancelled"with a reason note when the customer cancels.
DELETE /api/v1/visits/:id permanently removes a visit. Use this for clearing test data or removing a duplicate that was created in error — it does not preserve audit history. For a customer-facing cancellation, prefer PUT with visitStatus: "cancelled" so the timeline keeps the record.
Endpoints
/api/v1/visitsPass your JWT API key as Authorization: Bearer <token>. Send
alongside X-API-Key: <token> for maximum compatibility.
In: header
Response Body
application/json
application/json
curl -X GET "https://api.fieldcamp.ai/api/v1/visits"{
"success": true,
"data": {
"visits": [
{
"jobId": "string",
"scheduledStart": "2019-08-24T14:15:22Z",
"scheduledEnd": "2019-08-24T14:15:22Z",
"technicianId": "string",
"id": "string",
"visitStatus": "scheduled",
"createdAt": "2019-08-24T14:15:22Z"
}
]
}
}{
"success": false,
"error": "Invalid or expired token"
}/api/v1/visitsPass your JWT API key as Authorization: Bearer <token>. Send
alongside X-API-Key: <token> for maximum compatibility.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
curl -X POST "https://api.fieldcamp.ai/api/v1/visits" \ -H "Content-Type: application/json" \ -d '{ "jobId": "job_abc123", "scheduledStart": "2026-05-01T09:00:00.000Z", "scheduledEnd": "2026-05-01T17:00:00.000Z", "technicianId": "user_xyz789" }'{
"success": true,
"data": {
"visitId": "string",
"visit": {
"jobId": "string",
"scheduledStart": "2019-08-24T14:15:22Z",
"scheduledEnd": "2019-08-24T14:15:22Z",
"technicianId": "string",
"id": "string",
"visitStatus": "scheduled",
"createdAt": "2019-08-24T14:15:22Z"
}
}
}{
"success": false,
"error": "jobNumber is required"
}{
"success": false,
"error": "Invalid or expired token"
}/api/v1/visits/{id}Pass your JWT API key as Authorization: Bearer <token>. Send
alongside X-API-Key: <token> for maximum compatibility.
In: header
Path Parameters
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Any subset of NewVisit fields.
Response Body
application/json
application/json
curl -X PUT "https://api.fieldcamp.ai/api/v1/visits/string" \ -H "Content-Type: application/json" \ -d '{}'{
"success": true,
"data": {
"visit": {
"jobId": "string",
"scheduledStart": "2019-08-24T14:15:22Z",
"scheduledEnd": "2019-08-24T14:15:22Z",
"technicianId": "string",
"id": "string",
"visitStatus": "scheduled",
"createdAt": "2019-08-24T14:15:22Z"
}
}
}{
"success": false,
"error": "Invalid or expired token"
}/api/v1/visits/{id}Pass your JWT API key as Authorization: Bearer <token>. Send
alongside X-API-Key: <token> for maximum compatibility.
In: header
Path Parameters
Response Body
application/json
curl -X DELETE "https://api.fieldcamp.ai/api/v1/visits/string"{
"success": false,
"error": "Invalid or expired token"
}Best practices
Visit creation is a write-heavy endpoint. Send an Idempotency-Key header so retries don't double-book the crew.
Listen for visit.status_changed to update your own systems the moment a tech arrives or completes work.
Batch reads with broad filters rather than polling per visit. The API rewards smart queries.
409 Conflict on a teamId collision means the team is already booked — re-fetch and offer the dispatcher an alternate slot.
Troubleshooting
Why am I getting 400: technicianId is not allowed?
The legacy technicianId field was replaced by teamId[]. Convert single IDs to a one-element array.
My visit was created but doesn't appear on the calendar.
Check visitStatus. If it's unscheduled or anyTime: true, it lives on the unscheduled queue until a slot is set. The dispatch calendar has a separate panel for these.
Can I move a completed visit back to in_progress?
Only by re-opening it from the FieldCamp UI. The API will reject the direct status transition to protect reporting integrity.
How do I assign a two-person crew?
Pass both user IDs in teamId[] on the same visit. Do not create two visits — that double-counts the work in capacity planning.
Does deleting a visit delete the job? No. Visits and jobs are separate resources. Deleting all visits on a job leaves the job in place; FieldCamp will prompt a dispatcher to add a new visit or close the job.
FAQs
What's the difference between scheduled and arrived?
scheduled means a time slot exists. arrived means the technician is physically on-site (confirmed via GPS or a manual mobile action) but hasn't started work yet.
Can the AI Dispatcher create visits on my behalf? Yes. When you submit a job for AI dispatch, it creates the visit, assigns the team, and sets the status — all via the same v1 endpoints.
Are unscheduled visits counted against capacity?
Only after they are assigned and a scheduledStart is set. Until then they live in the backlog and don't reserve a slot.
Related articles
- Jobs | FieldCamp API
- Team | FieldCamp API
- Webhooks | FieldCamp API
- Rate limits | FieldCamp API
- Idempotency | FieldCamp API
- Errors | FieldCamp API
- Job and visit statuses in FieldCamp
- How to use the dispatch calendar
- Route optimization in FieldCamp
- How AI Dispatcher works under the hood
- Multi-day jobs overview
- Submitting a job for AI dispatch