> For the complete documentation index, see [llms.txt](https://support.swisspay.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.swisspay.ai/api-reference/customers.md).

# Customers

Store the people you charge so you can look them up later, associate them with payments, and avoid retyping their details on every order.

All endpoints are under `https://app.swisspay.ai/api/v1/` and authenticate with a Bearer API key:

```
Authorization: Bearer sk_test_...
```

Keys follow your workspace environment: live workspaces issue `sk_live_...` keys, sandbox workspaces issue `sk_test_...` keys. A key only works while its mode matches the workspace environment (`key_mode_mismatch` otherwise).

All requests and responses are JSON. Timestamps (`created`) are Unix seconds. See the [Payments API](/api-reference/payments.md) for conventions around minor units and error envelopes -- they apply equally here.

## The Customer object

| Field              | Type           | Description                                                                                                                                 |
| ------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`               | string         | Unique identifier, of the form `cus_` followed by 24 lowercase alphanumeric characters.                                                     |
| `object`           | string         | Always `"customer"`.                                                                                                                        |
| `email`            | string         | Customer's email address. Unique per merchant, compared case-insensitively.                                                                 |
| `name`             | string \| null | Full name as you'd like it on receipts.                                                                                                     |
| `phone`            | string \| null | E.164 phone number. Not validated; pass through whatever your shop captures.                                                                |
| `locale`           | string \| null | BCP-47 locale tag (e.g. `de-CH`, `en`). Used by integrations that localize emails or hosted pages.                                          |
| `external_id`      | string \| null | Your own identifier for this customer (e.g. user id in your system). If present, unique per merchant.                                       |
| `billing_address`  | object \| null | Flat address object with `line1`, `line2`, `city`, `postal_code`, `state`, `country`. `country` is an ISO-3166 alpha-2 code. All optional.  |
| `delivery_address` | object \| null | Same shape as `billing_address`. Sent alongside the billing address to the underlying fraud engine -- a mismatch is a common fraud signal.  |
| `date_of_birth`    | string \| null | ISO-8601 date (`YYYY-MM-DD`). Improves fraud scoring and is required by some payment methods (e.g. Klarna).                                 |
| `metadata`         | object         | Free-form string->string map. Up to 20 keys; keys up to 40 characters; values up to 500 characters. Defaults to `{}` if you don't send any. |
| `created`          | integer        | Creation time as Unix seconds.                                                                                                              |

A customer's `billing_address` is used for [address verification](/api-reference/payments.md#address-verification-avs) on their card payments, unless the payment sends a `billing_address` of its own. Two details are worth getting right when you store one: `country` must be an ISO-3166 alpha-2 code or the address is not sent for verification at all, and `state` should be the postal abbreviation (`ZH`, `CA`) rather than the full name, because verification matches on that field.

## Create a customer

```
POST /api/v1/customers
```

Creates a customer on your merchant account and returns the new `Customer`.

### Headers

| Header            | Required | Notes                                                                            |
| ----------------- | -------- | -------------------------------------------------------------------------------- |
| `Authorization`   | yes      | `Bearer sk_test_...`                                                             |
| `Content-Type`    | yes      | `application/json`                                                               |
| `Idempotency-Key` | no       | Optional but recommended for unattended writes. See [Idempotency](#idempotency). |

### Request body

| Field                         | Type   | Required | Description                                                                                                          |
| ----------------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `email`                       | string | yes      | Must be unique on your merchant. Comparison is case-insensitive (`Jane@example.com` and `jane@example.com` collide). |
| `name`                        | string | no       | Full name.                                                                                                           |
| `phone`                       | string | no       | E.164 recommended; not enforced.                                                                                     |
| `locale`                      | string | no       | BCP-47 tag, e.g. `de-CH`, `en`, `fr-FR`.                                                                             |
| `external_id`                 | string | no       | Your own id. Must be unique on your merchant if provided.                                                            |
| `billing_address`             | object | no       | See below.                                                                                                           |
| `billing_address.line1`       | string | no       | Street and number.                                                                                                   |
| `billing_address.line2`       | string | no       | Apartment, suite, etc.                                                                                               |
| `billing_address.city`        | string | no       | City or locality.                                                                                                    |
| `billing_address.postal_code` | string | no       | ZIP / postcode.                                                                                                      |
| `billing_address.state`       | string | no       | State, canton, or region.                                                                                            |
| `billing_address.country`     | string | no       | ISO-3166 alpha-2 code (e.g. `CH`, `DE`, `US`).                                                                       |
| `delivery_address`            | object | no       | Same shape as `billing_address`. Recommended for physical-goods orders.                                              |
| `date_of_birth`               | string | no       | ISO-8601 date (`YYYY-MM-DD`). Must not be in the future.                                                             |
| `metadata`                    | object | no       | Up to 20 string->string entries. Keys <= 40 chars, values <= 500 chars.                                              |

### Response -- success

`201 Created`

```json
{
  "id": "cus_01hqxyzabcdef0123456789",
  "object": "customer",
  "email": "jane@example.com",
  "name": "Jane Doe",
  "phone": "+41791234567",
  "locale": "de-CH",
  "external_id": "user_42",
  "billing_address": {
    "line1": "Bahnhofstrasse 1",
    "line2": null,
    "city": "Zurich",
    "postal_code": "8001",
    "state": null,
    "country": "CH"
  },
  "metadata": {
    "plan": "pro"
  },
  "created": 1747209600
}
```

If you only send `email`, all other fields come back as `null` (or `{}` for `metadata`):

```json
{
  "id": "cus_01hqxyzabcdef0123456789",
  "object": "customer",
  "email": "jane@example.com",
  "name": null,
  "phone": null,
  "locale": null,
  "external_id": null,
  "billing_address": null,
  "metadata": {},
  "created": 1747209600
}
```

### Errors

| HTTP | `type`                  | `code`                       | When                                                                                                                       |
| ---- | ----------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 422  | `validation_error`      | `customer_email_taken`       | A customer with this `email` already exists on your merchant. Email comparison is case-insensitive.                        |
| 422  | `validation_error`      | `customer_external_id_taken` | A customer with this `external_id` already exists on your merchant.                                                        |
| 422  | `validation_error`      | `invalid_params`             | Missing or malformed `email`, bad `locale`, unknown `country` code, or `metadata` exceeds the size limits described above. |
| 404  | `invalid_request_error` | `not_found`                  | (Retrieve only.) Customer with that id does not exist on your merchant.                                                    |

See the [Payments API errors section](/api-reference/payments.md#errors) for the shared error envelope and broader categories like `authentication_error`.

### Idempotency

`Idempotency-Key` is **optional** on this endpoint. We recommend sending one for any unattended write. Without a key, a network retry that loses the response on the wire may return `422 customer_email_taken` on the second attempt because the customer already exists; either send an `Idempotency-Key`, or `GET /api/v1/customers?email=...` before retrying.

The replay semantics are identical to [Payments](/api-reference/payments.md#idempotency): same key + same body returns the original response; same key + different body returns `409 idempotency_error`.

### Example -- minimal create

```bash
curl https://app.swisspay.ai/api/v1/customers \
  -H "Authorization: Bearer sk_test_example" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com"
  }'
```

### Example -- full create with idempotency

```bash
curl https://app.swisspay.ai/api/v1/customers \
  -H "Authorization: Bearer sk_test_example" \
  -H "Idempotency-Key: signup_user_42" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "name": "Jane Doe",
    "phone": "+41791234567",
    "locale": "de-CH",
    "external_id": "user_42",
    "billing_address": {
      "line1": "Bahnhofstrasse 1",
      "city": "Zurich",
      "postal_code": "8001",
      "country": "CH"
    },
    "metadata": {
      "plan": "pro"
    }
  }'
```

## Retrieve a customer

```
GET /api/v1/customers/:id
```

Returns the `Customer` with the given `id`. The shape is identical to the create response.

```bash
curl https://app.swisspay.ai/api/v1/customers/cus_01hqxyzabcdef0123456789 \
  -H "Authorization: Bearer sk_test_example"
```

Returns `404` with `type: "invalid_request_error"` and `code: "not_found"` if the customer does not exist on your account:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "No customer found with that id."
  }
}
```

## List customers

```
GET /api/v1/customers
```

Returns your customers, most recent first.

### Query parameters

| Parameter  | Type    | Default | Notes                                                                                         |
| ---------- | ------- | ------- | --------------------------------------------------------------------------------------------- |
| `page`     | integer | `1`     | Page number, 1-indexed.                                                                       |
| `per_page` | integer | `20`    | Page size, between `1` and `100`.                                                             |
| `email`    | string  | --      | Exact match (case-insensitive). Useful before a create-retry to check if the customer exists. |

### Response

```json
{
  "data": [
    {
      "id": "cus_01hqxyzabcdef0123456789",
      "object": "customer",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "phone": null,
      "locale": null,
      "external_id": "user_42",
      "billing_address": null,
      "metadata": {},
      "created": 1747209600
    }
  ],
  "has_more": false,
  "total_count": 1
}
```

`has_more` is `true` when more pages exist beyond the current one. Cursor pagination is on the roadmap; for now, page through with `page`.

```bash
curl "https://app.swisspay.ai/api/v1/customers?page=1&per_page=20" \
  -H "Authorization: Bearer sk_test_example"
```

```bash
curl "https://app.swisspay.ai/api/v1/customers?email=jane@example.com" \
  -H "Authorization: Bearer sk_test_example"
```
