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 customercustomers_credit_balance_adjustments_create: Adjust a customer's credit balancecustomers_credit_balance_get: Get a customer's credit balancecustomers_delete: Delete a customercustomers_entitlement_overrides_create: Override an entitlementcustomers_entitlement_overrides_delete: Delete an entitlement overridecustomers_entitlement_overrides_list: List a customer's entitlement overridescustomers_entitlements_get: Get a customer's entitlementscustomers_evidence_sets_list: List a customer's location evidencecustomers_get: Get a customercustomers_list: List customerscustomers_tax_ids_add: Add a tax ID to a customercustomers_tax_ids_delete: Remove a tax ID from a customercustomers_update: Update a customer
customers_create
Create a customer. Operation customers.create · 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_takenotherwise). 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 asinvalid. Dry runs simulate the check. - Type: inferred (spec §13.1):
businesswith a tax ID that isn'tinvalid, or withbusinessNamewhen you declaretype: business; otherwise what you declared. Tax is still decided per sale: only avalidtax 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
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: 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.reasonsays which. Nothing is stored. Types Ownrecord can't check (e.g.au_abn) are stored asunverifiedinstead.customer_email_taken: Another active customer of the brand has this email address in this mode.resource_not_found: No test clock withtestClockIdexists for the brand in test mode.test_clock_mode:testClockIdwas given in live mode: test clocks belong to test mode.test_clock_advancing: The test clock is advancing: wait until it is ready.test_clock_customer_limit: The test clock already has 10 customers.idempotency_key_reused: The idempotency key was used before with another input.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.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:
{
"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:
{
"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:
{
"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:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: The amount is zero, not whole minor units or malformed, or the reason is missing.resource_not_found: This brand has no such customer in this mode.customer_deleted: The customer was deleted.credit_balance_insufficient: A negative adjustment takes back more than the balance (details.balance).idempotency_key_reused: The idempotency key was used before for another request.version_conflict: Another posting moved the balance at the same moment; retry.approval_required: The organization's approval policy needs a second person for this; nothing changed yet.approval_rejected: The same request was sent again after it was rejected.approval_expired: The same request was sent again after its approval request expired.stale_approval: The approved request could no longer run as requested.fresh_auth_required: Your last sign-in is older than 15 minutes.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't adjust credit balances (customers:admin: owners and admins).livemode_not_allowed: You have no live-mode access, so you can't use live invoices.
Example
A goodwill credit after an outage
Arguments:
{
"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:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a customer ID (cus_…).resource_not_found: This brand has no such customer in this mode.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).permission_denied: You can't read this brand's customers (customers:read).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:
{
"id": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed:idisn't a customer ID, or the reason is too long.resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).customer_deleted: The customer was already deleted.version_conflict: The customer changed at the same moment; retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't delete this brand's customers (customers:admin: owners and admins).
Example
Delete a duplicate customer
Arguments:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
"reason": "Duplicate of cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: A field is missing or malformed, the reason is empty, orexpiresAtisn't in the future.resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).customer_deleted: The customer was deleted.entitlement_feature_not_found: The brand has no feature with this key in this mode.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: The customer already has 100 active overrides.idempotency_key_reused: The idempotency key was used before with another input.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't change this brand's customers' entitlements (customers:write: owners, admins and support).
Examples
Extra seats for a month
Arguments:
{
"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:
{
"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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"featureKey": "feature.export",
"value": true,
"reason": "Beta tester",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: An ID is malformed, or the reason is missing or too long.resource_not_found: This brand has no such customer in this mode, or the customer has no override with thisoverrideId.version_conflict: The override changed at the same moment; retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't change this brand's customers' entitlements (customers:write: owners, admins and support).
Example
End a goodwill grant early
Arguments:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"overrideId": "ceo_01m4ty3c2b5qx7mhd9k8w6s0b2",
"reason": "Migration finished",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a customer ID, or a filter or pagination value is invalid.resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't read this brand's customers (customers:read).
Example
Overrides that apply now
Arguments:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"active": true,
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a customer ID (cus_…).resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a customer ID, or a pagination value is invalid.resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed:idisn't a customer ID (cus_…).resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't read this brand's customers (customers:read).
Examples
A business customer
Arguments:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0ab",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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,businessorunknown.
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_idwith an organization or entity key; a brand key acts on its own brand. - Read-only: changes nothing;
idempotency_keyis ignored.
Errors
validation_failed: A filter or pagination value is invalid.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't read this brand's customers (customers:read).
Examples
Find a customer by email
Arguments:
{
"email": "ana@example.com",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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:
{
"type": "business",
"limit": 2,
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed: A field is missing or malformed.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.reasonsays which. Nothing is stored. Types Ownrecord can't check (e.g.au_abn) are stored asunverifiedinstead.resource_not_found: This brand has no such customer in this mode (customers belong to one brand and one mode).customer_deleted: The customer was deleted.customer_tax_id_exists: The customer already has this tax ID (details.tax_id_id).customer_tax_id_limit: The customer already has 5 tax IDs.version_conflict: The customer changed at the same moment; retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.permission_denied: You can't change this brand's customers (customers:write: owners, admins and support).
Example
Add an Austrian VAT ID
Arguments:
{
"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:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
validation_failed:idortaxIdIdis malformed.resource_not_found: This brand has no such customer in this mode, or the customer has no tax ID with thistaxIdId.customer_deleted: The customer was deleted.version_conflict: The customer changed at the same moment; retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.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:
{
"id": "cus_01m4ty3c2b5qx7mhd9k8w6s0aa",
"taxIdId": "ATU00000000",
"brand_id": "brd_01jbx4m3v8e9t0s6k2c5n7q1wz",
"idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
Result output:
{
"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 · 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_idwith an organization or entity key; a brand key acts on its own brand. - Idempotency: the result returns the
idempotency_keyused, 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: truechecks permissions and rules and computes the result, then rolls everything back.
Errors
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: This brand has no such customer in this mode (customers belong to one brand and one mode).customer_deleted: The customer was deleted.customer_email_taken: Another active customer of the brand has the new email address in this mode.version_conflict: The customer changed at the same moment; retry.scope_required: The request names no brand (an org or entity key withoutOwnrecord-Brand).livemode_not_allowed: You have no live-mode access, so you can't act on live customers.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:
{
"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:
{
"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
}