Docs
API Reference

Customers

Manage customer records and look them up by phone or email

Customers are the people (or businesses) you do work for. Each customer record holds contact details, an optional business name, and a billing address. Customers are scoped per organisation.

The devices you've serviced for a customer are a separate subcollection managed through the Devices endpoints, not an embedded field on the customer.

Endpoints

  • GET/organisations/{orgId}/customers — List customers (paginated)
  • POST/organisations/{orgId}/customers — Create customer
  • GET/organisations/{orgId}/customers/lookup — Find by phone or email (exact match)
  • GET/organisations/{orgId}/customers/{customerId} — Get customer
  • PUT/organisations/{orgId}/customers/{customerId} — Update customer
  • DELETE/organisations/{orgId}/customers/{customerId} — Delete customer (default cascade=true)

List is ordered by firstName (customers created in the app have no createdAt and would be invisible if we ordered by that field). Delete cascades devices, jobs, and their calendar events. A cascade that would delete jobs requires both customers:delete and tickets:delete (403 insufficient_scope with requiredScopes: ["customers:delete","tickets:delete"] if either is missing). Outstanding form gates on any job return 409 FORM_GATE_DELETE. Pass ?cascade=false to refuse when jobs exist (409 CUSTOMER_HAS_TICKETS).

Customer-form workflow

The dedicated lookup endpoint runs an exact-match query server-side and is designed for customer-facing intake forms. Use it to find an existing record before booking a job, then create a job with the returned id. No composite Firestore index is required — phone and email are queried in parallel and deduplicated by document id.

Required scopes: customers:read / customers:write / customers:delete (plus tickets:delete when a cascade would delete jobs).

firstName is required on create. Everything else is optional. On update every field is optional — send only what you want to change. The list endpoint supports limit, startAfter (cursor), and search (matches name, email or business).

Machine-readable version: OpenAPI spec.

On this page