Working with Customers
Introduction
A Customer is a reusable payer identity. Instead of re-collecting billing details and card data on every charge, you create a customer once, attach payment methods to it, and then charge that customer as often as you need.
Customers unlock:
- Saved payment methods — a payer's stored cards and bank accounts follow them between sessions.
- One-click / repeat billing — charge a customer without passing a payment method id.
- Payer history — list every payment, payment intent, and payment method for a payer.
- Multi-location payers — the same customer can be attached to several merchant accounts under one business.
- Card Account Updater — keep a payer's stored cards current automatically. See Enrolling Customers in Account Updater.
Customer ids are prefixed cus_.
Before you start
All requests use your API key in the x-api-key header. See
API Key Usage.
curl "https://<api-host>/customers" \
-H "x-api-key: key_123456789" \
-H "x-business-id: bus_123232"
A few concepts to keep straight:
| Concept | What it means |
|---|---|
business_id | The business the customer belongs to. All customers are under the business hierarchy. Set at creation from the account you create under. Immutable. |
primary_account_id | The merchant account that owns the customer's billing relationship. Can be changed to another attached account. This is the account that gets billed for value added services like account updater |
| Attached accounts | Every account the customer may transact with. A customer always has at least one — the account you create it under. Adding customers to accounts allows for the flexibility at checkout when using the method at multiple locations/accounts |
A customer can only be charged through an account it is actively attached to.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /customers | Create a customer |
GET | /customers | List customers |
GET | /customers/{id} | Fetch one customer |
PATCH | /customers/{id} | Update a customer |
DELETE | /customers/{id} | Delete a customer |
GET | /customers/{id}/accounts | List accounts attached to the customer |
POST | /customers/{id}/accounts | Attach another account |
DELETE | /customers/{id}/accounts/{accountId} | Detach an account |
PUT | /customers/{id}/primary-account | Change the primary (billing) account |
GET | /customers/{id}/payments | List the customer's payments |
GET | /customers/{id}/payment_intents | List the customer's payment intents |
GET | /customers/{id}/payment_methods | List the customer's payment methods |
POST | /payment_methods/{id}/attach | Attach a payment method to a customer |
POST | /payment_methods/{id}/detach | Detach a payment method from a customer |
1. Create a customer
account_id is required — it becomes the customer's initial attached account and its
primary_account_id. Everything else is optional.
curl -X POST "https://<api-host>/customers" \
-H "x-api-key: key_123456789" \
-H "Content-Type: application/json" \
-d '{
"account_id": "acct_12345678",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"phone": "555-123-4567",
"description": "Preferred customer, net-30",
"address": {
"name": "Jane Doe",
"line1": "123 Main Street",
"line2": "Suite 400",
"city": "Houston",
"state": "TX",
"postal_code": "77002",
"country": "US"
},
"shipping_address": {
"line1": "500 Warehouse Row",
"city": "Houston",
"state": "TX",
"postal_code": "77003",
"country": "US"
},
"tax_exempt": "none",
"user_fields": {
"crm_id": "CRM-99182",
"segment": "wholesale"
}
}'
Response:
{
"id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"entity": "customer",
"business_id": "bus_12345678",
"primary_account_id": "acct_12345678",
"name": "Jane Doe",
"email": "jane.doe@example.com",
"phone": "555-123-4567",
"description": "Preferred customer, net-30",
"address": {
"name": "Jane Doe",
"line1": "123 Main Street",
"line2": "Suite 400",
"city": "Houston",
"state": "TX",
"postal_code": "77002",
"country": "US"
},
"shipping_address": {
"line1": "500 Warehouse Row",
"city": "Houston",
"state": "TX",
"postal_code": "77003",
"country": "US"
},
"tax_exempt": "none",
"cau_enrolled": false,
"user_fields": {
"crm_id": "CRM-99182",
"segment": "wholesale"
},
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
Field reference
| Field | Type | Notes |
|---|---|---|
account_id | string (required, write-only) | Initial account. Becomes primary_account_id. |
name | string, 1–200 | Person or business name. |
email | string, email, ≤255 | Contact email. |
phone | string, 7–50 | Contact phone. |
description | string, ≤500 | Free-form internal note. |
address | Address | Billing address. |
shipping_address | Address | Shipping address, same shape as address. |
default_payment_method | string, ≤50 | A payment method id. Must not already be linked to another customer. |
tax_exempt | none | exempt | reverse | Defaults to none. |
cau_enrolled | boolean | Card Account Updater. See the Account Updater guide. |
user_fields | object | Your own key/value metadata. Size-limited. |
Address object: name, line1, line2, city, state, postal_code, country —
all optional. country is an ISO 3166-1 alpha-2 code (US, CA, GB).
2. List customers
curl "https://<api-host>/customers?limit=20" \
-H "x-api-key: key_123456789" \
-H "x-account-id: acct_12345678"
Scope the list with a header:
| Header | Result |
|---|---|
x-account-id | Customers attached to that merchant account. |
x-business-id | Every customer under that business, across its accounts. |
Query parameters:
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Defaults to 10, maximum 50. |
starting_after | string | Cursor — the id of the last customer from the previous page. |
{
"data": [
{
"id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"entity": "customer",
"business_id": "bus_12345678",
"primary_account_id": "acct_12345678",
"name": "Jane Doe",
"tax_exempt": "none",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
],
"meta": {
"has_more": true,
"limit": 20
}
}
Paginate by passing the last id back:
curl "https://<api-host>/customers?limit=20&starting_after=cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE" \
-H "x-api-key: key_123456789" \
-H "x-account-id: acct_12345678"
3. Fetch and update a customer
curl "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE" \
-H "x-api-key: key_123456789"
PATCH applies only the fields you send:
curl -X PATCH "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE" \
-H "x-api-key: key_123456789" \
-H "Content-Type: application/json" \
-d '{
"email": "jane.doe@newdomain.com",
"phone": "555-987-6543"
}'
address, shipping_address, and user_fields are replaced in full when you include
them. Sending {"address": {"postal_code": "77004"}} wipes line1, city, and state.
Send the whole object, or omit the field entirely to leave it untouched.
Correct way to change one line of an address:
{
"address": {
"name": "Jane Doe",
"line1": "123 Main Street",
"line2": "Suite 900",
"city": "Houston",
"state": "TX",
"postal_code": "77002",
"country": "US"
}
}
business_id, primary_account_id, created_at, and updated_at are not writable.
To move the primary account, use
PUT /customers/{id}/primary-account.
4. Attach payment methods
A payment method is bound to at most one customer at a time, and a customer can hold up to 25 payment methods.
Option A — attach at creation time (recommended)
Pass customer_id when you create a Payment Method Intent. The resulting payment method is
attached to the customer automatically the moment it is created, adopted as the customer's
default if they don't have one yet, and enrolled in the Card Account Updater if the
customer is enrolled.
curl -X POST "https://<api-host>/payment_method_intents" \
-H "x-api-key: key_123456789" \
-H "x-account-id: acct_12345678" \
-H "Content-Type: application/json" \
-d '{
"payment_method_types": ["card"],
"customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"validate": true
}'
See Storing Card and Bank Data for the full Payment Method Intent flow.
Option B — attach an existing payment method
curl -X POST "https://<api-host>/payment_methods/pm_1234567890/attach" \
-H "x-api-key: key_123456789" \
-H "Content-Type: application/json" \
-d '{ "customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE" }'
Returns the updated payment method:
{
"id": "pm_1234567890",
"entity": "payment_method",
"account_id": "acct_12345678",
"customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"payment_method_type": "card",
"source_type": "vgs",
"status": "active",
"cau_enrolled": false,
"cau_enrollment_status": "not_enrolled",
"card": {
"brand": "visa",
"last_four_digits": "4242",
"exp_month": "12",
"exp_year": "2028"
},
"created_at": "2026-01-15T10:32:00Z",
"updated_at": "2026-01-15T10:35:00Z"
}
Rules enforced on attach:
- The payment method's owning account must be one of the customer's attached accounts,
otherwise
422. - If the payment method is already attached to a different customer, the call fails
422— detach it first. - Re-attaching to the same customer is a no-op and succeeds.
- If the customer has no
default_payment_methodyet, this attach becomes the default. - Exceeding 25 payment methods returns
422.
Detach
curl -X POST "https://<api-host>/payment_methods/pm_1234567890/detach" \
-H "x-api-key: key_123456789"
No body. If the detached payment method was the customer's default, the default is
cleared. If the card was enrolled in the Card Account Updater it is unsubscribed first.
Detaching a payment method that isn't attached to anyone returns 422.
List a customer's payment methods
curl "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/payment_methods" \
-H "x-api-key: key_123456789"
Because payment methods are capped per customer, this endpoint returns the full set in one
response — meta.has_more is always false.
5. Work with multiple accounts
A customer can transact with several merchant accounts, as long as those accounts belong to the same business.
List attached accounts
curl "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/accounts" \
-H "x-api-key: key_123456789"
{
"data": [
{
"id": "cusacct_4YBwcUzU8QbgSRHqo6Z2Agg",
"entity": "customer_account",
"customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"account_id": "acct_12345678",
"business_id": "bus_12345678",
"partner_id": "par_12345678",
"attached_by": "usr_12345678",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
],
"meta": { "has_more": false, "limit": 10 }
}
Attach another account
curl -X POST "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/accounts" \
-H "x-api-key: key_123456789" \
-H "Content-Type: application/json" \
-d '{ "account_id": "acct_87654321" }'
The target account must belong to the same business as the customer, otherwise 422.
A customer enrolled in the Card Account Updater cannot be attached to an account that
doesn't have Account Updater enabled — that also returns 422.
Change the primary account
curl -X PUT "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/primary-account" \
-H "x-api-key: key_123456789" \
-H "Content-Type: application/json" \
-d '{ "account_id": "acct_87654321" }'
The target must already be attached, otherwise 422. Returns the updated customer.
Detach an account
curl -X DELETE "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/accounts/acct_87654321" \
-H "x-api-key: key_123456789"
You cannot detach the primary account — that returns 409. Change the primary account
first. If the customer's default payment method lived on the detached account, the default
is cleared.
6. Charge a customer
Pay with the customer's default payment method
Provide exactly one of payment_method_id or customer_id:
curl -X POST "https://<api-host>/payment_intents/pi_2SG6ibTHTo4N6Sdx6Oh66IGg3o3/payments" \
-H "x-api-key: key_123456789" \
-H "x-account-id: acct_12345678" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE",
"description": "Invoice 10041"
}'
This fails with a validation error when:
- the customer is not attached to the account the payment runs under,
- the customer has no
default_payment_methodset, or - the stored default has since been detached from the customer.
Associate a payment intent with a customer
curl -X POST "https://<api-host>/payment_intents" \
-H "x-api-key: key_123456789" \
-H "x-account-id: acct_12345678" \
-H "Content-Type: application/json" \
-d '{
"amount": 1000,
"customer_id": "cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE"
}'
The association is set-once. You may PATCH customer_id onto an intent that doesn't
have one yet, but changing it to a different customer returns 422. An intent also picks
up the association automatically when it is completed by a payment made with a customer's
payment method.
Review payer history
curl "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/payments?limit=20" \
-H "x-api-key: key_123456789"
curl "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE/payment_intents?limit=20" \
-H "x-api-key: key_123456789"
Both return newest-first and accept limit, starting_after, and ending_before
(pass an id to page backwards).
7. Delete a customer
curl -X DELETE "https://<api-host>/customers/cus_2XAvbTyT7PafRQGpn5Y1Zffl3XE" \
-H "x-api-key: key_123456789"
Delete returns the customer object that was removed, then cascades:
- Every attached account is detached.
- Every payment method linked to the customer is unlinked (the payment methods themselves are not deleted, they simply stop belonging to a customer).
After delete, every read of that customer returns 404, and any payment method still
carrying the deleted customer's id can no longer be charged — create a new payment method
for that payer.
Error reference
| Status | When |
|---|---|
400 | Malformed body, user_fields too large, or account_id disagreeing with the x-account-id header. |
401 | Missing or invalid API key. |
404 | Customer doesn't exist, is deleted, or isn't visible to your key. Cross-account lookups return 404, not 403. |
409 | Detaching the primary account. |
422 | Cross-business account attach, payment-method-per-customer limit, payment method already on another customer, changing an existing customer association, or a Card Account Updater compatibility conflict. |
See Error Handling for the response body shape.
Best practices
- Create the customer before you collect card data. Passing
customer_idon the Payment Method Intent removes the separate attach call and keeps Account Updater enrollment automatic. - Store your own identifier in
user_fields(crm_id,subscriber_id) so you can reconcile without a second lookup table. - Send whole nested objects on
PATCH. Partialaddressupdates silently drop the fields you left out. - Don't reuse a payment method across customers. Detach first; a payment method belongs to exactly one customer.
- Check
default_payment_methodbefore charging bycustomer_id. A detach can clear it out from under you.