Create a customer, vault a bank account, and charge that saved payment method on a later transaction.
Use this flow when you want to store a payer once and reuse their bank account on later ACH, RCC, or RTP transactions without sending the full account details on every charge.
POST /customers → customerUuid
POST /customers/payment-methods → paymentMethodUuid
GET /customers/{customerUuid} → look up the customer
GET /customers/{customerUuid}/payment-methods → list saved methods
PUT /transactions → charge the vaulted method
Authorization
Authenticate with Basic (API key) or Bearer (JWT from POST /auth/login).
| Credential | entityUuid query param |
|---|---|
| Basic | Optional. The acting merchant is taken from the key. |
| Bearer | Required. The merchant you are acting as. |
The routes below require these AVP actions on the caller:
| Step | Method and path | Action |
|---|---|---|
| Create customer | POST /customers | CreateCustomer |
| Create payment method | POST /customers/payment-methods | ManagePaymentMethods |
| Get customer | GET /customers/{customerUuid} | GetCustomer |
| List payment methods | GET /customers/{customerUuid}/payment-methods | ViewPaymentMethods |
| Charge | PUT /transactions | CreateTransaction |
CreateTransaction is already API-key eligible. CreateCustomer and ManagePaymentMethods succeed for API keys only when those actions are in the live AVP ApiKeyActionGroup (and, for merchants on the rbac canary, when APIKey: true in the permissions registry). A 403 with no useful body usually means the action is missing from that group or the role.
Optional later lookups also use ListCustomers. Those are separate actions.
1. Create a customer
POST /customers?entityUuid={merchantUuid}
Consumer:
{
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected]",
"phone": "5555550100",
"accountHolderType": "consumer"
}Organization (companyName is required when accountHolderType is organization):
{
"companyName": "Acme LLC",
"accountHolderType": "organization",
"firstName": "Pat",
"lastName": "Lee"
}If you omit accountHolderType, send either a person (firstName + lastName) or companyName.
201 response (envelope abbreviated):
{
"status": "success",
"data": {
"companyUuid": "11111111-1111-1111-1111-111111111111",
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"firstName": "Jane",
"lastName": "Doe",
"status": "active"
}
}Keep data.customerUuid. There is no unique constraint on customerId or email: retrying create mints another customer.
The customer must stay active. An inactive customer is rejected on PUT /transactions.
2. Vault a bank account
POST /customers/payment-methods?entityUuid={merchantUuid}
{
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"type": "bank",
"accountType": "Checking",
"accountNumber": "123456789",
"routingNumber": "021000021",
"nickname": "Payroll checking",
"isDefault": true
}| Field | Notes |
|---|---|
type | bank |
accountType | Checking or Savings. |
accountNumber / routingNumber | Required. Duplicate bank account + routing for the same customer returns 409. |
isDefault | Optional. The first method on a customer becomes default even if this is false. |
201 response (abbreviated):
{
"status": "success",
"data": {
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"paymentMethodUuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"type": "bank",
"accountType": "Checking",
"lastFourDigits": "6789",
"displayName": "Checking ****6789",
"isDefault": true
}
}Keep data.paymentMethodUuid. Marking a method default does not make PUT /transactions pick it automatically. Always send paymentMethodUuid on the charge.
To change the default later:
POST /customers/{customerUuid}/payment-methods/{paymentMethodUuid}/set-default?entityUuid={merchantUuid}
3. Get a customer
GET /customers/{customerUuid}?entityUuid={merchantUuid}
No request body. Unknown UUID returns 404 Customer not found. Inactive customers are still returned; charging them is rejected.
{
"status": "success",
"data": {
"companyUuid": "11111111-1111-1111-1111-111111111111",
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected]",
"accountHolderType": "consumer",
"defaultPaymentMethod": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"status": "active"
}
}defaultPaymentMethod is the UUID of the default saved method when one is set.
4. List a customer's payment methods
GET /customers/{customerUuid}/payment-methods?entityUuid={merchantUuid}
{
"status": "success",
"data": {
"paymentMethods": [
{
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"paymentMethodUuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"type": "bank",
"accountType": "Checking",
"accountNumber": "123456789",
"routingNumber": "021000021",
"lastFourDigits": "6789",
"displayName": "Checking ****6789",
"isDefault": true
}
]
}
}An empty paymentMethods array means the customer exists but has no saved methods. A missing customer is 404.
One method: GET /customers/{customerUuid}/payment-methods/{paymentMethodUuid}. Unknown method UUID returns 404 Payment method not found.
5. Charge the saved method
PUT /transactions?entityUuid={merchantUuid}
Send both customerUuid and paymentMethodUuid. The server loads the vaulted bank account and overwrites paymentDetails with stored name, account type, account number, and routing number.
paymentDetails must still pass validation before that overwrite. Include a structurally valid object (name, accountType, accountNumber, routingNumber for ACH/RCC/RTP). Those values are ignored once the vault load succeeds.
{
"method": "ACH",
"type": "Debit",
"amount": 10000,
"standardEntryClassCode": "WEB",
"effectiveDate": "2026-09-24",
"customerUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"paymentMethodUuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"paymentDetails": {
"name": "Jane Doe",
"accountType": "Checking",
"accountNumber": "000000000",
"routingNumber": "021000021"
}
}amount is in cents (10000 = $100.00). Omit effectiveDate to use the next processing window.
201 means the transaction was accepted. Fraud and limit failures can still return 201 with status DECLINED or HELD in the body.
A saved bank method can be charged as ACH, RCC, or RTP. A mismatch returns 400 payment method type is incompatible with transaction method.
Linking without charging the vault
customerUuid alone attaches the transaction to the customer. It does not pull the default saved method. Omit paymentMethodUuid only when you are supplying real paymentDetails (or a Chirp/Plaid reference) yourself.
paymentMethodUuid without customerUuid returns 400 customerUuid is required when paymentMethodUuid is provided.
curl sketch
# 1. Customer
CUSTOMER=$(curl -sS -X POST \
-H "Authorization: Basic $ENCODED" \
-H "Content-Type: application/json" \
-d '{"firstName":"Jane","lastName":"Doe","accountHolderType":"consumer"}' \
"$API_BASE/customers?entityUuid=$MERCHANT_UUID")
CUSTOMER_UUID=$(echo "$CUSTOMER" | jq -r '.data.customerUuid')
# 2. Bank payment method
PM=$(curl -sS -X POST \
-H "Authorization: Basic $ENCODED" \
-H "Content-Type: application/json" \
-d "{\"customerUuid\":\"$CUSTOMER_UUID\",\"type\":\"bank\",\"accountType\":\"Checking\",\"accountNumber\":\"123456789\",\"routingNumber\":\"021000021\"}" \
"$API_BASE/customers/payment-methods?entityUuid=$MERCHANT_UUID")
PM_UUID=$(echo "$PM" | jq -r '.data.paymentMethodUuid')
# 3. Look up the customer
curl -sS \
-H "Authorization: Basic $ENCODED" \
"$API_BASE/customers/$CUSTOMER_UUID?entityUuid=$MERCHANT_UUID"
# 4. List saved payment methods
curl -sS \
-H "Authorization: Basic $ENCODED" \
"$API_BASE/customers/$CUSTOMER_UUID/payment-methods?entityUuid=$MERCHANT_UUID"
# 5. Charge
curl -sS -X PUT \
-H "Authorization: Basic $ENCODED" \
-H "Content-Type: application/json" \
-d "{\"method\":\"ACH\",\"type\":\"Debit\",\"amount\":10000,\"standardEntryClassCode\":\"WEB\",\"customerUuid\":\"$CUSTOMER_UUID\",\"paymentMethodUuid\":\"$PM_UUID\",\"paymentDetails\":{\"name\":\"Jane Doe\",\"accountType\":\"Checking\",\"accountNumber\":\"000000000\",\"routingNumber\":\"021000021\"}}" \
"$API_BASE/transactions?entityUuid=$MERCHANT_UUID"See Basic Authentication for encoding the API key.