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.
  • Geocoded address fields on every client: latitude, longitude, and plusCode, populated automatically when an address is provided.
  • 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. Pass the same Address object to both if they are identical; do not skip billingAddress.
  • 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 returned read-only on every client whose address could be geocoded. They are the same coordinates the dispatcher uses for route optimization and AI dispatching.

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 greater than or equal to the supplied value. Ideal for delta syncs.
  • limit and cursor — standard pagination parameters used across the API.

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. 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.

To restore a soft-deleted client, update the record through PUT /api/v1/clients/{id} — the endpoint accepts updates against archived records and clears the deleted flag on success.

Creating clients safely

Search first

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

Send an Idempotency-Key

Pass a unique Idempotency-Key header on POST /api/v1/clients so retries do not create duplicates. See Idempotency.

Always include billingAddress

Even if the billing and property addresses are identical, post the same Address object to both. Missing billingAddress will fail validation.

Trust the geocode

The response will include latitude, longitude, and plusCode if the address could be resolved. Store them — they drive route optimization for the jobs you create later.

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

AuthorizationBearer <token>

Pass 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

application/json

curl -X GET "https://api.fieldcamp.ai/api/v1/clients"
{
  "success": true,
  "data": {
    "clients": [
      {
        "firstName": "Jane",
        "lastName": "Cooper",
        "email": "jane@example.com",
        "phoneNumber": {
          "countryCode": "+44",
          "number": "7555123456",
          "countryIdentifier": "gb"
        },
        "companyName": "Acme Moving Co.",
        "companyAddress": {
          "street": "221B Baker Street",
          "houseNumber": "",
          "city": "London",
          "state": "England",
          "country": "United Kingdom",
          "zipCode": "NW1 6XE",
          "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
          "latitude": 51.5237,
          "longitude": -0.1585,
          "plusCode": ""
        },
        "propertyAddress": {
          "street": "221B Baker Street",
          "houseNumber": "",
          "city": "London",
          "state": "England",
          "country": "United Kingdom",
          "zipCode": "NW1 6XE",
          "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
          "latitude": 51.5237,
          "longitude": -0.1585,
          "plusCode": ""
        },
        "billingAddress": {
          "street": "221B Baker Street",
          "houseNumber": "",
          "city": "London",
          "state": "England",
          "country": "United Kingdom",
          "zipCode": "NW1 6XE",
          "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
          "latitude": 51.5237,
          "longitude": -0.1585,
          "plusCode": ""
        },
        "stage": "Active Client",
        "properties": [
          "string"
        ],
        "jobFormIds": [
          "string"
        ],
        "id": "string",
        "recordId": "string",
        "createdAt": "2019-08-24T14:15:22Z",
        "updatedAt": "2019-08-24T14:15:22Z"
      }
    ]
  }
}
{
  "success": false,
  "error": "Invalid or expired token"
}
{
  "success": false,
  "error": "Internal server error"
}
POST/api/v1/clients

Authorization

AuthorizationBearer <token>

Pass 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

application/json

curl -X POST "https://api.fieldcamp.ai/api/v1/clients" \  -H "Content-Type: application/json" \  -d '{    "firstName": "Jane",    "lastName": "Cooper",    "email": "jane@example.com",    "phoneNumber": {      "countryCode": "+1",      "number": "5550101234",      "countryIdentifier": "us"    },    "companyName": "Acme Moving Co.",    "propertyAddress": {      "street": "500 Example Ave",      "city": "Springfield",      "state": "IL",      "country": "United States",      "zipCode": "62701"    },    "billingAddress": {      "street": "500 Example Ave",      "city": "Springfield",      "state": "IL",      "country": "United States",      "zipCode": "62701"    },    "stage": "Active Client",    "properties": [],    "jobFormIds": []  }'
{
  "success": true,
  "data": {
    "client": {
      "firstName": "Jane",
      "lastName": "Cooper",
      "email": "jane@example.com",
      "phoneNumber": {
        "countryCode": "+44",
        "number": "7555123456",
        "countryIdentifier": "gb"
      },
      "companyName": "Acme Moving Co.",
      "companyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "propertyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "billingAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "stage": "Active Client",
      "properties": [
        "string"
      ],
      "jobFormIds": [
        "string"
      ],
      "id": "string",
      "recordId": "string",
      "createdAt": "2019-08-24T14:15:22Z",
      "updatedAt": "2019-08-24T14:15:22Z"
    }
  }
}
{
  "success": false,
  "error": "jobNumber is required"
}
{
  "success": false,
  "error": "Invalid or expired token"
}
{
  "success": false,
  "error": "Internal server error"
}
GET/api/v1/clients/{id}

Authorization

AuthorizationBearer <token>

Pass your JWT API key as Authorization: Bearer <token>. Send alongside X-API-Key: <token> for maximum compatibility.

In: header

Path Parameters

id*string

Response Body

application/json

application/json

curl -X GET "https://api.fieldcamp.ai/api/v1/clients/string"
{
  "success": true,
  "data": {
    "client": {
      "firstName": "Jane",
      "lastName": "Cooper",
      "email": "jane@example.com",
      "phoneNumber": {
        "countryCode": "+44",
        "number": "7555123456",
        "countryIdentifier": "gb"
      },
      "companyName": "Acme Moving Co.",
      "companyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "propertyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "billingAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "stage": "Active Client",
      "properties": [
        "string"
      ],
      "jobFormIds": [
        "string"
      ],
      "id": "string",
      "recordId": "string",
      "createdAt": "2019-08-24T14:15:22Z",
      "updatedAt": "2019-08-24T14:15:22Z"
    }
  }
}
{
  "success": false,
  "error": "Invalid or expired token"
}
Empty
PUT/api/v1/clients/{id}

Authorization

AuthorizationBearer <token>

Pass your JWT API key as Authorization: Bearer <token>. Send alongside X-API-Key: <token> for maximum compatibility.

In: header

Path Parameters

id*string

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

curl -X PUT "https://api.fieldcamp.ai/api/v1/clients/string" \  -H "Content-Type: application/json" \  -d '{    "stage": "Active Client",    "properties": [      "string"    ],    "jobFormIds": [      "string"    ]  }'
{
  "success": true,
  "data": {
    "client": {
      "firstName": "Jane",
      "lastName": "Cooper",
      "email": "jane@example.com",
      "phoneNumber": {
        "countryCode": "+44",
        "number": "7555123456",
        "countryIdentifier": "gb"
      },
      "companyName": "Acme Moving Co.",
      "companyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "propertyAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "billingAddress": {
        "street": "221B Baker Street",
        "houseNumber": "",
        "city": "London",
        "state": "England",
        "country": "United Kingdom",
        "zipCode": "NW1 6XE",
        "formattedAddress": "221B Baker Street, London, England, NW1 6XE, United Kingdom",
        "latitude": 51.5237,
        "longitude": -0.1585,
        "plusCode": ""
      },
      "stage": "Active Client",
      "properties": [
        "string"
      ],
      "jobFormIds": [
        "string"
      ],
      "id": "string",
      "recordId": "string",
      "createdAt": "2019-08-24T14:15:22Z",
      "updatedAt": "2019-08-24T14:15:22Z"
    }
  }
}
{
  "success": false,
  "error": "Invalid or expired token"
}
Empty
DELETE/api/v1/clients/{id}

Authorization

AuthorizationBearer <token>

Pass your JWT API key as Authorization: Bearer <token>. Send alongside X-API-Key: <token> for maximum compatibility.

In: header

Path Parameters

id*string

Response Body

application/json

curl -X DELETE "https://api.fieldcamp.ai/api/v1/clients/string"
Empty
{
  "success": false,
  "error": "Invalid or expired token"
}
Empty

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.

Geocode fields are missing on a freshly created client. Geocoding runs asynchronously. The POST response returns the record before the geocode resolves. Re-fetch the client a few seconds later, or subscribe to the client.updated webhook and wait for latitude and longitude to populate.

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