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
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:
- Fills empty fields on the target customer from the source (name, phone, birth date, etc.)
- Merges custom field data (target values take priority on conflicts)
- Transfers all relationships (bookings, orders, invoices) to the target
- Links the global account if the source had one and the target doesn't
- 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 |