Skip to main content

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:

ConceptWhat it means
business_idThe business the customer belongs to. All customers are under the business hierarchy. Set at creation from the account you create under. Immutable.
primary_account_idThe 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 accountsEvery 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

MethodPathDescription
POST/customersCreate a customer
GET/customersList customers
GET/customers/{id}Fetch one customer
PATCH/customers/{id}Update a customer
DELETE/customers/{id}Delete a customer
GET/customers/{id}/accountsList accounts attached to the customer
POST/customers/{id}/accountsAttach another account
DELETE/customers/{id}/accounts/{accountId}Detach an account
PUT/customers/{id}/primary-accountChange the primary (billing) account
GET/customers/{id}/paymentsList the customer's payments
GET/customers/{id}/payment_intentsList the customer's payment intents
GET/customers/{id}/payment_methodsList the customer's payment methods
POST/payment_methods/{id}/attachAttach a payment method to a customer
POST/payment_methods/{id}/detachDetach 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

FieldTypeNotes
account_idstring (required, write-only)Initial account. Becomes primary_account_id.
namestring, 1–200Person or business name.
emailstring, email, ≤255Contact email.
phonestring, 7–50Contact phone.
descriptionstring, ≤500Free-form internal note.
addressAddressBilling address.
shipping_addressAddressShipping address, same shape as address.
default_payment_methodstring, ≤50A payment method id. Must not already be linked to another customer.
tax_exemptnone | exempt | reverseDefaults to none.
cau_enrolledbooleanCard Account Updater. See the Account Updater guide.
user_fieldsobjectYour 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:

HeaderResult
x-account-idCustomers attached to that merchant account.
x-business-idEvery customer under that business, across its accounts.

Query parameters:

ParameterTypeNotes
limitintegerDefaults to 10, maximum 50.
starting_afterstringCursor — 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"
}'
Nested objects are replaced, not merged

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.

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_method yet, 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_method set, 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:

  1. Every attached account is detached.
  2. 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

StatusWhen
400Malformed body, user_fields too large, or account_id disagreeing with the x-account-id header.
401Missing or invalid API key.
404Customer doesn't exist, is deleted, or isn't visible to your key. Cross-account lookups return 404, not 403.
409Detaching the primary account.
422Cross-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_id on 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. Partial address updates 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_method before charging by customer_id. A detach can clear it out from under you.