# Customers tools

> MCP tools for Customers: customers_create, customers_credit_balance_adjustments_create, customers_credit_balance_get, customers_delete, customers_entitlement_overrides_create, customers_entitlement_overrides_delete, customers_entitlement_overrides_list, customers_entitlements_get, customers_evidence_sets_list, customers_get, customers_list, customers_tax_ids_add, customers_tax_ids_delete, customers_update.

- `customers_create`: Create a customer
- `customers_credit_balance_adjustments_create`: Adjust a customer's credit balance
- `customers_credit_balance_get`: Get a customer's credit balance
- `customers_delete`: Delete a customer
- `customers_entitlement_overrides_create`: Override an entitlement
- `customers_entitlement_overrides_delete`: Delete an entitlement override
- `customers_entitlement_overrides_list`: List a customer's entitlement overrides
- `customers_entitlements_get`: Get a customer's entitlements
- `customers_evidence_sets_list`: List a customer's location evidence
- `customers_get`: Get a customer
- `customers_list`: List customers
- `customers_tax_ids_add`: Add a tax ID to a customer
- `customers_tax_ids_delete`: Remove a tax ID from a customer
- `customers_update`: Update a customer

## `customers_create`

**Create a customer.** Operation [`customers.create`](https://ownrecord.io/docs/reference/customers-create.md) · Brand level · permission `customers:write` · risk tier `write` · since M4

Creates a customer of the brand in the mode of the request (spec §7.5). Checkout creates customers itself; use
this to create them ahead of a sale or from your own sign-up.

- **Email:** one active customer per address in a brand and mode (`customer_email_taken` otherwise). The same
  address in another brand is another customer.
- **Tax IDs:** each is checked with its register before it is stored (VIES for EU VAT IDs, HMRC for UK VAT
  numbers, the Swiss UID register; Norwegian and US numbers by format; other types are kept `unverified`).
  The result, its source, the consultation number and the time are stored per ID. A malformed number is
  refused (`tax_id_invalid`); a number the register doesn't know is stored as `invalid`. Dry runs simulate
  the check.
- **Type:** inferred (spec §13.1): `business` with a tax ID that isn't `invalid`, or with `businessName` when you
  declare `type: business`; otherwise what you declared. Tax is still decided per sale: only a `valid` tax ID
  makes a sale business-to-business, and the location comes from the evidence checkout captures.

Emits `customer.created`.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: true`

### Arguments

| Argument          | Type                                       | Required | Description                                                                                                                                                                                                                             |
| ----------------- | ------------------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`           | string                                     | yes      | The customer's email address. One active customer per address in a brand and mode. (at most 254 characters)                                                                                                                             |
| `name`            | string                                     | no       | The person's name. (1–200 characters)                                                                                                                                                                                                   |
| `businessName`    | string                                     | no       | The business's legal name, for business customers. (1–200 characters)                                                                                                                                                                   |
| `type`            | one of `individual`, `business`, `unknown` | no       | What you say the customer is: `individual`, `business` (needs `businessName` or a tax ID) or `unknown`. The stored `type` is inferred: a tax ID that isn't rejected makes the customer a `business` (spec §13.1). (default `"unknown"`) |
| `locale`          | string                                     | no       | Language for checkout, the portal, emails and invoices, e.g. `de`.                                                                                                                                                                      |
| `billingAddress`  | object                                     | no       | –                                                                                                                                                                                                                                       |
| `taxIds`          | array of object                            | no       | The customer's tax IDs. Each is checked with its register (VIES, HMRC, the Swiss UID register) and stored with the result; a malformed number is refused (`tax_id_invalid`). (at most 5 items) (default `[]`)                           |
| `testClockId`     | ID (`clk_…`)                               | no       | Test mode: a test clock (`clk_…`) to attach the customer to. Its subscriptions bill at the clock's time (spec §16.13).                                                                                                                  |
| `metadata`        | object                                     | no       | Your own key-value pairs (e.g. your user ID).                                                                                                                                                                                           |
| `entity_id`       | ID (`ent_…`)                               | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                                                |
| `brand_id`        | ID (`brd_…`)                               | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                                                |
| `idempotency_key` | string                                     | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters)                                |
| `dry_run`         | boolean                                    | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                                                |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A field is missing or malformed, a tax ID is given twice, or a business customer has neither a business name nor a tax ID (`details.issues`).
- [`tax_id_invalid`](https://ownrecord.io/docs/reference/errors.md#tax_id_invalid): A tax ID's number isn't valid for its type and country (format, checksum, another country's prefix, a Northern Ireland XI number); `details.reason` says which. Nothing is stored. Types Ownrecord can't check (e.g. `au_abn`) are stored as `unverified` instead.
- [`customer_email_taken`](https://ownrecord.io/docs/reference/errors.md#customer_email_taken): Another active customer of the brand has this email address in this mode.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No test clock with `testClockId` exists for the brand in test mode.
- [`test_clock_mode`](https://ownrecord.io/docs/reference/errors.md#test_clock_mode): `testClockId` was given in live mode: test clocks belong to test mode.
- [`test_clock_advancing`](https://ownrecord.io/docs/reference/errors.md#test_clock_advancing): The test clock is advancing: wait until it is ready.
- [`test_clock_customer_limit`](https://ownrecord.io/docs/reference/errors.md#test_clock_customer_limit): The test clock already has 10 customers.
- [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused): The idempotency key was used before with another input.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers (`customers:write`: owners, admins and support).

### Examples

**A business in Austria with its VAT ID**

VIES confirms the VAT ID, so the customer is a business.

Arguments:

```json
{
  "email": "billing@alpenapps.example",
  "name": "Lena Huber",
  "businessName": "Alpen Apps GmbH",
  "type": "business",
  "locale": "de-AT",
  "billingAddress": {
    "line1": "Mariahilfer Straße 1",
    "city": "Wien",
    "postalCode": "1060",
    "country": "AT"
  },
  "taxIds": [
    {
      "type": "eu_vat",
      "value": "ATU 0000 0000",
      "country": "AT"
    }
  ],
  "metadata": {
    "userId": "u_4821"
  },
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "billing@alpenapps.example",
  "name": "Lena Huber",
  "businessName": "Alpen Apps GmbH",
  "locale": "de-AT",
  "type": "business",
  "declaredType": "business",
  "billingAddress": {
    "line1": "Mariahilfer Straße 1",
    "line2": null,
    "city": "Wien",
    "postalCode": "1060",
    "region": null,
    "country": "AT"
  },
  "taxIds": [
    {
      "id": "ATU00000000",
      "type": "eu_vat",
      "value": "ATU 0000 0000",
      "country": "AT",
      "number": "ATU00000000",
      "verification": {
        "status": "valid",
        "source": "VIES",
        "reason": null,
        "checkedAt": "2026-10-14T09:00:00.000Z",
        "consultationNumber": "WAPIAAAAZ1x2y3",
        "simulated": false
      },
      "history": [],
      "addedAt": "2026-10-14T09:00:00.000Z"
    }
  ],
  "taxCountryLocked": null,
  "taxExempt": null,
  "processorRefs": [],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": null,
    "totalPaid": {
      "amount": "0",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {
    "userId": "u_4821"
  },
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:00:00.000Z",
  "deletedAt": null
}
```

**A consumer**

Arguments:

```json
{
  "email": "ana@example.com",
  "name": "Ana Schmidt",
  "type": "individual",
  "billingAddress": {
    "country": "DE"
  },
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "ana@example.com",
  "name": "Ana Schmidt",
  "businessName": null,
  "locale": null,
  "type": "individual",
  "declaredType": "individual",
  "billingAddress": {
    "line1": null,
    "line2": null,
    "city": null,
    "postalCode": null,
    "region": null,
    "country": "DE"
  },
  "taxIds": [],
  "taxCountryLocked": null,
  "taxExempt": null,
  "processorRefs": [],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": null,
    "totalPaid": {
      "amount": "0",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {},
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:00:00.000Z",
  "deletedAt": null
}
```

## `customers_credit_balance_adjustments_create`

**Adjust a customer's credit balance.** Operation [`customers.credit_balance.adjustments.create`](https://ownrecord.io/docs/reference/customers-credit-balance-adjustments-create.md) · Brand level · permission `customers:admin` · risk tier `financial` · since M5

Adds credit to a customer's invoice credit balance (a goodwill credit), or takes unused credit back, with a
reason. A positive `amount` adds, a negative one removes; the balance never goes below zero
(`credit_balance_insufficient`). The credit is applied automatically to the customer's next invoices in that
currency, after tax.

In the ledger, credit granted without payment is a cost (`promo_expense`). Financial tier: owners and admins
(`customers:admin`) with a sign-in in the last 15 minutes; an approval policy may require a second person above
an amount. Dry-run it first. Emits `customer.credit_balance.updated`.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`) | yes      | The customer (`cus_…`).                                                                                                                                                                                  |
| `amount`          | object       | yes      | Signed: a positive amount adds credit (goodwill), a negative one takes unused credit back.                                                                                                               |
| `reason`          | string       | yes      | Why (required): shown in the ledger, the audit log and to approvers. (1–500 characters)                                                                                                                  |
| `entity_id`       | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The amount is zero, not whole minor units or malformed, or the reason is missing.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode.
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was deleted.
- [`credit_balance_insufficient`](https://ownrecord.io/docs/reference/errors.md#credit_balance_insufficient): A negative adjustment takes back more than the balance (`details.balance`).
- [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused): The idempotency key was used before for another request.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): Another posting moved the balance at the same moment; retry.
- [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required): The organization's approval policy needs a second person for this; nothing changed yet.
- [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected): The same request was sent again after it was rejected.
- [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired): The same request was sent again after its approval request expired.
- [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval): The approved request could no longer run as requested.
- [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required): Your last sign-in is older than 15 minutes.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't adjust credit balances (`customers:admin`: owners and admins).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't use live invoices.

### Example

**A goodwill credit after an outage**

Arguments:

```json
{
  "id": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
  "amount": {
    "amount": "250000000",
    "currency": "EUR"
  },
  "reason": "Goodwill for the September outage",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "object": "credit_balance_adjustment",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
  "amount": {
    "amount": "250000000",
    "currency": "EUR"
  },
  "balance": {
    "amount": "250000000",
    "currency": "EUR"
  },
  "reason": "Goodwill for the September outage",
  "ledgerTransactionId": "txn_01m4tx5nj8emwbyf7y9vcf47yq"
}
```

## `customers_credit_balance_get`

**Get a customer's credit balance.** Operation [`customers.credit_balance.get`](https://ownrecord.io/docs/reference/customers-credit-balance-get.md) · Brand level · permission `customers:read` · risk tier `read` · since M5

Returns a customer's invoice credit balance in every currency it has one: proration credits, goodwill credits
and overpayments, applied automatically to the customer's next invoices before they're charged or sent
(spec §16.11). It isn't the usage wallet. `changes` lists the latest movements: operator adjustments, credit
used by invoices at finalization and credit returned when such an invoice was voided.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument    | Type         | Required | Description                                                                                                                              |
| ----------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID (`cus_…`) | yes      | The customer (`cus_…`).                                                                                                                  |
| `entity_id` | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`  | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID (`cus_…`).
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't use live invoices.

### Example

**€50 left after an invoice used €200**

Arguments:

```json
{
  "id": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "credit_balance",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
  "livemode": false,
  "balances": [
    {
      "amount": "50000000",
      "currency": "EUR"
    }
  ],
  "changes": [
    {
      "ledgerTransactionId": "txn_01m4tx5nj8emwbyf7y9vcf47yn",
      "change": {
        "amount": "-200000000",
        "currency": "EUR"
      },
      "reason": "invoice_applied",
      "memo": "Invoice GEN-2026-000131",
      "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47yk",
      "effectiveAt": "2026-10-20T09:00:00.000Z"
    },
    {
      "ledgerTransactionId": "txn_01m4tx5nj8emwbyf7y9vcf47yq",
      "change": {
        "amount": "250000000",
        "currency": "EUR"
      },
      "reason": "adjustment",
      "memo": "Goodwill for the September outage",
      "invoiceId": null,
      "effectiveAt": "2026-10-20T08:30:00.000Z"
    }
  ]
}
```

## `customers_delete`

**Delete a customer.** Operation [`customers.delete`](https://ownrecord.io/docs/reference/customers-delete.md) · Brand level · permission `customers:admin` · risk tier `write` · since M4

Deletes a customer: a soft delete that sets `deletedAt`. The customer stays readable, because invoices,
payments and evidence belong to it and must be kept (spec §6.6), but it can't be changed any more, and its
email address can be used for a new customer. Deleted customers are left out of `customers.list` unless asked
for. Erasing personal data (pseudonymisation) is a separate operator action, still to come.

Give a `reason`: it goes to the audit log. Needs `customers:admin` (owners and admins). Emits
`customer.updated` with `deletedAt` set.

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                                                                                             |
| `reason`          | string       | no       | Why; recorded in the audit log. (1–500 characters)                                                                                                                                                       |
| `entity_id`       | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID, or the reason is too long.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was already deleted.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The customer changed at the same moment; retry.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't delete this brand's customers (`customers:admin`: owners and admins).

### Example

**Delete a duplicate customer**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "reason": "Duplicate of cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "ana@example.com",
  "name": "Ana Schmidt",
  "businessName": null,
  "locale": "de",
  "type": "individual",
  "declaredType": "individual",
  "billingAddress": {
    "line1": null,
    "line2": null,
    "city": null,
    "postalCode": null,
    "region": null,
    "country": "DE"
  },
  "taxIds": [],
  "taxCountryLocked": {
    "country": "DE",
    "subdivision": null,
    "lockedAt": "2026-10-14T09:30:00.000Z",
    "evidenceSetId": "evs_01m4ty3c2b5qx7mhd9k8w6s0ac"
  },
  "taxExempt": null,
  "processorRefs": [
    {
      "processorConnectionId": "pcon_01m4ty3c2b5qx7mhd9k8w6s0af",
      "customerRef": "cus_QxY12AbC"
    }
  ],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": "2026-10-14T09:30:00.000Z",
    "totalPaid": {
      "amount": "119000000",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {},
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T10:00:00.000Z",
  "deletedAt": "2026-10-14T10:00:00.000Z"
}
```

## `customers_entitlement_overrides_create`

**Override an entitlement.** Operation [`customers.entitlement_overrides.create`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-create.md) · Brand level · permission `customers:write` · risk tier `write` · since M5

Grants a customer a value of one feature, with a reason and an optional expiry, e.g. 20 extra seats while a
team migrates or `feature.export = true` as a goodwill gesture. The override is one more source of the
customer's entitlements and merges with the others by the feature's combine rule: with `max` it raises a
limit, with `sum` it adds to it, with `or` it can only switch a feature on, and with `first` it decides
(the newest override comes first).

It applies until `expiresAt` (exclusive), or until deleted; for a customer on a test clock, `expiresAt` is a
time on the clock (#541). The customer's entitlements are recomputed
within one job cycle, and `customer.entitlements.updated` is sent when a value changes. Needs
`customers:write` (owners, admins and support); the reason goes to the override and the audit log.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type                        | Required | Description                                                                                                                                                                                              |
| ----------------- | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`)                | yes      | The customer's ID (`cus_…`).                                                                                                                                                                             |
| `featureKey`      | string                      | yes      | The feature to grant a value of, e.g. `seats.max`. (at most 100 characters)                                                                                                                              |
| `value`           | boolean or number or string | yes      | The value to grant: true/false for a boolean feature, a whole number ≥ 0 for a limit, one of the values of an enum feature. It merges with the customer's other values by the feature's combine rule.    |
| `reason`          | string                      | yes      | Why the customer gets this; stored on the override and in the audit log. (1–500 characters)                                                                                                              |
| `expiresAt`       | date-time (ISO 8601)        | no       | When the override stops applying (exclusive). Must be in the future. Omit for no expiry.                                                                                                                 |
| `entity_id`       | ID (`ent_…`)                | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`)                | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string                      | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                     | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A field is missing or malformed, the reason is empty, or `expiresAt` isn't in the future.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was deleted.
- [`entitlement_feature_not_found`](https://ownrecord.io/docs/reference/errors.md#entitlement_feature_not_found): The brand has no feature with this key in this mode.
- [`entitlement_value_invalid`](https://ownrecord.io/docs/reference/errors.md#entitlement_value_invalid): The value doesn't fit the feature: true/false for booleans, a whole number ≥ 0 for limits, a listed value for enums.
- [`entitlement_override_limit`](https://ownrecord.io/docs/reference/errors.md#entitlement_override_limit): The customer already has 100 active overrides.
- [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused): The idempotency key was used before with another input.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers' entitlements (`customers:write`: owners, admins and support).

### Examples

**Extra seats for a month**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "featureKey": "seats.max",
  "value": 20,
  "reason": "Goodwill while their team migrates (ticket 4821)",
  "expiresAt": "2026-11-14T00:00:00.000Z",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
  "object": "entitlement_override",
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "featureKey": "seats.max",
  "value": 20,
  "reason": "Goodwill while their team migrates (ticket 4821)",
  "setBy": {
    "type": "user",
    "id": "usr_01m4ty3c2b5qx7mhd9k8w6s0b4"
  },
  "expiresAt": "2026-11-14T00:00:00.000Z",
  "active": true,
  "createdAt": "2026-10-14T10:00:00.000Z"
}
```

**Switch a feature on for good**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "featureKey": "feature.export",
  "value": true,
  "reason": "Beta tester",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b3",
  "object": "entitlement_override",
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "featureKey": "feature.export",
  "value": true,
  "reason": "Beta tester",
  "setBy": {
    "type": "user",
    "id": "usr_01m4ty3c2b5qx7mhd9k8w6s0b4"
  },
  "expiresAt": null,
  "active": true,
  "createdAt": "2026-10-14T10:00:00.000Z"
}
```

## `customers_entitlement_overrides_delete`

**Delete an entitlement override.** Operation [`customers.entitlement_overrides.delete`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-delete.md) · Brand level · permission `customers:write` · risk tier `write` · since M5

Removes an override from a customer; the response is the override as it was. The customer's entitlements
are recomputed within one job cycle, and `customer.entitlements.updated` is sent when a value changes. An
expired override no longer applies anyway; deleting it only tidies the list.

Give a `reason`: it goes to the audit log with the removed override. Needs `customers:write` (owners, admins
and support).

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                                                                                             |
| `overrideId`      | ID (`ceo_…`) | yes      | The override's ID (`ceo_…`).                                                                                                                                                                             |
| `reason`          | string       | yes      | Why the override is removed; recorded in the audit log. (1–500 characters)                                                                                                                               |
| `entity_id`       | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): An ID is malformed, or the reason is missing or too long.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode, or the customer has no override with this `overrideId`.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The override changed at the same moment; retry.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers' entitlements (`customers:write`: owners, admins and support).

### Example

**End a goodwill grant early**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "overrideId": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
  "reason": "Migration finished",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
  "object": "entitlement_override",
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "featureKey": "seats.max",
  "value": 20,
  "reason": "Goodwill while their team migrates (ticket 4821)",
  "setBy": {
    "type": "user",
    "id": "usr_01m4ty3c2b5qx7mhd9k8w6s0b4"
  },
  "expiresAt": "2026-11-14T00:00:00.000Z",
  "active": true,
  "createdAt": "2026-10-14T10:00:00.000Z"
}
```

## `customers_entitlement_overrides_list`

**List a customer's entitlement overrides.** Operation [`customers.entitlement_overrides.list`](https://ownrecord.io/docs/reference/customers-entitlement-overrides-list.md) · Brand level · permission `customers:read` · risk tier `read` · since M5

Lists the overrides granted to a customer, newest first, including expired ones (`active: false`) unless
`active=true`. Each has its feature, value, reason, who set it and its expiry.

Paginate with `limit` (1–100, default 10) and `starting_after` (the `next_cursor` of the previous page).

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type              | Required | Description                                                                                                                              |
| ---------------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | ID (`cus_…`)      | yes      | The customer's ID (`cus_…`).                                                                                                             |
| `active`         | boolean or string | no       | true: only overrides that apply now (not expired).                                                                                       |
| `limit`          | integer           | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`ceo_…`)      | no       | The override's ID (`ceo_…`).                                                                                                             |
| `entity_id`      | ID (`ent_…`)      | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`       | ID (`brd_…`)      | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID, or a filter or pagination value is invalid.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).

### Example

**Overrides that apply now**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "active": true,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
      "object": "entitlement_override",
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "livemode": false,
      "featureKey": "seats.max",
      "value": 20,
      "reason": "Goodwill while their team migrates (ticket 4821)",
      "setBy": {
        "type": "user",
        "id": "usr_01m4ty3c2b5qx7mhd9k8w6s0b4"
      },
      "expiresAt": "2026-11-14T00:00:00.000Z",
      "active": true,
      "createdAt": "2026-10-14T10:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## `customers_entitlements_get`

**Get a customer's entitlements.** Operation [`customers.entitlements.get`](https://ownrecord.io/docs/reference/customers-entitlements-get.md) · Brand level · permission `customers:read` · risk tier `read` · since M5

Returns what a customer of the brand may use, in the mode of the request: one value per feature of the brand
(`values`), where each value comes from (`sources`) and when it was resolved (`computedAt`).

Values are resolved from the customer's subscriptions (`active`, `trialing`, `past_due` during the grace
period, `paused` when the brand keeps entitlements during a pause), one-time purchases of products with
`lifetimeEntitlements`, overrides set by your team, and feature defaults, merged with each feature's combine
rule. A `perUnit` limit is multiplied by the quantity.

Ownrecord stores the result and recomputes it within one job cycle of every change; the
`customer.entitlements.updated` webhook says when it changed. A read never returns a set known to be out of
date: while a recompute is pending it resolves the values from the sources. Enforcing them is up to your
product. For one feature, `entitlements.check` is lighter.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument    | Type         | Required | Description                                                                                                                              |
| ----------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                             |
| `entity_id` | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`  | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID (`cus_…`).
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).

### Examples

**A subscriber with an override**

Three seats of a plan granting 5 seats per unit (15), raised to 20 by an override (`seats.max` combines with `max`); `support.level` has its default.

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "customer_entitlements",
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "values": {
    "feature.export": true,
    "seats.max": 20,
    "support.level": "community"
  },
  "sources": [
    {
      "type": "subscription",
      "ref": "sub_01m4ty3c2b5qx7mhd9k8w6s0b0",
      "featureKey": "feature.export",
      "value": true
    },
    {
      "type": "override",
      "ref": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
      "featureKey": "seats.max",
      "value": 20
    },
    {
      "type": "subscription",
      "ref": "sub_01m4ty3c2b5qx7mhd9k8w6s0b0",
      "featureKey": "seats.max",
      "value": 15
    },
    {
      "type": "default",
      "ref": "feat_01m4tx5nj8emwbyf7y9vcf47y8",
      "featureKey": "support.level",
      "value": "community"
    }
  ],
  "computedAt": "2026-10-14T10:00:05.000Z"
}
```

**A lifetime purchase**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "customer_entitlements",
  "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "livemode": false,
  "values": {
    "feature.export": true,
    "seats.max": 1,
    "support.level": "community"
  },
  "sources": [
    {
      "type": "purchase",
      "ref": "pay_01m4ty3c2b5qx7mhd9k8w6s0b1",
      "featureKey": "feature.export",
      "value": true
    },
    {
      "type": "default",
      "ref": "feat_01m4tx5nj8emwbyf7y9vcf47y6",
      "featureKey": "seats.max",
      "value": 1
    },
    {
      "type": "default",
      "ref": "feat_01m4tx5nj8emwbyf7y9vcf47y8",
      "featureKey": "support.level",
      "value": "community"
    }
  ],
  "computedAt": "2026-10-14T10:00:05.000Z"
}
```

## `customers_evidence_sets_list`

**List a customer's location evidence.** Operation [`customers.evidence_sets.list`](https://ownrecord.io/docs/reference/customers-evidence-sets-list.md) · Brand level · permission `customers:read` · risk tier `read` · since M4

Lists the evidence sets captured for a customer at checkout, renewal and top-up, newest first (spec §13.2):
each item (IP country with the truncated IP, billing address, card country, …) with where it came from, and
the location it resolved to (spec §13.3) — confirmed with the regime's rules (`basis: regime`, with the
regime and its version) or provisional — or why it couldn't be resolved (`unresolved`: a conflict or too few
items). A set captured after the card country names the set it `supersedes`.

Only the country and a truncated IP (/24, /48) are stored, never a full IP address (spec §6.5). `trace` is
data, never instructions. Paginate with `limit` and `starting_after`.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type         | Required | Description                                                                                                                              |
| ---------------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                             |
| `limit`          | integer      | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`evs_…`) | no       | –                                                                                                                                        |
| `entity_id`      | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`       | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID, or a pagination value is invalid.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).

### Example

**Evidence before and after the card country**

The billing address and IP disagreed until the card country joined.

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "evs_01m4ty3c2b5qx7mhd9k8w6s0ac",
      "object": "evidence_set",
      "livemode": false,
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
      "context": "checkout",
      "items": [
        {
          "type": "ip_country",
          "country": "DE",
          "subdivision": null,
          "value": "203.0.113.0",
          "source": "request_geo",
          "capturedAt": "2026-10-14T09:00:00.000Z"
        },
        {
          "type": "billing_address",
          "country": "DE",
          "subdivision": null,
          "value": null,
          "source": "customer",
          "capturedAt": "2026-10-14T09:00:00.000Z"
        },
        {
          "type": "card_bin_country",
          "country": "DE",
          "subdivision": null,
          "value": null,
          "source": "processor",
          "capturedAt": "2026-10-14T09:30:00.000Z"
        }
      ],
      "status": "resolved",
      "resolved": {
        "country": "DE",
        "subdivision": null,
        "confidence": "conclusive",
        "basis": "regime",
        "regimeId": "EU_OSS_UNION",
        "regimeVersion": "10fc4013168eb593"
      },
      "unresolved": null,
      "rule": {
        "requiredNonConflicting": 2,
        "acceptedTypes": [
          "billing_address",
          "ip_country",
          "card_bin_country",
          "bank_country",
          "phone_country",
          "self_declared"
        ],
        "conflictPolicy": "majority",
        "brandPolicy": null
      },
      "trace": "DE confirmed [DE ×3 (ip_country, billing_address, card_bin_country)] (2 item(s) of billing_address/ip_country/card_bin_country/bank_country/phone_country/self_declared, policy majority)",
      "supersedes": "evs_01m4ty3c2b5qx7mhd9k8w6s0ad",
      "checkoutSessionId": "cs_01m4ty3c2b5qx7mhd9k8w6s0ae",
      "subscriptionId": null,
      "createdAt": "2026-10-14T09:30:00.000Z"
    },
    {
      "id": "evs_01m4ty3c2b5qx7mhd9k8w6s0ad",
      "object": "evidence_set",
      "livemode": false,
      "customerId": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
      "context": "checkout",
      "items": [
        {
          "type": "ip_country",
          "country": "DE",
          "subdivision": null,
          "value": "203.0.113.0",
          "source": "request_geo",
          "capturedAt": "2026-10-14T09:00:00.000Z"
        },
        {
          "type": "billing_address",
          "country": "DE",
          "subdivision": null,
          "value": null,
          "source": "customer",
          "capturedAt": "2026-10-14T09:00:00.000Z"
        }
      ],
      "status": "resolved",
      "resolved": {
        "country": "DE",
        "subdivision": null,
        "confidence": "conclusive",
        "basis": "regime",
        "regimeId": "EU_OSS_UNION",
        "regimeVersion": "10fc4013168eb593"
      },
      "unresolved": null,
      "rule": {
        "requiredNonConflicting": 2,
        "acceptedTypes": [
          "billing_address",
          "ip_country",
          "card_bin_country",
          "bank_country",
          "phone_country",
          "self_declared"
        ],
        "conflictPolicy": "majority",
        "brandPolicy": null
      },
      "trace": "DE confirmed [DE ×2 (ip_country, billing_address)] (2 item(s) of billing_address/ip_country/card_bin_country/bank_country/phone_country/self_declared, policy majority)",
      "supersedes": null,
      "checkoutSessionId": "cs_01m4ty3c2b5qx7mhd9k8w6s0ae",
      "subscriptionId": null,
      "createdAt": "2026-10-14T09:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## `customers_get`

**Get a customer.** Operation [`customers.get`](https://ownrecord.io/docs/reference/customers-get.md) · Brand level · permission `customers:read` · risk tier `read` · since M4

Returns a customer of the brand in the mode of the request: profile, inferred and declared type, billing
address, tax IDs with their latest check and earlier checks, the locked tax country (set at the first
successful payment, spec §9.3), processor references and risk. Deleted customers are returned too, with
`deletedAt`. The customer's location evidence is listed by `customers.evidence_sets.list`.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument    | Type         | Required | Description                                                                                                                              |
| ----------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                             |
| `entity_id` | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`  | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` isn't a customer ID (`cus_…`).
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).

### Examples

**A business customer**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "billing@alpenapps.example",
  "name": "Lena Huber",
  "businessName": "Alpen Apps GmbH",
  "locale": "de-AT",
  "type": "business",
  "declaredType": "business",
  "billingAddress": {
    "line1": "Mariahilfer Straße 1",
    "line2": null,
    "city": "Wien",
    "postalCode": "1060",
    "region": null,
    "country": "AT"
  },
  "taxIds": [
    {
      "id": "ATU00000000",
      "type": "eu_vat",
      "value": "ATU 0000 0000",
      "country": "AT",
      "number": "ATU00000000",
      "verification": {
        "status": "valid",
        "source": "VIES",
        "reason": null,
        "checkedAt": "2026-10-14T09:00:00.000Z",
        "consultationNumber": "WAPIAAAAZ1x2y3",
        "simulated": false
      },
      "history": [],
      "addedAt": "2026-10-14T09:00:00.000Z"
    }
  ],
  "taxCountryLocked": null,
  "taxExempt": null,
  "processorRefs": [],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": null,
    "totalPaid": {
      "amount": "0",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {
    "userId": "u_4821"
  },
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:00:00.000Z",
  "deletedAt": null
}
```

**A consumer after the first payment**

The first payment locked the tax country to Germany.

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "ana@example.com",
  "name": "Ana Schmidt",
  "businessName": null,
  "locale": "de",
  "type": "individual",
  "declaredType": "individual",
  "billingAddress": {
    "line1": null,
    "line2": null,
    "city": null,
    "postalCode": null,
    "region": null,
    "country": "DE"
  },
  "taxIds": [],
  "taxCountryLocked": {
    "country": "DE",
    "subdivision": null,
    "lockedAt": "2026-10-14T09:30:00.000Z",
    "evidenceSetId": "evs_01m4ty3c2b5qx7mhd9k8w6s0ac"
  },
  "taxExempt": null,
  "processorRefs": [
    {
      "processorConnectionId": "pcon_01m4ty3c2b5qx7mhd9k8w6s0af",
      "customerRef": "cus_QxY12AbC"
    }
  ],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": "2026-10-14T09:30:00.000Z",
    "totalPaid": {
      "amount": "119000000",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {},
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z",
  "deletedAt": null
}
```

## `customers_list`

**List customers.** Operation [`customers.list`](https://ownrecord.io/docs/reference/customers-list.md) · Brand level · permission `customers:read` · risk tier `read` · since M4

Lists the brand's customers in the mode of the request, newest first: only this organization's customers,
never another organization's. Deleted customers are left out unless `includeDeleted=true`.

- `email`: the customer with exactly this address (addresses are compared in lower case);
- `emailPrefix`: customers whose address starts with this text (search);
- `type`: `individual`, `business` or `unknown`.

Paginate with `limit` (1–100, default 10) and `starting_after` (the `next_cursor` of the previous page).

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type                                       | Required | Description                                                                                                                              |
| ---------------- | ------------------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`          | integer                                    | no       | (1–100)                                                                                                                                  |
| `starting_after` | ID (`cus_…`)                               | no       | The customer's ID (`cus_…`).                                                                                                             |
| `email`          | string                                     | no       | Only the customer with exactly this email address. (at most 254 characters)                                                              |
| `emailPrefix`    | string                                     | no       | Customers whose email starts with this text (search). (1–254 characters)                                                                 |
| `type`           | one of `individual`, `business`, `unknown` | no       | –                                                                                                                                        |
| `includeDeleted` | boolean or string                          | no       | Include deleted customers (default false). `true`/`false`, also as a query string.                                                       |
| `entity_id`      | ID (`ent_…`)                               | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                 |
| `brand_id`       | ID (`brd_…`)                               | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand. |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A filter or pagination value is invalid.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read this brand's customers (`customers:read`).

### Examples

**Find a customer by email**

Arguments:

```json
{
  "email": "ana@example.com",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
      "object": "customer",
      "livemode": false,
      "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "email": "ana@example.com",
      "name": "Ana Schmidt",
      "businessName": null,
      "locale": "de",
      "type": "individual",
      "declaredType": "individual",
      "billingAddress": {
        "line1": null,
        "line2": null,
        "city": null,
        "postalCode": null,
        "region": null,
        "country": "DE"
      },
      "taxIds": [],
      "taxCountryLocked": {
        "country": "DE",
        "subdivision": null,
        "lockedAt": "2026-10-14T09:30:00.000Z",
        "evidenceSetId": "evs_01m4ty3c2b5qx7mhd9k8w6s0ac"
      },
      "taxExempt": null,
      "processorRefs": [
        {
          "processorConnectionId": "pcon_01m4ty3c2b5qx7mhd9k8w6s0af",
          "customerRef": "cus_QxY12AbC"
        }
      ],
      "risk": {
        "level": "normal",
        "reasons": [],
        "firstPaymentAt": "2026-10-14T09:30:00.000Z",
        "totalPaid": {
          "amount": "119000000",
          "currency": "EUR"
        }
      },
      "portalLastLoginAt": null,
      "testClockId": null,
      "metadata": {},
      "createdAt": "2026-10-14T09:00:00.000Z",
      "updatedAt": "2026-10-14T09:30:00.000Z",
      "deletedAt": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

**Business customers, two per page**

Arguments:

```json
{
  "type": "business",
  "limit": 2,
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
      "object": "customer",
      "livemode": false,
      "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "email": "billing@alpenapps.example",
      "name": "Lena Huber",
      "businessName": "Alpen Apps GmbH",
      "locale": "de-AT",
      "type": "business",
      "declaredType": "business",
      "billingAddress": {
        "line1": "Mariahilfer Straße 1",
        "line2": null,
        "city": "Wien",
        "postalCode": "1060",
        "region": null,
        "country": "AT"
      },
      "taxIds": [
        {
          "id": "ATU00000000",
          "type": "eu_vat",
          "value": "ATU 0000 0000",
          "country": "AT",
          "number": "ATU00000000",
          "verification": {
            "status": "valid",
            "source": "VIES",
            "reason": null,
            "checkedAt": "2026-10-14T09:00:00.000Z",
            "consultationNumber": "WAPIAAAAZ1x2y3",
            "simulated": false
          },
          "history": [],
          "addedAt": "2026-10-14T09:00:00.000Z"
        }
      ],
      "taxCountryLocked": null,
      "taxExempt": null,
      "processorRefs": [],
      "risk": {
        "level": "normal",
        "reasons": [],
        "firstPaymentAt": null,
        "totalPaid": {
          "amount": "0",
          "currency": "EUR"
        }
      },
      "portalLastLoginAt": null,
      "testClockId": null,
      "metadata": {
        "userId": "u_4821"
      },
      "createdAt": "2026-10-14T09:00:00.000Z",
      "updatedAt": "2026-10-14T09:00:00.000Z",
      "deletedAt": null
    },
    {
      "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0a9",
      "object": "customer",
      "livemode": false,
      "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
      "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
      "email": "billing@alpenapps.example",
      "name": "Lena Huber",
      "businessName": "Alpen Apps GmbH",
      "locale": "de-AT",
      "type": "business",
      "declaredType": "business",
      "billingAddress": {
        "line1": "Mariahilfer Straße 1",
        "line2": null,
        "city": "Wien",
        "postalCode": "1060",
        "region": null,
        "country": "AT"
      },
      "taxIds": [
        {
          "id": "ATU00000000",
          "type": "eu_vat",
          "value": "ATU 0000 0000",
          "country": "AT",
          "number": "ATU00000000",
          "verification": {
            "status": "valid",
            "source": "VIES",
            "reason": null,
            "checkedAt": "2026-10-14T09:00:00.000Z",
            "consultationNumber": "WAPIAAAAZ1x2y3",
            "simulated": false
          },
          "history": [],
          "addedAt": "2026-10-14T09:00:00.000Z"
        }
      ],
      "taxCountryLocked": null,
      "taxExempt": null,
      "processorRefs": [],
      "risk": {
        "level": "normal",
        "reasons": [],
        "firstPaymentAt": null,
        "totalPaid": {
          "amount": "0",
          "currency": "EUR"
        }
      },
      "portalLastLoginAt": null,
      "testClockId": null,
      "metadata": {
        "userId": "u_4821"
      },
      "createdAt": "2026-10-14T09:00:00.000Z",
      "updatedAt": "2026-10-14T09:00:00.000Z",
      "deletedAt": null
    }
  ],
  "has_more": true,
  "next_cursor": "cus_01m4ty3c2b5qx7mhd9k8w6s0a9"
}
```

## `customers_tax_ids_add`

**Add a tax ID to a customer.** Operation [`customers.tax_ids.add`](https://ownrecord.io/docs/reference/customers-tax-ids-add.md) · Brand level · permission `customers:write` · risk tier `write` · since M4

Adds a tax ID to a customer after checking it with its register (VIES for EU VAT IDs, HMRC for UK VAT numbers,
the Swiss UID register; Norwegian and US numbers by format; other types are kept `unverified`). The answer is
stored with the ID: status, source, reason, consultation number and time (spec §9.4). A malformed number is
refused (`tax_id_invalid`); one the register doesn't know is stored as `invalid` and doesn't make the customer
a business. Dry runs simulate the check.

At most 5 tax IDs per customer; the same number twice is refused (`customer_tax_id_exists`). The type is
inferred again (spec §13.1). Emits `customer.updated`.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: true`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                                                                                             |
| `type`            | string       | yes      | The tax ID's type: `eu_vat`, `gb_vat`, `ch_uid`/`ch_vat`, `no_org`/`no_vat` or `us_ein`. Other types are kept but can't be checked (`unverified`).                                                       |
| `value`           | string       | yes      | The number as the customer gave it, with or without the country prefix and separators. (1–50 characters)                                                                                                 |
| `country`         | string       | yes      | The country that issued the tax ID (ISO 3166-1 alpha-2; `EL` = Greece).                                                                                                                                  |
| `entity_id`       | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A field is missing or malformed.
- [`tax_id_invalid`](https://ownrecord.io/docs/reference/errors.md#tax_id_invalid): A tax ID's number isn't valid for its type and country (format, checksum, another country's prefix, a Northern Ireland XI number); `details.reason` says which. Nothing is stored. Types Ownrecord can't check (e.g. `au_abn`) are stored as `unverified` instead.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was deleted.
- [`customer_tax_id_exists`](https://ownrecord.io/docs/reference/errors.md#customer_tax_id_exists): The customer already has this tax ID (`details.tax_id_id`).
- [`customer_tax_id_limit`](https://ownrecord.io/docs/reference/errors.md#customer_tax_id_limit): The customer already has 5 tax IDs.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The customer changed at the same moment; retry.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers (`customers:write`: owners, admins and support).

### Example

**Add an Austrian VAT ID**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "type": "eu_vat",
  "value": "ATU 0000 0000",
  "country": "AT",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "billing@alpenapps.example",
  "name": "Lena Huber",
  "businessName": "Alpen Apps GmbH",
  "locale": "de-AT",
  "type": "business",
  "declaredType": "business",
  "billingAddress": {
    "line1": "Mariahilfer Straße 1",
    "line2": null,
    "city": "Wien",
    "postalCode": "1060",
    "region": null,
    "country": "AT"
  },
  "taxIds": [
    {
      "id": "ATU00000000",
      "type": "eu_vat",
      "value": "ATU 0000 0000",
      "country": "AT",
      "number": "ATU00000000",
      "verification": {
        "status": "valid",
        "source": "VIES",
        "reason": null,
        "checkedAt": "2026-10-14T09:00:00.000Z",
        "consultationNumber": "WAPIAAAAZ1x2y3",
        "simulated": false
      },
      "history": [],
      "addedAt": "2026-10-14T09:00:00.000Z"
    }
  ],
  "taxCountryLocked": null,
  "taxExempt": null,
  "processorRefs": [],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": null,
    "totalPaid": {
      "amount": "0",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {
    "userId": "u_4821"
  },
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:00:00.000Z",
  "deletedAt": null
}
```

## `customers_tax_ids_delete`

**Remove a tax ID from a customer.** Operation [`customers.tax_ids.delete`](https://ownrecord.io/docs/reference/customers-tax-ids-delete.md) · Brand level · permission `customers:write` · risk tier `write` · since M4

Removes a tax ID from a customer. `taxIdId` is the tax ID's `id` on the customer: its canonical number, e.g.
`ATU12345678`. Past sales keep the check they were quoted with; future sales no longer use the ID. The type is
inferred again (spec §13.1): without a tax ID, a customer is a business only when declared one with a business
name. Emits `customer.updated`.

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type         | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`) | yes      | The customer's ID (`cus_…`).                                                                                                                                                                             |
| `taxIdId`         | string       | yes      | The tax ID's `id` on the customer: its canonical number, e.g. `ATU12345678`.                                                                                                                             |
| `entity_id`       | ID (`ent_…`) | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                 |
| `brand_id`        | ID (`brd_…`) | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                 |
| `idempotency_key` | string       | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean      | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `id` or `taxIdId` is malformed.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode, or the customer has no tax ID with this `taxIdId`.
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was deleted.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The customer changed at the same moment; retry.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers (`customers:write`: owners, admins and support).

### Example

**Remove the Austrian VAT ID**

The customer keeps its business name, so it stays a business.

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "taxIdId": "ATU00000000",
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "billing@alpenapps.example",
  "name": "Lena Huber",
  "businessName": "Alpen Apps GmbH",
  "locale": "de-AT",
  "type": "business",
  "declaredType": "business",
  "billingAddress": {
    "line1": "Mariahilfer Straße 1",
    "line2": null,
    "city": "Wien",
    "postalCode": "1060",
    "region": null,
    "country": "AT"
  },
  "taxIds": [],
  "taxCountryLocked": null,
  "taxExempt": null,
  "processorRefs": [],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": null,
    "totalPaid": {
      "amount": "0",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {
    "userId": "u_4821"
  },
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z",
  "deletedAt": null
}
```

## `customers_update`

**Update a customer.** Operation [`customers.update`](https://ownrecord.io/docs/reference/customers-update.md) · Brand level · permission `customers:write` · risk tier `write` · since M4

Changes a customer's email, name, business name, declared type, language, billing address or metadata. Send
only the fields to change; `null` removes an optional one. `billingAddress` and `metadata` are replaced as a
whole.

The type is inferred again (spec §13.1). Tax IDs have their own operations (`customers.tax_ids.add`,
`customers.tax_ids.delete`), and the locked tax country changes only through payments (spec §9.3). A changed
billing address is used as evidence at the next checkout; it doesn't change where past sales were taxed.

Emits `customer.updated` with the old values in `previous_attributes` (nothing when nothing changed).

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type                                       | Required | Description                                                                                                                                                                                                       |
| ----------------- | ------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`cus_…`)                               | yes      | The customer's ID (`cus_…`).                                                                                                                                                                                      |
| `email`           | string                                     | no       | (at most 254 characters)                                                                                                                                                                                          |
| `name`            | string or null                             | no       | null removes it.                                                                                                                                                                                                  |
| `businessName`    | string or null                             | no       | null removes it.                                                                                                                                                                                                  |
| `type`            | one of `individual`, `business`, `unknown` | no       | What you say the customer is: `individual`, `business` (needs `businessName` or a tax ID) or `unknown`. The stored `type` is inferred: a tax ID that isn't rejected makes the customer a `business` (spec §13.1). |
| `locale`          | string or null                             | no       | null removes it.                                                                                                                                                                                                  |
| `billingAddress`  | object or null                             | no       | Replaces the address; null removes it.                                                                                                                                                                            |
| `metadata`        | object                                     | no       | Replaces the metadata (send `{}` to clear it).                                                                                                                                                                    |
| `entity_id`       | ID (`ent_…`)                               | no       | Optional: the brand's legal entity (ent\_…); `brand_id` alone is enough.                                                                                                                                          |
| `brand_id`        | ID (`brd_…`)                               | no       | The brand to act on (brd\_…), inside your API key's reach. Needed with an organization or entity key; a brand key acts on its own brand.                                                                          |
| `idempotency_key` | string                                     | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters)          |
| `dry_run`         | boolean                                    | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                          |

- **Brand:** pass `brand_id` with an organization or entity key; a brand key acts on its own brand.
- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): Nothing to change, a field is malformed, or the customer would be a declared business without a business name or a tax ID.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): This brand has no such customer in this mode (customers belong to one brand and one mode).
- [`customer_deleted`](https://ownrecord.io/docs/reference/errors.md#customer_deleted): The customer was deleted.
- [`customer_email_taken`](https://ownrecord.io/docs/reference/errors.md#customer_email_taken): Another active customer of the brand has the new email address in this mode.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The customer changed at the same moment; retry.
- [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required): The request names no brand (an org or entity key without `Ownrecord-Brand`).
- [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed): You have no live-mode access, so you can't act on live customers.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't change this brand's customers (`customers:write`: owners, admins and support).

### Example

**Set the language and the full address**

Arguments:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "locale": "de-DE",
  "billingAddress": {
    "line1": "Torstraße 5",
    "city": "Berlin",
    "postalCode": "10119",
    "country": "DE"
  },
  "brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
  "object": "customer",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "email": "ana@example.com",
  "name": "Ana Schmidt",
  "businessName": null,
  "locale": "de-DE",
  "type": "individual",
  "declaredType": "individual",
  "billingAddress": {
    "line1": "Torstraße 5",
    "line2": null,
    "city": "Berlin",
    "postalCode": "10119",
    "region": null,
    "country": "DE"
  },
  "taxIds": [],
  "taxCountryLocked": {
    "country": "DE",
    "subdivision": null,
    "lockedAt": "2026-10-14T09:30:00.000Z",
    "evidenceSetId": "evs_01m4ty3c2b5qx7mhd9k8w6s0ac"
  },
  "taxExempt": null,
  "processorRefs": [
    {
      "processorConnectionId": "pcon_01m4ty3c2b5qx7mhd9k8w6s0af",
      "customerRef": "cus_QxY12AbC"
    }
  ],
  "risk": {
    "level": "normal",
    "reasons": [],
    "firstPaymentAt": "2026-10-14T09:30:00.000Z",
    "totalPaid": {
      "amount": "119000000",
      "currency": "EUR"
    }
  },
  "portalLastLoginAt": null,
  "testClockId": null,
  "metadata": {},
  "createdAt": "2026-10-14T09:00:00.000Z",
  "updatedAt": "2026-10-14T09:30:00.000Z",
  "deletedAt": null
}
```
