Admin Customer Management

Create, search, and manage customer records from the admin API.

For general conventions, see JSON-API Conventions. For authentication, see Authentication.


Prerequisites

  • A valid Bearer token with admin access
  • Organization context (?o={organization_id})

Customer vs. Customer Account

The platform distinguishes between two entities:

Entity Scope Purpose
Customer Per-organization A business record with contact info, bookings, and custom fields. Owned by a specific organization.
Customer Account Global (platform-wide) An end-user identity that can log in. Linked to Customer records across multiple organizations via account_id.

A single person may have multiple Customer records (one per organization they interact with), all linked to the same CustomerAccount.


Creating a Customer

POST /api/v1/customers?o={org_id}

{
  "data": {
    "type": "customers",
    "attributes": {
      "given_name": "Jane",
      "family_name": "Doe",
      "email": "jane@example.com",
      "mobile": "+49 170 1234567",
      "company": "Acme Corp",
      "locale": "de-DE"
    }
  }
}

Key Attributes

Attribute Type Description
given_name string First name
family_name string Last name
email string Email address
mobile string Mobile phone number
phone string Landline phone number
company string Company name
sex string Gender
birth_date string Date of birth (Y-m-d)
locale string Preferred language (defaults to organization locale)

Custom Fields

Organizations can define custom fields (type custom-fields) to collect additional data on customer records — member numbers, preferences, notes, etc. Each field value is stored as a custom-entry linked to the customer.

Reading Custom Field Data

There are two ways to retrieve custom field values on a customer.

Option 1 — custom_entry_map attribute (recommended for display)

This attribute is always returned on the customer resource. It is a map keyed by the custom field's UUID:

GET /api/v1/customers/{customer_id}?o={org_id}
{
  "data": {
    "type": "customers",
    "id": "{customer_uuid}",
    "attributes": {
      "given_name": "Jane",
      "custom_entry_map": {
        "{field_uuid_1}": {
          "value": "Gold",
          "label": "Membership Tier",
          "formatted_value": "Gold",
          "type": "select"
        },
        "{field_uuid_2}": {
          "value": "12345",
          "label": "Member Number",
          "formatted_value": "12345",
          "type": "text"
        }
      }
    }
  }
}
Key Type Description
value varies The raw stored value (see field type table below)
label string Human-readable field name
formatted_value string Display-ready value (e.g. option labels for select fields)
type string Field type (text, textarea, number, select, multiselect, checkbox, date)

Option 2 — include=custom_entries,custom_entries.field (full JSON resources)

Use this when you need the full custom-entries and custom-fields resource objects:

GET /api/v1/customers/{customer_id}?o={org_id}&include=custom_entries,custom_entries.field

Returns custom_entries as a JSON has-many relationship in the response, with field sideloaded in included.


Writing Custom Field Data

Send the custom_entry_map attribute inside a PATCH request on the customer. The backend performs an atomic upsert and delete: entries in the map are created or updated, and any entries for fields not present in the map are deleted.

PATCH /api/v1/customers/{customer_id}?o={org_id}

{
  "data": {
    "type": "customers",
    "id": "{customer_uuid}",
    "attributes": {
      "custom_entry_map": {
        "{field_uuid_1}": { "value": "Gold" },
        "{field_uuid_2}": { "value": "12345" }
      }
    }
  }
}

To clear a specific field, omit it from the map (alongside all fields you want to keep). To clear all custom fields, send an empty object:

"custom_entry_map": {}

Note: Always include all fields you want to retain in a single request. Any field UUID not present in the map will have its entry deleted.


Value Format by Field Type

Field type Expected value format Example
text string "Jane"
textarea string "Long text..."
number number or numeric string 42
select string (option key or label) "gold"
multiselect string[] (array of option keys) ["yoga", "pilates"]
checkbox boolean or "1"/"0" true
date string in Y-m-d format "1990-06-15"

File upload fields (file, image) are managed through a separate upload flow and cannot be set via custom_entry_map.


Full Example: Create Customer with Custom Fields

POST /api/v1/customers?o={org_id}

{
  "data": {
    "type": "customers",
    "attributes": {
      "given_name": "Jane",
      "family_name": "Doe",
      "email": "jane@example.com",
      "custom_entry_map": {
        "{field_uuid_membership_tier}": { "value": "Gold" },
        "{field_uuid_member_number}":   { "value": "12345" }
      }
    }
  }
}

Then read back the values:

GET /api/v1/customers/{customer_uuid}?o={org_id}

The custom_entry_map in the response will contain the stored values with their labels and formatted representations.


Finding Customers

By Email (Exact Match)

GET /api/v1/customers?o={org_id}&filter[email]=jane@example.com

By Search (Fuzzy)

Searches across name, email, and phone:

GET /api/v1/customers?o={org_id}&filter[search]=jane

By Custom Field

See the custom field filter syntax in JSON-API Conventions.


Resolving a Customer by Code

Look up a customer from a QR code, barcode, or account pass:

GET /api/v1/customers/resolve-code?o={org_id}&code={identifier_code}

Resolves the code via the ModelIdentifier system and returns the associated customer resource.

Creating Custom Model Identifiers

To assign a custom identifier (barcode, member number, etc.) to a customer, create a model-identifier linked to the customer:

POST /api/v1/model-identifiers?o={org_id}

{
  "data": {
    "type": "model-identifiers",
    "attributes": {
      "code": "MEMBER-12345",
      "type": "custom"
    },
    "relationships": {
      "identifiable": {
        "data": {
          "type": "customers",
          "id": "{customer_uuid}"
        }
      }
    }
  }
}

Once created, the customer can be looked up using resolve-code with the assigned code. You can also view a customer's existing identifiers via the model-identifiers relationship:

GET /api/v1/customers/{customer_id}/model-identifiers?o={org_id}

Merging Duplicate Customers

Combine two customer records into one:

POST /api/v1/customers/{source_customer_id}/merge?o={org_id}

{
  "referenceId": "{target_customer_uuid}"
}

The merge process:

  1. Fills empty fields on the target customer from the source (name, phone, birth date, etc.)
  2. Merges custom field data (target values take priority on conflicts)
  3. Transfers all relationships (bookings, orders, invoices) to the target
  4. Links the global account if the source had one and the target doesn't
  5. Deletes the source customer record

Warning: This operation is irreversible. The source customer is permanently deleted.


Removing a Customer from a Community

GET /api/v1/customers/{customer_id}/remove-from-community?o={org_id}&community_id={community_uuid}

Removes the customer's membership from the specified community.


Exporting Customers

Trigger an asynchronous export:

POST /api/v1/exports?o={org_id}

{
  "data": {
    "type": "exports",
    "attributes": {
      "type": "customers",
      "filters": {
        "search": "jane"
      }
    }
  }
}

The export is processed in the background. Retrieve the download URL from the export resource once complete.


Common Errors

Error Cause Solution
422 — email already exists Duplicate email in organization Use search to find the existing record, or merge
404 — code not found Invalid identifier code Verify the code belongs to this organization
422 — merge reference not found Invalid target customer UUID Verify the target customer exists