---
title: "Customers"
description: "One record per customer, in every environment — the anchor every balance, subscription, and entitlement check hangs off."
---

# Customers

The people who use your product.

A customer is one person or account that uses your app. QuotaStack keeps one
record per customer, in every environment.

Every credit balance, a [subscription](/docs/concepts/subscriptions), and an
[entitlement](/docs/concepts/entitlements) check all read from this one
record.

You can look up a customer by QuotaStack's own ID or by your own ID. Most
calls take either one.

## Which call do I want?

- You are creating a customer for the first time → `POST /v1/customers`
- You have your own user ID and want the customer → `GET /v1/customer-by-external-id/{external_id}`
- You have QuotaStack's customer ID and want the customer → `GET /v1/customers/{id}`
  Store the `external_id` you already use for that user. Then you never need to look up the QuotaStack ID at all.

8 of 8 operations documented in full.

## List customers (paginated).

`GET /v1/customers`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Nothing. A read has no side effects.
- **Fires:** No webhooks.

This endpoint lists every customer, newest first. Add `search` to match part of `external_id` or `display_name`.

### Query parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `search` | string | optional | Only customers whose `external_id` or `display_name` has this text. If you leave it out: You get the full list back. |
| `cursor` | string | optional | Opaque pagination cursor from the previous page's `pagination.next_cursor`. Omit to start at the first page. |
| `limit` | integer | optional | Page size. Default 20, max 100. |

### Response 200

```json
{
  "data": [
    {
      "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
      "external_id": "user_42",
      "environment": "live",
      "display_name": "Ada Lovelace",
      "overage_policy": null,
      "low_balance_threshold_mc": null,
      "metadata": {},
      "created_at": "2026-07-20T09:00:00Z",
      "updated_at": "2026-07-20T09:00:00Z"
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `data` | array | required | One row per customer, newest first. |
| `pagination` | object | required | Cursor-based pagination envelope. Returned on every list endpoint. No `total` — cursor-only. |

### Errors

- `400` — The `cursor` value is not one QuotaStack issued. Omit it to start from the first page. See [bad-request](/docs/api/errors/bad-request).
- `401` — Missing or invalid authentication.
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)

## Create a customer.

`POST /v1/customers`

- **Idempotency-Key:** Required. Returns 422 without it.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** One new customer record. Nothing else changes.
- **Fires:** No webhooks.

Creating a customer makes one record, nothing else. QuotaStack grants no [credits](/docs/concepts/credits) and starts no [subscription](/docs/concepts/subscriptions). Do both as separate calls. Send your own `external_id` so later calls can use it instead of the QuotaStack UUID.

### Header parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `Idempotency-Key` | string | required | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with `422`, before any change is made. Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries `X-Idempotent-Replayed: true`. Replays are available for 24 hours. Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example `topup:{payment_intent_id}` or `renewal:{subscription_id}:{period_start}`. |

### Body parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `external_id` | string | required | Your own ID for this customer. Must be unique for your tenant. If you leave it out: QuotaStack rejects the call. This field is required. |
| `display_name` | string | optional | A label people see in the dashboard. Up to 200 characters. If you leave it out: QuotaStack stores `null`. The dashboard shows the `external_id` instead. |
| `overage_policy` | string | optional | The customer's own value wins when set. The tenant default [overage](/docs/concepts/credits) rule applies when it is not. If you leave it out: Left out, QuotaStack stores `null`. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `` `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | Turns on the `credit.low_balance` [webhook](/docs/concepts/webhooks) at this amount, for this customer only. Zero turns it off. Unit: millicredits, where 1 credit is 1000 millicredits. If you leave it out: Left out, QuotaStack uses the tenant default. Range: 0–∞. |
| `metadata` | object | optional | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: Left out, QuotaStack stores `{}`. |

### Request

```json
{
  "external_id": "user_42",
  "display_name": "Ada Lovelace"
}
```

### Response 201

```json
{
  "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
  "external_id": "user_42",
  "environment": "live",
  "display_name": "Ada Lovelace",
  "overage_policy": null,
  "low_balance_threshold_mc": null,
  "metadata": {},
  "created_at": "2026-07-20T09:00:00Z",
  "updated_at": "2026-07-20T09:00:00Z"
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | QuotaStack-assigned customer UUID. |
| `tenant_id` | string (uuid) | required |  |
| `environment` | string | required | Which environment this resource lives in. Determined by the API key prefix used (`qs_live_…` → live, `qs_test_…` → sandbox). `live` `sandbox` |
| `external_id` | string | required | Your own ID for this customer, set when you created it. |
| `display_name` | string | optional | A label people see in the dashboard. `null` until you set one. |
| `overage_policy` | string | optional | The value stored for this customer. `null` means none is set; the tenant default [overage](/docs/concepts/credits) rule applies. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | The `credit.low_balance` [webhook](/docs/concepts/webhooks) threshold for this customer. `null` means the tenant default applies. Unit: millicredits, where 1 credit is 1000 millicredits. Range: 0–∞. |
| `metadata` | object | required | Your own key-value tags, unchanged since the last write. |
| `created_at` | string (date-time) | required |  |
| `updated_at` | string (date-time) | required |  |

### Errors

- `400` — QuotaStack could not parse the request body as JSON. See [bad-request](/docs/api/errors/bad-request).
- `401` — Missing or invalid authentication.
- `409` — The `external_id` is taken. Fetch that customer instead of making a new one. See [conflict](/docs/api/errors/conflict).
- `422` — `external_id` is missing, `display_name` is over 200 characters, or `low_balance_threshold_mc` is negative. The response names the field. See [validation-error](/docs/api/errors/validation-error).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)
- [Credits](/docs/concepts/credits)
- [Subscriptions](/docs/concepts/subscriptions)

## Retrieve a customer by QuotaStack id.

`GET /v1/customers/{id}`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Nothing. A read has no side effects.
- **Fires:** No webhooks.

This call finds one customer. Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID. An unknown `id` never creates a customer here, unlike a [credit](/docs/concepts/credits) grant. QuotaStack returns `404` instead.

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID. |

### Response 200

```json
{
  "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
  "external_id": "user_42",
  "environment": "live",
  "display_name": "Ada Lovelace",
  "overage_policy": null,
  "low_balance_threshold_mc": null,
  "metadata": {},
  "created_at": "2026-07-20T09:00:00Z",
  "updated_at": "2026-07-20T09:00:00Z"
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | QuotaStack-assigned customer UUID. |
| `tenant_id` | string (uuid) | required |  |
| `environment` | string | required | Which environment this resource lives in. Determined by the API key prefix used (`qs_live_…` → live, `qs_test_…` → sandbox). `live` `sandbox` |
| `external_id` | string | required | Your own ID for this customer, set when you created it. |
| `display_name` | string | optional | A label people see in the dashboard. `null` until you set one. |
| `overage_policy` | string | optional | The value stored for this customer. `null` means none is set; the tenant default [overage](/docs/concepts/credits) rule applies. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | The `credit.low_balance` [webhook](/docs/concepts/webhooks) threshold for this customer. `null` means the tenant default applies. Unit: millicredits, where 1 credit is 1000 millicredits. Range: 0–∞. |
| `metadata` | object | required | Your own key-value tags, unchanged since the last write. |
| `created_at` | string (date-time) | required |  |
| `updated_at` | string (date-time) | required |  |

### Errors

- `401` — Missing or invalid authentication.
- `404` — No customer here has this `id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)

## Update a customer by QuotaStack id.

`PATCH /v1/customers/{id}`

- **Idempotency-Key:** Required. Returns 422 without it.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Replaces `display_name`, `overage_policy`, and `low_balance_threshold_mc` with whatever you send. Merges `metadata` into what is already there, one key at a time. QuotaStack never removes a key you leave out.
- **Fires:** No webhooks.

This call changes one or more fields. A field you skip keeps its old value. Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID.

### Header parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `Idempotency-Key` | string | required | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with `422`, before any change is made. Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries `X-Idempotent-Replayed: true`. Replays are available for 24 hours. Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example `topup:{payment_intent_id}` or `renewal:{subscription_id}:{period_start}`. |

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID. |

### Body parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `display_name` | string | optional | A label people see in the dashboard, up to 200 characters. Send an empty string to clear it. If you leave it out: The old value stays. QuotaStack changes nothing. |
| `overage_policy` | string | optional | The customer's own value wins when set. The tenant default [overage](/docs/concepts/credits) rule applies when it is not. Send an empty string to clear it and fall back to the tenant default. If you leave it out: The old value stays. QuotaStack changes nothing. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `` `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | Turns on the `credit.low_balance` [webhook](/docs/concepts/webhooks) at this amount, for this customer only. Zero turns it off. Unit: millicredits, where 1 credit is 1000 millicredits. If you leave it out: The old value stays. QuotaStack changes nothing. Range: 0–∞. |
| `metadata` | object | optional | Your own key-value tags. QuotaStack merges this into what is already there, one key at a time. A key you send overwrites its old value. A key you leave out keeps its old value. If you leave it out: The old value stays. QuotaStack changes nothing. There is no way to remove a key through this endpoint. |

### Request

```json
{
  "display_name": "Ada Lovelace"
}
```

### Response 200

```json
{
  "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
  "external_id": "user_42",
  "environment": "live",
  "display_name": "Ada Lovelace",
  "overage_policy": null,
  "low_balance_threshold_mc": null,
  "metadata": {},
  "created_at": "2026-07-20T09:00:00Z",
  "updated_at": "2026-07-20T09:00:00Z"
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | QuotaStack-assigned customer UUID. |
| `tenant_id` | string (uuid) | required |  |
| `environment` | string | required | Which environment this resource lives in. Determined by the API key prefix used (`qs_live_…` → live, `qs_test_…` → sandbox). `live` `sandbox` |
| `external_id` | string | required | Your own ID for this customer, set when you created it. |
| `display_name` | string | optional | A label people see in the dashboard. `null` until you set one. |
| `overage_policy` | string | optional | The value stored for this customer. `null` means none is set; the tenant default [overage](/docs/concepts/credits) rule applies. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | The `credit.low_balance` [webhook](/docs/concepts/webhooks) threshold for this customer. `null` means the tenant default applies. Unit: millicredits, where 1 credit is 1000 millicredits. Range: 0–∞. |
| `metadata` | object | required | Your own key-value tags, unchanged since the last write. |
| `created_at` | string (date-time) | required |  |
| `updated_at` | string (date-time) | required |  |

### Errors

- `400` — QuotaStack could not parse the request body as JSON. See [bad-request](/docs/api/errors/bad-request).
- `401` — Missing or invalid authentication.
- `404` — No customer here has this `id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `422` — `display_name` is over 200 characters, or `low_balance_threshold_mc` is negative. The response names the field. See [validation-error](/docs/api/errors/validation-error).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)

## Soft-delete a customer by QuotaStack id.

`DELETE /v1/customers/{id}`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Sets `deleted_at` on the customer. Nothing more. The [credit](/docs/concepts/credits) account stays. Active [subscriptions](/docs/concepts/subscriptions) stay. Open [reservations](/docs/concepts/reservations) stay. [Ledger](/docs/concepts/credits) history stays. To wind the customer down fully, cancel the [subscription](/docs/concepts/subscriptions) and zero the balance yourself.
- **Fires:** No webhooks.

A delete here is soft. QuotaStack sets `deleted_at` and nothing else. Every later read skips this customer. A second delete on the same `id` returns `404`, not a second success. Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID. You can later make a new customer with the same `external_id`. QuotaStack treats it as a different record, with a new `id`.

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | Your own `external_id`, or the QuotaStack UUID. QuotaStack tries `external_id` first, then the UUID. |

### Errors

- `401` — Missing or invalid authentication.
- `404` — No customer here has this `id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)
- [Adjust credits](/docs/api/credits#adjustCustomerCredits)

## Retrieve a customer by your `external_id`.

`GET /v1/customer-by-external-id/{external_id}`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Nothing. A read has no side effects.
- **Fires:** No webhooks.

The `external_id` twin of `getCustomer`. Only your own `external_id` matches here, never a QuotaStack UUID.

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `external_id` | string | required | Your own ID for this customer, set when you created it. QuotaStack looks up only this value, never the QuotaStack UUID. If you leave it out: QuotaStack rejects the call. This path segment is required. |

### Response 200

```json
{
  "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
  "external_id": "user_42",
  "environment": "live",
  "display_name": "Ada Lovelace",
  "overage_policy": null,
  "low_balance_threshold_mc": null,
  "metadata": {},
  "created_at": "2026-07-20T09:00:00Z",
  "updated_at": "2026-07-20T09:00:00Z"
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | QuotaStack-assigned customer UUID. |
| `tenant_id` | string (uuid) | required |  |
| `environment` | string | required | Which environment this resource lives in. Determined by the API key prefix used (`qs_live_…` → live, `qs_test_…` → sandbox). `live` `sandbox` |
| `external_id` | string | required | Your own ID for this customer, set when you created it. |
| `display_name` | string | optional | A label people see in the dashboard. `null` until you set one. |
| `overage_policy` | string | optional | The value stored for this customer. `null` means none is set; the tenant default [overage](/docs/concepts/credits) rule applies. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | The `credit.low_balance` [webhook](/docs/concepts/webhooks) threshold for this customer. `null` means the tenant default applies. Unit: millicredits, where 1 credit is 1000 millicredits. Range: 0–∞. |
| `metadata` | object | required | Your own key-value tags, unchanged since the last write. |
| `created_at` | string (date-time) | required |  |
| `updated_at` | string (date-time) | required |  |

### Errors

- `401` — Missing or invalid authentication.
- `404` — No customer here has this `external_id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)

## Update a customer by your `external_id`.

`PATCH /v1/customer-by-external-id/{external_id}`

- **Idempotency-Key:** Required. Returns 422 without it.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Replaces `display_name`, `overage_policy`, and `low_balance_threshold_mc` with whatever you send. Merges `metadata` into what is already there, one key at a time. QuotaStack never removes a key you leave out.
- **Fires:** No webhooks.

The `external_id` twin of `updateCustomer`. Only your own `external_id` matches here, never a QuotaStack UUID. A field you skip keeps its old value.

### Header parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `Idempotency-Key` | string | required | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with `422`, before any change is made. Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries `X-Idempotent-Replayed: true`. Replays are available for 24 hours. Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example `topup:{payment_intent_id}` or `renewal:{subscription_id}:{period_start}`. |

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `external_id` | string | required | Your own ID for this customer, set when you created it. QuotaStack looks up only this value, never the QuotaStack UUID. If you leave it out: QuotaStack rejects the call. This path segment is required. |

### Body parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `display_name` | string | optional | A label people see in the dashboard, up to 200 characters. Send an empty string to clear it. If you leave it out: The old value stays. QuotaStack changes nothing. |
| `overage_policy` | string | optional | The customer's own value wins when set. The tenant default [overage](/docs/concepts/credits) rule applies when it is not. Send an empty string to clear it and fall back to the tenant default. If you leave it out: The old value stays. QuotaStack changes nothing. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `` `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | Turns on the `credit.low_balance` [webhook](/docs/concepts/webhooks) at this amount, for this customer only. Zero turns it off. Unit: millicredits, where 1 credit is 1000 millicredits. If you leave it out: The old value stays. QuotaStack changes nothing. Range: 0–∞. |
| `metadata` | object | optional | Your own key-value tags. QuotaStack merges this into what is already there, one key at a time. A key you send overwrites its old value. A key you leave out keeps its old value. If you leave it out: The old value stays. QuotaStack changes nothing. There is no way to remove a key through this endpoint. |

### Request

```json
{
  "display_name": "Ada Lovelace"
}
```

### Response 200

```json
{
  "id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
  "external_id": "user_42",
  "environment": "live",
  "display_name": "Ada Lovelace",
  "overage_policy": null,
  "low_balance_threshold_mc": null,
  "metadata": {},
  "created_at": "2026-07-20T09:00:00Z",
  "updated_at": "2026-07-20T09:00:00Z"
}
```

### Response fields

| Field | Type | Required | Meaning |
|---|---|---|---|
| `id` | string (uuid) | required | QuotaStack-assigned customer UUID. |
| `tenant_id` | string (uuid) | required |  |
| `environment` | string | required | Which environment this resource lives in. Determined by the API key prefix used (`qs_live_…` → live, `qs_test_…` → sandbox). `live` `sandbox` |
| `external_id` | string | required | Your own ID for this customer, set when you created it. |
| `display_name` | string | optional | A label people see in the dashboard. `null` until you set one. |
| `overage_policy` | string | optional | The value stored for this customer. `null` means none is set; the tenant default [overage](/docs/concepts/credits) rule applies. `block` — Refuse usage once the balance runs out. `allow` — Let usage continue past the balance. QuotaStack floors it at zero and logs the rest as [overage](/docs/concepts/credits). `notify` — The same as `allow`. No [webhook](/docs/concepts/webhooks) fires for the [overage](/docs/concepts/credits) yet. `null` |
| `low_balance_threshold_mc` | integer (int64) | optional | The `credit.low_balance` [webhook](/docs/concepts/webhooks) threshold for this customer. `null` means the tenant default applies. Unit: millicredits, where 1 credit is 1000 millicredits. Range: 0–∞. |
| `metadata` | object | required | Your own key-value tags, unchanged since the last write. |
| `created_at` | string (date-time) | required |  |
| `updated_at` | string (date-time) | required |  |

### Errors

- `400` — QuotaStack could not parse the request body as JSON. See [bad-request](/docs/api/errors/bad-request).
- `401` — Missing or invalid authentication.
- `404` — No customer here has this `external_id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `422` — `display_name` is over 200 characters, or `low_balance_threshold_mc` is negative. The response names the field. See [validation-error](/docs/api/errors/validation-error).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)

## Soft-delete a customer by your `external_id`.

`DELETE /v1/customer-by-external-id/{external_id}`

- **Idempotency-Key:** Not used. This is a read.
- **Environment:** Sandbox and Live
- **Auth:** Tenant API key
- **Writes:** Sets `deleted_at` on the customer. Nothing more. The [credit](/docs/concepts/credits) account stays. Active [subscriptions](/docs/concepts/subscriptions) stay. Open [reservations](/docs/concepts/reservations) stay. [Ledger](/docs/concepts/credits) history stays.
- **Fires:** No webhooks.

The `external_id` twin of `deleteCustomer`. Only your own `external_id` matches here, never a QuotaStack UUID. A second delete on the same `external_id` returns `404`, not a second success. You can later make a new customer with the same `external_id`. QuotaStack treats it as a different record, with a new `id`.

### Path parameters

| Field | Type | Required | Meaning |
|---|---|---|---|
| `external_id` | string | required | Your own ID for this customer, set when you created it. QuotaStack looks up only this value, never the QuotaStack UUID. If you leave it out: QuotaStack rejects the call. This path segment is required. |

### Errors

- `401` — Missing or invalid authentication.
- `404` — No customer here has this `external_id`. The customer may already be deleted. See [not-found](/docs/api/errors/not-found).
- `429` — Rate limit exceeded.
- `500` — Unexpected server error.

### See also

- [Customer identification](/docs/concepts/customer-identification)
- [Adjust credits](/docs/api/credits#adjustCustomerCreditsByExternalId)
