Customers

Manage customer information and relationships

Overview

Customers are the core entity representing clients who receive pool services. They can be residential or commercial, and include contact information, service addresses, and billing details.

List Customers (Cursor-Based)

Retrieve a cursor-based paginated list of customers with optional filtering and search. Maximum of 100 records per request. Uses a selector argument that wraps both filters and search.

query InfiniteCustomers(
  $selector: CustomersSelector
  $sort: [CustomersSort]
  $first: Int
  $after: String
  $last: Int
  $before: String
) {
  infiniteCustomers(
    selector: $selector
    sort: $sort
    first: $first
    after: $after
    last: $last
    before: $before
  ) {
    edges {
      node {
        id
        firstName
        lastName
        email
        phoneNumber
        status
        streetAddress
        city
        state
        tags
        isCommercial
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}

# Variables
{
  "first": 20,
  "selector": {
    "search": "Smith",
    "filters": {
      "status": { "eq": "ACTIVE" }
    }
  }
}

Response:

{
  "data": {
    "infiniteCustomers": {
      "edges": [
        {
          "node": {
            "id": "cust_456",
            "firstName": "John",
            "lastName": "Smith",
            "email": "john@example.com",
            "phoneNumber": "555-0123",
            "status": "ACTIVE",
            "streetAddress": "123 Main St",
            "city": "Austin",
            "state": "TX",
            "tags": ["Premium"],
            "isCommercial": false
          },
          "cursor": "eyJpZCI6ImN1c3RfNDU2In0="
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "endCursor": "eyJpZCI6ImN1c3RfNDU2In0="
      },
      "totalCount": 150
    }
  }
}

List Customers (Offset-Based)

Retrieve an offset-based paginated list of customers. Maximum of 100 records per request. Uses limit and offset for pagination.

query PaginatedCustomers(
  $selector: CustomersSelector
  $sort: [CustomersSort]
  $limit: Int
  $offset: Int
) {
  paginatedCustomers(
    selector: $selector
    sort: $sort
    limit: $limit
    offset: $offset
  ) {
    items {
      id
      firstName
      lastName
      email
      phoneNumber
      status
      streetAddress
      city
      state
    }
    totalCount
  }
}

# Variables
{
  "limit": 20,
  "offset": 0,
  "selector": {
    "filters": {
      "status": { "eq": "ACTIVE" },
      "zone": { "eq": "North" }
    }
  }
}

Response:

{
  "data": {
    "paginatedCustomers": {
      "items": [
        {
          "id": "cust_456",
          "firstName": "John",
          "lastName": "Smith",
          "email": "john@example.com",
          "phoneNumber": "555-0123",
          "status": "ACTIVE",
          "streetAddress": "123 Main St",
          "city": "Austin",
          "state": "TX"
        }
      ],
      "totalCount": 45
    }
  }
}

Get Single Customer

There is no dedicated single-customer query. To retrieve a single customer by ID, use infiniteCustomers with an id filter in the selector.

query GetCustomerById($selector: CustomersSelector) {
  infiniteCustomers(selector: $selector, first: 1) {
    edges {
      node {
        id
        firstName
        lastName
        email
        phoneNumber
        alternatePhoneNumber
        streetAddress
        city
        state
        zipCode
        billingAddress
        billingCity
        billingState
        billingZipCode
        status
        tags
        isCommercial
        notes
        salesNotes
        zone
        paymentMethod
        billingMethod
        billingFrequency
        quickbooksId
        stripeId
        customFields
      }
    }
  }
}

# Variables
{
  "selector": {
    "filters": {
      "id": { "eq": "cust_456" }
    }
  }
}

Response:

{
  "data": {
    "infiniteCustomers": {
      "edges": [
        {
          "node": {
            "id": "cust_456",
            "firstName": "John",
            "lastName": "Smith",
            "email": "john@example.com",
            "phoneNumber": "555-0123",
            "alternatePhoneNumber": "555-0124",
            "streetAddress": "123 Main St",
            "city": "Austin",
            "state": "TX",
            "zipCode": "78701",
            "billingAddress": null,
            "billingCity": null,
            "billingState": null,
            "billingZipCode": null,
            "status": "ACTIVE",
            "tags": ["Premium"],
            "isCommercial": false,
            "notes": "Prefers morning appointments",
            "salesNotes": null,
            "zone": "North",
            "paymentMethod": "CREDIT_CARD",
            "billingMethod": "EMAIL",
            "billingFrequency": "MONTHLY",
            "quickbooksId": "QB-12345",
            "stripeId": "cus_abc123",
            "customFields": null
          }
        }
      ]
    }
  }
}

Customer Lookup (Autocomplete)

Search for customers by name or other fields using the autocomplete lookup query. Useful for search-as-you-type interfaces.

query InfiniteCustomerLookup(
  $query: String!
  $first: Int
  $after: String
) {
  infiniteCustomerLookup(
    query: $query
    first: $first
    after: $after
  ) {
    edges {
      node {
        id
        firstName
        lastName
        streetAddress
        city
        state
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

# Variables
{
  "query": "Smi",
  "first": 10
}

Response:

{
  "data": {
    "infiniteCustomerLookup": {
      "edges": [
        {
          "node": {
            "id": "cust_456",
            "firstName": "John",
            "lastName": "Smith",
            "streetAddress": "123 Main St",
            "city": "Austin",
            "state": "TX"
          },
          "cursor": "eyJpZCI6ImN1c3RfNDU2In0="
        },
        {
          "node": {
            "id": "cust_789",
            "firstName": "Sarah",
            "lastName": "Smithson",
            "streetAddress": "789 Elm Dr",
            "city": "Dallas",
            "state": "TX"
          },
          "cursor": "eyJpZCI6ImN1c3RfNzg5In0="
        }
      ],
      "pageInfo": {
        "hasNextPage": false,
        "endCursor": "eyJpZCI6ImN1c3RfNzg5In0="
      }
    }
  }
}

Search

The search field within the CustomersSelector accepts a string (max 600 characters) and searches across: firstName, lastName, streetAddress, city, state, zipCode, phoneNumber, alternatePhoneNumber, tertiaryPhoneNumber, and fourthPhoneNumber. Multi-word searches will also attempt first + last name combinations.

query SearchCustomers($selector: CustomersSelector, $first: Int) {
  infiniteCustomers(selector: $selector, first: $first) {
    edges {
      node {
        id
        firstName
        lastName
        phoneNumber
        streetAddress
        city
        state
      }
    }
    totalCount
  }
}

# Variables
{
  "first": 20,
  "selector": {
    "search": "John Smith"
  }
}

Response:

{
  "data": {
    "infiniteCustomers": {
      "edges": [
        {
          "node": {
            "id": "cust_456",
            "firstName": "John",
            "lastName": "Smith",
            "phoneNumber": "555-0123",
            "streetAddress": "123 Main St",
            "city": "Austin",
            "state": "TX"
          }
        }
      ],
      "totalCount": 1
    }
  }
}

Create Customer

Create a new customer record.

mutation CreateCustomer($input: CreateCustomerInput!) {
  createCustomer(input: $input) {
    id
    firstName
    lastName
    email
    status
  }
}

# Variables
{
  "input": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "phoneNumber": "555-0200",
    "streetAddress": "456 Oak Ave",
    "city": "Austin",
    "state": "TX",
    "zipCode": "78702",
    "status": "ACTIVE",
    "tags": ["Residential"]
  }
}

Response:

{
  "data": {
    "createCustomer": {
      "id": "cust_457",
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@example.com",
      "status": "ACTIVE"
    }
  }
}

Update Customer

Update an existing customer.

mutation UpdateCustomer($id: ID!, $input: UpdateCustomerInput!) {
  updateCustomer(id: $id, input: $input) {
    id
    firstName
    lastName
    email
    phoneNumber
    status
  }
}

# Variables
{
  "id": "cust_456",
  "input": {
    "phoneNumber": "555-0999",
    "notes": "Updated contact preference: email only"
  }
}

Response:

{
  "data": {
    "updateCustomer": {
      "id": "cust_456",
      "firstName": "John",
      "lastName": "Smith",
      "email": "john@example.com",
      "phoneNumber": "555-0999",
      "status": "ACTIVE"
    }
  }
}

Update Customer Quoted Prices

Update service pricing (quoted prices) for a customer. Each quoted price associates a service type with a price. Pass the full list of quoted prices — existing entries will be replaced. Entries with a price of 0 or less are ignored. typeId is optional and refers to a service type ID.

mutation UpdateCustomerQuotedPrices(
  $customerId: ID!
  $input: UpdateCustomerQuotedPricesInput!
) {
  updateCustomerQuotedPrices(
    customerId: $customerId
    input: $input
  ) {
    id
    customerId
    typeId
    price
    serviceTypeDisplay
  }
}

# Variables
{
  "customerId": "cust_456",
  "input": {
    "quotedPrices": [
      { "typeId": "type_weekly", "price": 150.00 },
      { "typeId": "type_opening", "price": 350.00 },
      { "typeId": "type_closing", "price": 300.00 }
    ]
  }
}

Response:

{
  "data": {
    "updateCustomerQuotedPrices": [
      {
        "id": "1",
        "customerId": "cust_456",
        "typeId": "type_weekly",
        "price": 150.00,
        "serviceTypeDisplay": "Weekly Service"
      },
      {
        "id": "2",
        "customerId": "cust_456",
        "typeId": "type_opening",
        "price": 350.00,
        "serviceTypeDisplay": "Pool Opening"
      },
      {
        "id": "3",
        "customerId": "cust_456",
        "typeId": "type_closing",
        "price": 300.00,
        "serviceTypeDisplay": "Pool Closing"
      }
    ]
  }
}

Bulk & Export Operations

Creating, updating, deleting, exporting, emailing, or texting many customers at once is handled asynchronously through the Bulk Operations API. These jobs run in the background and report progress you can poll. See the Bulk Operations guide for the full workflow.

Field Reference

FieldTypeDescription
idID!Unique identifier
firstNameString(nullable)Customer's first name
lastNameString!Customer's last name
quickBooksNameString(nullable)Name used in QuickBooks
customerSinceDateDate(nullable)Date the customer relationship started
streetAddressString(nullable)Street address
cityString(nullable)City
stateString(nullable)State/Province
zipCodeString(nullable)Postal code
billingAddressString(nullable)Billing street address
latitudeNumber(nullable)Latitude coordinate
longitudeNumber(nullable)Longitude coordinate
phoneNumberString(nullable)Primary phone number
phoneNumberLabelString(nullable)Label for primary phone (e.g. Home, Work)
phoneNumberEnableSMSBoolean(nullable)Whether SMS is enabled for primary phone
alternatePhoneNumberString(nullable)Alternate phone number
alternatePhoneNumberLabelString(nullable)Label for alternate phone
alternatePhoneNumberEnableSMSBoolean(nullable)Whether SMS is enabled for alternate phone
tertiaryPhoneNumberString(nullable)Third phone number
tertiaryPhoneNumberLabelString(nullable)Label for third phone
tertiaryPhoneNumberEnableSMSBoolean(nullable)Whether SMS is enabled for third phone
fourthPhoneNumberString(nullable)Fourth phone number
fourthPhoneNumberLabelString(nullable)Label for fourth phone
fourthPhoneNumberEnableSMSBoolean(nullable)Whether SMS is enabled for fourth phone
emailString(nullable)Primary email address
alternateEmailString(nullable)Alternate email address
tertiaryEmailString(nullable)Third email address
fourthEmailString(nullable)Fourth email address
billingEmailString(nullable)Email for billing communications
billingCCEmailString(nullable)CC email for billing communications
statusString(nullable)Customer status (e.g. ACTIVE, INACTIVE, LEAD)
tags[String](nullable)Tags for categorization
paymentMethodString(nullable)Preferred payment method
billingMethodString(nullable)Billing method
contactMethodString(nullable)Preferred contact method
zoneString(nullable)Service zone
reportNotificationString(nullable)Report notification preference
paymentTypeString(nullable)Payment type
poolCustomerPoolEntity(nullable)Associated pool information
bodiesOfWater[BodyOfWaterEntity](nullable)Associated bodies of water
serviceCustomerServiceEntity(nullable)Service configuration
salesNotesString(nullable)Sales-related notes
notesString(nullable)Internal notes
readyForInvoiceBoolean(nullable)Whether customer is ready for invoicing
isCommercialBoolean(nullable)Whether this is a commercial customer
billingCityString(nullable)Billing city
billingStateString(nullable)Billing state
billingZipCodeString(nullable)Billing postal code
billingFirstNameString(nullable)Billing contact first name
billingLastNameString(nullable)Billing contact last name
useAddressForBillingBoolean(nullable)Whether to use service address for billing
billingFrequencyString(nullable)Billing frequency
stripeIdString(nullable)Stripe customer ID
quickbooksIdString(nullable)QuickBooks customer ID
quotedPrices[CustomerQuotedPriceEntity](nullable)Quoted prices for the customer
customFieldsJSON(nullable)Custom field values
nextScheduledAptAppointmentEntity(nullable)Next scheduled appointment

Filter Reference

Available fields for the CustomerFilter object within CustomersSelector.filters.

FieldTypeDescription
idIdFilter(nullable)Filter by customer ID
statusEnumFilter(nullable)Filter by status
isCommercialBooleanFilter(nullable)Filter by commercial flag
readyForInvoiceBooleanFilter(nullable)Filter by invoice readiness
zoneStringFilter(nullable)Filter by service zone
cityStringFilter(nullable)Filter by city
stateStringFilter(nullable)Filter by state
zipCodeStringFilter(nullable)Filter by postal code
tagsScalarListFilter(nullable)Filter by tags
customerSinceDateDateTimeFilter(nullable)Filter by customer since date
deletedBooleanFilter(nullable)Filter by deleted status (defaults to false)