FieldCamp
Resources

Clients | FieldCamp API

Use the FieldCamp Clients API to create, search, update, soft-delete, and sync the people and companies you do work for.

The FieldCamp Clients API is how your integration creates and maintains the people or companies you do work for. Every job, estimate, invoice, and visit eventually references a client, so most integrations create a client once on first contact and then reuse the same client ID on every follow-up. With release_v2, the resource is available under the versioned path /api/v1/clients, which adds server-side search, stage, and since filters, a soft-delete endpoint, and geocoded address fields.

What changed in release_v2

The /api/v1/clients namespace is now the recommended path for new integrations. The legacy /api/clients endpoints still exist for backward compatibility, but they do not expose the new filters or the geocoded address fields, and we strongly recommend migrating.

  • Versioned base path: /api/v1/clients (instead of /api/clients).
  • Server-side filters on the list endpoint: search, stage, and since.
  • New DELETE /api/v1/clients/{id} soft-delete endpoint that hides the client from the UI without losing history.
  • Address coordinates: latitude, longitude and plusCode are sub-fields inside each Address object, and you supply them — there is no server-side geocoding on this endpoint.
  • Custom properties on both create and update, via properties.
  • Consistent stage vocabulary aligned with client categories and stages used in the FieldCamp UI.

If you previously built your own deduplication loop on top of GET /api/clients, you can now collapse it into a single GET /api/v1/clients?search=jane@acme.com call. See Idempotency for safe-retry patterns.

Fields worth knowing

  • email is still the de-facto lookup key. Always store it. With release_v2 you can pass it to the search query parameter instead of paginating client-side.
  • phoneNumber is structured — { countryCode, number, countryIdentifier }. See the PhoneNumber block in the NewClient schema rendered below.
  • billingAddress and propertyAddress are independent, and both are optional. Pass the same Address object to both if they are identical. The only required-field rule on create is that you send either firstName or email — everything else, including both addresses, can be omitted.
  • properties carries your account's custom client fields. Send an array of { id, value } (extra keys like name or type are tolerated and ignored) or an object keyed by field id. Discover the ids with GET /api/v1/objects/client/schema — see Custom Objects. On PUT the merge is partial by field id, so properties you do not mention are preserved; send value: null to clear one.
  • stage is free-text but commonly one of Lead, Active Client, or Inactive. The stage query parameter on the list endpoint matches the same vocabulary you configure in the sales pipeline.
  • latitude / longitude / plusCode are optional sub-fields of an Address object — not top-level client fields, and not filled in for you. Supply them when you have them: they are the coordinates the dispatcher uses for route optimization and AI dispatching, so an address without them still geocodes at dispatch time rather than at create time.

Authentication

All requests to /api/v1/clients require a valid fc_live key passed via the X-Api-Key header with appropriate scopes — see Authentication for setup, and API key management to create and rotate keys per integration.

GET /api/v1/clients accepts the following query parameters:

  • search — free-text match against client name, email, and phone number. Case-insensitive, partial-match.
  • stage — exact match against the client's stage field (for example Lead, Active Client, Inactive).
  • since — ISO 8601 timestamp. Returns clients whose updatedAt is at or after the supplied value. Send it with a timezone (…Z or an offset), or add the timezone param for a zone-less value (otherwise it's read as UTC). For a reliable delta sync, pair it with sort=updatedAt:asc and use the last row's updatedAt as your next since.
  • sort — order as field:direction (default desc), e.g. sort=updatedAt:asc. Sortable fields: createdAt, updatedAt, firstName, lastName, companyName, stage.
  • timezone — IANA timezone name (e.g. America/Denver) used to interpret a zone-less since.
  • page and limit — offset pagination. page is 1-based (default 1); limit defaults to 30 and is capped at 100. A larger value like limit=1000 is not rejected — it is silently clamped to 100, so you still get 200 with only 100 rows. Read meta.total for the real count and page through the rest with page.

Use since for nightly syncs into your data warehouse. Keep the highest updatedAt you have seen and pass it back on the next run — you only pull the records that actually changed. See Incremental sync for the full recipe. The same pattern powers the calendar and accounting sync API.

Example: find by email

curl https://api.fieldcamp.ai/api/v1/clients?search=jane@acme.com \
  -H "Authorization: Bearer $FIELDCAMP_API_KEY"

Example: pull leads only

curl "https://api.fieldcamp.ai/api/v1/clients?stage=Lead&limit=100" \
  -H "Authorization: Bearer $FIELDCAMP_API_KEY"

Example: incremental sync

curl "https://api.fieldcamp.ai/api/v1/clients?since=2026-05-01T00:00:00Z" \
  -H "Authorization: Bearer $FIELDCAMP_API_KEY"

If you exceed the per-minute request budget, you will receive an HTTP 429 with throttling headers — see Rate limits for the sliding-window details and back-off guidance.

Soft delete

DELETE /api/v1/clients/{id} performs a soft delete. The client is hidden from the FieldCamp UI and excluded from default list responses, but their historical jobs, invoices, and payments remain intact. This mirrors the in-app delete or archive a client flow.

Soft-deleted clients are excluded from GET /api/v1/clients by default. If you maintain a mirror of the table in your own system, listen for the client.deleted webhook so you can mark the row inactive on your side too. See Webhooks and the webhook events catalog for payload shapes.

There is no restore over the API. PUT /api/v1/clients/{id} returns 404 NOT_FOUND once a client is soft-deleted, and isDeleted, deletedAt and status are outside the update contract — sending any of them returns 400. Restore the client from the app, or ask us if you need it done in bulk.

Creating clients safely

Search first

Call GET /api/v1/clients?search=<email> before creating. If you get a hit, reuse the existing client ID.

De-duplicate on your side

No idempotency header is read on this endpoint today, and email has no unique index — a retried POST creates a second client. So the search above is your de-duplication, and it is worth storing the FieldCamp id against your own record so a retry updates rather than re-creates. See Idempotency for the patterns we recommend until server-side keys ship.

Send the addresses you have

Both billingAddress and propertyAddress are optional and independent — post the same Address object to both when they are identical. Include latitude / longitude inside the address if you already hold them.

Send your custom fields too

Read the field ids once from GET /api/v1/objects/client/schema and pass them as properties. A create that skips them lands a client your team then has to complete by hand.

If you are wiring this in for the first time, the API quickstart walks through the full flow from issuing an fc_live key to scheduling a job.

Endpoints

GET/api/v1/clients

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 a since that has no timezone of its own. Ignored when since 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, firstName, lastName, companyName, stage. An unsupported field returns 400.

search?string

Free-text match on name, email, phone and company name.

stage?string

Filter by client stage, matched case-insensitively. Valid values are your account's pipeline stage keys — read them from GET /api/v1/objects/client/schema.

Response Body

application/json

application/json

application/json

curl -X GET "https://example.com/api/v1/clients"
{}
POST/api/v1/clients

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

Request Body

application/json

Any key outside this list returns 400 with the offending key named — this API never accepts and silently discards a field.

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/clients" \  -H "Content-Type: application/json" \  -d '{}'
{}
GET/api/v1/clients/{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/clients/665f1c2ab3e4d5f6a7b8c9d0"
{}
PUT/api/v1/clients/{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

Any key outside this list returns 400 with the offending key named — this API never accepts and silently discards a field.

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PUT "https://example.com/api/v1/clients/665f1c2ab3e4d5f6a7b8c9d0" \  -H "Content-Type: application/json" \  -d '{}'
{}
DELETE/api/v1/clients/{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 DELETE "https://example.com/api/v1/clients/665f1c2ab3e4d5f6a7b8c9d0"
{}

Troubleshooting

search returns no rows but the client clearly exists. The search filter excludes soft-deleted clients by default. Try the same call without search and look for the record; if you find it via direct GET /api/v1/clients/{id}, it has likely been archived through the client detail page actions menu.

Coordinates are missing on a freshly created client. They are not filled in for you — latitude, longitude and plusCode live inside the Address object you send, so a client created without them simply has none. Include them in the address on create, or update the client later with a complete address.

stage filter returns nothing. The match is exact and case-sensitive on the canonical value. Confirm the stage string you are passing matches the value configured in your pipeline — see client categories and stages for the defaults.

I am getting 4xx responses I do not expect. Check the response envelope and HTTP status against the errors and retries reference before adding new logic — most transient failures have a documented retry pattern.

I am still hitting the old /api/clients paths. The legacy paths are kept for backward compatibility and will continue to work, but they do not return geocoded fields and do not accept search, stage, or since. Migrate to /api/v1/clients to unlock those features and to keep up with future schema additions documented in the changelog.

FAQs

Can I hard-delete a client? No. DELETE /api/v1/clients/{id} is always a soft delete. This is intentional — clients are referenced by historical jobs and invoices, and hard deletion would break those records.

Is there a bulk import endpoint? Not yet. For initial migrations, use the in-app CSV import for very large lists, or loop POST /api/v1/clients with an Idempotency-Key per row for smaller batches.

How do I model leads versus active customers? Set the stage field. The same vocabulary powers the sales pipeline kanban board so your CRM stays consistent across API and UI.

Why is the email field still optional? Some field-service customers (especially walk-in or referral work) only have a phone number. Email remains optional, but if you have it, send it — search uses it as the primary match key.

How do I keep my CRM in sync without polling every minute? Combine the since filter for delta pulls with webhooks for real-time client.created, client.updated, and client.deleted events.

On this page