Create a customer
The customers.create operation: POST /api/v1/customers in the REST API and the customers_create tool in the MCP server.
/api/v1/customersCreates 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.
At a glance
- REST:
POST /api/v1/customers - MCP tool:
customers_create - Operation ID:
customers.create - Scope: Brand
- Permission:
customers:write - Risk tier:
write - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
customers:writescope - Since: M4
Request
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The customer's email address. One active customer per address in a brand and mode. |
name | string | No | The person's name. |
businessName | string | No | The business's legal name, for business customers. |
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 | No | Language for checkout, the portal, emails and invoices, e.g. de. |
billingAddress | object | No | |
billingAddress.line1 | string | No | |
billingAddress.line2 | string | No | |
billingAddress.city | string | No | |
billingAddress.postalCode | string | No | |
billingAddress.region | string | No | |
billingAddress.country | string | Yes | ISO 3166-1 alpha-2 country code (required). |
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). |
taxIds[].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). |
taxIds[].value | string | Yes | The number as the customer gave it, with or without the country prefix and separators. |
taxIds[].country | string | Yes | The country that issued the tax ID (ISO 3166-1 alpha-2; EL = Greece). |
testClockId | clk_… ID | 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). |
Response
201 Created with a JSON object:
| Field | Type | Description |
|---|---|---|
id | cus_… ID | |
object | "customer" | |
livemode | boolean | |
entityId | ent_… ID | |
brandId | brd_… ID | |
email | string | |
name | string or null | |
businessName | string or null | |
locale | string or null | |
type | one of individual, business, unknown | Inferred (spec §13.1): business with a tax ID that isn't rejected, or with a business name when declared a business. Tax is decided per sale: only a valid tax ID makes a sale business-to-business. |
declaredType | one of individual, business, unknown | What you declared. |
billingAddress | object or null | |
taxIds | array of object | |
taxIds[].id | string | Identifies the tax ID on the customer: its canonical number, e.g. ATU12345678. |
taxIds[].type | string | eu_vat, gb_vat, ch_uid/ch_vat, no_org/no_vat, us_ein, or another type (unverified). |
taxIds[].value | string | The number as given. |
taxIds[].country | string | |
taxIds[].number | string or null | The canonical number (null for types Ownrecord can't check). |
taxIds[].verification | object | The latest check (spec §9.4). |
taxIds[].verification.status | one of valid, invalid, unavailable, format_only, unverified | valid, invalid, unavailable (the register couldn't answer), format_only (no register: format and checksum) or unverified (a type Ownrecord can't check). |
taxIds[].verification.source | string | Who decided: VIES, HMRC, CH_UID, FORMAT (format and checksum only) or NONE. |
taxIds[].verification.reason | string or null | Why, e.g. not_registered or service_unavailable. |
taxIds[].verification.checkedAt | date-time | |
taxIds[].verification.consultationNumber | string or null | The register's proof of the check (VIES request identifier, HMRC consultation number). |
taxIds[].verification.simulated | boolean | A fixture answer (development and test environments), not a register's. |
taxIds[].history | array of object | Earlier checks, newest first (at most 10). |
taxIds[].history[].status | one of valid, invalid, unavailable, format_only, unverified | valid, invalid, unavailable (the register couldn't answer), format_only (no register: format and checksum) or unverified (a type Ownrecord can't check). |
taxIds[].history[].source | string | Who decided: VIES, HMRC, CH_UID, FORMAT (format and checksum only) or NONE. |
taxIds[].history[].reason | string or null | Why, e.g. not_registered or service_unavailable. |
taxIds[].history[].checkedAt | date-time | |
taxIds[].history[].consultationNumber | string or null | The register's proof of the check (VIES request identifier, HMRC consultation number). |
taxIds[].history[].simulated | boolean | A fixture answer (development and test environments), not a register's. |
taxIds[].addedAt | date-time | |
taxCountryLocked | object or null | Where the customer's purchases are taxed (spec §9.3): set at the first successful payment, moved only when a top-up's evidence resolves to another country. |
taxExempt | object or null | Tax exemption (spec §9.5). Not applied yet: exemption certificates come in a later release. |
processorRefs | array of object | The customer at each payment processor used (set by checkout). |
processorRefs[].processorConnectionId | pcon_… ID | |
processorRefs[].customerRef | string | |
risk | object | |
risk.level | one of normal, elevated, blocked | |
risk.reasons | array of string | |
risk.firstPaymentAt | date-time or null | |
risk.totalPaid | money | |
portalLastLoginAt | date-time or null | |
testClockId | clk_… ID or null | Test mode: the test clock the customer was created on (spec §16.13). |
metadata | object | |
createdAt | date-time | |
updatedAt | date-time | |
deletedAt | date-time or null | When the customer was deleted (soft delete); null while active. |
Examples
A business in Austria with its VAT ID
VIES confirms the VAT ID, so the customer is a business.
POST /api/v1/customers
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/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"
}
}
Response (201):
{
"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
POST /api/v1/customers
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"email": "ana@example.com",
"name": "Ana Schmidt",
"type": "individual",
"billingAddress": {
"country": "DE"
}
}
Response (201):
{
"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
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | 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). | Fix the fields listed in details.issues and send the request again. |
tax_id_invalid | 400 | 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. | Check the number and its country for typing errors (details.reason says what is wrong), then send it again. A Northern Ireland business enters its GB VAT number. |
customer_email_taken | 409 | Another active customer of the brand has this email address in this mode. | Use the existing customer (details.customer_id, or customers.list with email), or give this customer another address. A deleted customer's address can be used again. |
resource_not_found | 404 | No test clock with testClockId exists for the brand in test mode. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
test_clock_mode | 400 | testClockId was given in live mode: test clocks belong to test mode. | Attach test clocks to test-mode customers only. Live customers bill at the real time. |
test_clock_advancing | 409 | The test clock is advancing: wait until it is ready. | Wait until the clock's status is ready (read it with test_clocks.get), then try again. An advance processes every due renewal, trial end and cancellation of the clock's subscriptions first. |
test_clock_customer_limit | 400 | The test clock already has 10 customers. | Create another test clock for more customers. A clock holds at most 10 customers. |
idempotency_key_reused | 409 | The idempotency key was used before with another input. | Use a new Idempotency-Key for a different request, or resend the original request unchanged. |
scope_required | 400 | The request names no brand (an org or entity key without Ownrecord-Brand). | Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (details.required_scope). |
livemode_not_allowed | 403 | You have no live-mode access, so you can't act on live customers. | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin. |
permission_denied | 403 | You can't change this brand's customers (customers:write: owners, admins and support). | Ask an organization owner or admin for a role or scope that allows this action. |
It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.