FieldCamp
Resources

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.

Where a visit comes from

FieldCamp generates the visit from the job's time window when you POST /api/v1/jobs. You then shape it with PATCH /api/v1/visits/{id}, which is how you get each of these:

  • Explicit start and end — set visitStartDateTime / visitEndDateTime instead of leaving the job's window.
  • A different team than the parent job — set teamId on the visit, e.g. the estimator goes first and the install crew follows.
  • An anytime visit — set anyTime: true so it is flexible within the day.
  • A different serviceDuration — useful when one stop is long (an install) and another short (a follow-up walkthrough).

Jobs that need more than one visit — a two-stop install, or a return trip for a backordered part — are created as multi-visit or recurring jobs, and FieldCamp generates the series. The API does not add visits to an existing job one at a time.

The AI Dispatcher optimizes the assignment using skills, capacity and travel time on the visits FieldCamp generated, so you keep that intelligence without creating visits yourself.

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.

visitStatusMeaning
scheduledAssigned to a team with a fixed start and end time.
unscheduledCreated without a time slot — sits on the unscheduled queue.
in_transitTechnician is driving to the site (GPS confirmed).
arrivedTechnician has reached the location but hasn't started work.
in_progressWork is actively being performed.
pausedWork was started then paused (lunch, awaiting a part, customer not ready).
completedThe visit has been finished and submitted from the field.
cancelledThe visit was cancelled before completion.

Status transitions are driven by the mobile app, dispatch actions, and workflow automations. Over the API you write a status with PATCH /api/v1/visits/{id}, and the API validates the value itself — anything outside the eight statuses returns 400. It does not, however, police the ORDER: the API will accept completedscheduled even though the product flow would not, so if your integration depends on a one-way progression, enforce that on your side.

See Job and visit statuses for the parent-job mapping of these states.

Fields

  • jobId — the parent job. Read-only on this resource: it is set when the visit is generated with its job, and PATCH cannot move a visit to a different job.
  • visitStartDateTime / visitEndDateTime — UTC ISO-8601 datetimes. These are the field names the API uses; scheduledStart / scheduledEnd are not accepted and return 400.
  • teamId — the assignees for this visit. The Visits API accepts multiple assignees, so a two-person crew is a single visit, not two. Replaces the old single technicianId field. Get the ids from GET /api/teams — see Team.
  • serviceDuration — the planned on-site duration. Lets the dispatcher fit the visit into the team's capacity even when the end time is not yet pinned.
  • anyTime — boolean. Marks the visit as flexible-within-day (no fixed hour). The dispatcher slots it into the first open window.
  • notes, priority — free-text note and priority label for this visit.
  • visitStatus — one of the eight values in the lifecycle table above.

An unscheduled visit is expressed by its visitStatus of unscheduled, not by a separate unscheduled boolean — there is no such field, and sending one returns 400. Create the job with scheduleLater: true to get one.

The eight fields above are the complete PATCH contract. Any other key — including scheduledStart, scheduledEnd, unscheduled, jobId or technicianId — returns 400 naming the offending key. The API never accepts and silently discards a field.

How visits get created

There is no POST on this resource

The Visits API is read-and-update only: GET /api/v1/visits, GET /api/v1/visits/{id} and PATCH /api/v1/visits/{id}. There is no POST /api/v1/visits, so a visit cannot be created directly over the API.

Visits come into existence with their job:

Create the job

POST /api/v1/jobs with startDateTime and endDateTime. FieldCamp generates the visit for that window as part of creating the job — see the Jobs resource.

Or create it without a time

Send scheduleLater: true and the job starts on draft with an unscheduled visit for a dispatcher to place.

Then read it back

GET /api/v1/jobs/{id} returns the job's visits array with each id and visitStatus, or list them with GET /api/v1/visits?jobId={jobId}. Use that id for every subsequent PATCH.

Assign and reschedule with PATCH

PATCH /api/v1/visits/{id} is where you set teamId, move visitStartDateTime / visitEndDateTime, or push visitStatus forward.

Confirm on the dispatch calendar

Visits surface immediately on the dispatch calendar. Live tracking turns on automatically once a team member starts the visit on mobile.

Multi-visit and recurring jobs generate their own visit series from the job's schedule rather than from individual API calls.

Listing and filtering visits

GET /api/v1/visits accepts these query parameters:

  • page — page number, 1-based. Defaults to 1.
  • limit — rows per page. Defaults to 30; values above 100 are not rejected — they are silently clamped to 100 (you still get 200 with 100 rows). Read meta.total for the real count and page through the rest.
  • sortfield:direction, e.g. sort=visitStartDateTime:asc. Direction defaults to desc. Sortable fields: createdAt, updatedAt, visitStartDateTime, visitEndDateTime, visitStatus. Any other field returns 400.
  • jobId — return only the visits belonging to one job.
  • visitStatusone status, matched case-insensitively. A value that is not a visit status returns 400 with the valid list. Comma-separated lists are not supported — send one request per status.
  • startDate / endDate — bound visitStartDateTime. Both are ISO-8601; a value that is not a date returns 400. Send them with a timezone (…Z or an offset), or add the timezone param for zone-less values (otherwise read as UTC).
  • since — rows whose updatedAt is at or after this ISO-8601 timestamp, for incremental sync. Not a date returns 400. Same timezone rule as above; for a reliable delta sync, pair it with sort=updatedAt:asc and use the last row's updatedAt as your next since.
  • timezone — IANA timezone name (e.g. America/Denver) used to interpret zone-less since, startDate and endDate values.

There is no teamId filter on this endpoint and no from / to aliases. Common patterns:

  • Backlog for a dispatcher?visitStatus=unscheduled.
  • One job's visits?jobId=....
  • A given day?startDate=2026-05-29T00:00:00Z&endDate=2026-05-29T23:59:59Z.
  • What changed since your last sync?since={last_updated_at}&sort=updatedAt:asc. See Incremental sync for the full recipe.

The response carries the rows in data and the pagination state in meta:

{ "success": true, "data": [ /* … visits … */ ], "meta": { "page": 1, "limit": 30, "total": 204270 } }

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

PATCH /api/v1/visits/{id} is the update verb — there is no PUT and no DELETE on this resource. It accepts exactly these eight fields, and any other key returns 400 naming it:

  • visitStartDateTime / visitEndDateTime — ISO-8601 timestamps.
  • visitStatus — one of the statuses above, matched case-insensitively and stored canonically.
  • teamId — the assignees for this visit.
  • notes
  • priority
  • anyTime — boolean; the visit is not time-boxed.
  • serviceDuration

Common updates:

  • Reassigning after a call-out — send a new teamId.
  • Bumping visitStartDateTime / visitEndDateTime when a prior visit overruns.
  • Cancelling — visitStatus: "cancelled". Because there is no DELETE, this is also how you retire a visit created in error; the timeline keeps the record either way.

Two behaviours worth knowing:

  • Moving a visit from unscheduled to scheduled clears the job's scheduleLater flag for you.
  • Setting visitStatus: "completed" stamps actualEndTime server-side with the current time — you do not send it.

A status change rolls up to the job

When you change visitStatus, FieldCamp re-derives the parent job's status from all of that job's active visits, mapped through your own job pipeline. So completing the last outstanding visit can move the job forward on its own, and a job.status_changed webhook fires when it does. If nothing sensible resolves for your pipeline the job is left untouched rather than forced onto a guess.

Endpoints

GET/api/v1/visits

Authorization

BearerAuth
AuthorizationBearer <token>

Your FieldCamp API key (starts with fc_live_). Send it as Authorization: Bearer fc_live_… — or as the X-Api-Key: fc_live_… header. Create a key in Settings → API. This is an API key, not a login JWT.

In: header

Query Parameters

page?integer

Page number, 1-based.

limit?integer

Rows per page. Values above 100 are clamped to 100.

since?string

Return only rows whose updatedAt is at or after this ISO-8601 timestamp — the incremental-sync cursor. A value that is not a date returns 400 rather than being ignored. Include a timezone (…Z or an offset), or pass the timezone param for a zone-less value; otherwise it is read as UTC.

timezone?string

IANA timezone name (e.g. America/Denver) used to interpret zone-less since, startDate and endDate values. Ignored for a value that already carries a Z or an offset.

sort?string

Order the result as field:direction. Direction is asc or desc (default desc). Sortable fields: createdAt, updatedAt, visitStartDateTime, visitEndDateTime, visitStatus. An unsupported field returns 400.

jobId?string

Return only visits belonging to this job.

visitStatus?string

Filter by visit status, matched case-insensitively. One of: scheduled, unscheduled, in_transit, arrived, in_progress, paused, completed, cancelled. Anything else returns 400.

startDate?string

Only visits starting at or after this ISO-8601 timestamp. A value that is not a date returns 400.

endDate?string

Only visits starting at or before this ISO-8601 timestamp. A value that is not a date returns 400.

Response Body

application/json

application/json

application/json

curl -X GET "https://example.com/api/v1/visits"
{}
GET/api/v1/visits/{id}

Authorization

BearerAuth
AuthorizationBearer <token>

Your FieldCamp API key (starts with fc_live_). Send it as Authorization: Bearer fc_live_… — or as the X-Api-Key: fc_live_… header. Create a key in Settings → API. This is an API key, not a login JWT.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/visits/665f1c2ab3e4d5f6a7b8c9d0"
{}
PATCH/api/v1/visits/{id}

Authorization

BearerAuth
AuthorizationBearer <token>

Your FieldCamp API key (starts with fc_live_). Send it as Authorization: Bearer fc_live_… — or as the X-Api-Key: fc_live_… header. Create a key in Settings → API. This is an API key, not a login JWT.

In: header

Path Parameters

id*string

Request Body

application/json

Send only the fields you are changing. Any key outside this list returns 400 with the offending key named — this API never accepts and silently discards a field. There is no POST or DELETE on this resource: visits are created with their job, and a visit is retired by setting visitStatus to cancelled.

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PATCH "https://example.com/api/v1/visits/665f1c2ab3e4d5f6a7b8c9d0" \  -H "Content-Type: application/json" \  -d '{}'
{}

Best practices

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 assign visits on my behalf? Yes. When you submit a job for AI dispatch, it works on the visits the job generated — assigning the team and moving the status for you.

Are unscheduled visits counted against capacity? Only after they are assigned and a visitStartDateTime is set. Until then they live in the backlog and don't reserve a slot. Move one out of the backlog with PATCH /api/v1/visits/{id} — setting visitStatus from unscheduled to scheduled also clears the job's scheduleLater flag for you.

On this page