> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anny.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Customer Management

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

For general conventions, see [JSON-API Conventions](/developers/guides/json-api-conventions). For authentication, see [Authentication](/developers/guides/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

```http theme={null}
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:

```http theme={null}
GET /api/v1/customers/{customer_id}?o={org_id}
```

```json theme={null}
{
  "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:API resources)**

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

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

Returns `custom_entries` as a JSON:API `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.

```http theme={null}
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:

```json theme={null}
"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

```http theme={null}
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:

```http theme={null}
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)

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

### By Search (Fuzzy)

Searches across name, email, and phone:

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

### By Custom Field

See the custom field filter syntax in [JSON-API Conventions](/developers/guides/json-api-conventions#custom-field-filters).

***

## Resolving a Customer by Code

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

```http theme={null}
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:

```http theme={null}
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:

```http theme={null}
GET /api/v1/customers/{customer_id}/model-identifiers?o={org_id}
```

***

## Merging Duplicate Customers

Combine two customer records into one:

```http theme={null}
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

```http theme={null}
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:

```http theme={null}
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                |

***

## Delivery Address

Customers can have a **delivery address** stored as a separate `address` resource. This is used when a service or plan requires a delivery address during checkout (configured via `requires_delivery_address` on the service/plan).

### Reading the Delivery Address

Include the `delivery_address` relationship:

```http theme={null}
GET /api/v1/customers/{customer_uuid}?o={org_id}&include=delivery_address
```

The response includes the address in the `included` array:

```json theme={null}
{
  "data": {
    "type": "customers",
    "id": "{uuid}",
    "relationships": {
      "delivery_address": {
        "data": { "type": "addresses", "id": "{address_uuid}" }
      }
    }
  },
  "included": [
    {
      "type": "addresses",
      "id": "{address_uuid}",
      "attributes": {
        "street_address": "123 Main St",
        "city": "Berlin",
        "zip_code": "10115",
        "country_code": "DE"
      }
    }
  ]
}
```

### How Delivery Addresses Are Set

Delivery addresses are collected during checkout when the booked service has `requires_delivery_address: true`. The address is submitted as part of the order form data and stored on the customer record. The order also snapshots the delivery address text at the time of purchase.

### Including on Other Resources

```http theme={null}
GET /api/v1/orders?o={org_id}&include=customer.delivery_address
GET /api/v1/bookings/{id}?o={org_id}&include=customer.delivery_address
GET /api/v1/plan-subscriptions?o={org_id}&include=customer.delivery_address
```
