Skip to main content

Payment instruments

A payment instrument is any way a customer makes a digital payment: credit card, debit card, direct debit, payment service provider (PayPal and similar), or digital wallet (Apple Pay, Google Pay).

In Payments AI a payment instrument lives on a customer record and you reference it by paymentInstrumentId on subscriptions and transactions.

Tokenization

Card data never reaches your server. The browser tokenizes the card details (typically with FramePay), and the resulting token is exchanged for a payment instrument attached to the customer. From there your code refers to the instrument by ID; the raw card data lives only inside Payments AI.

See Create customer with payment instrument for the full tutorial.

Lifecycle

Every payment instrument moves through a defined set of states. The state decides whether Payments AI can auto-charge it on a renewal.

Payment cards

StateMeaningCan be auto-charged?
inactive (or verification-needed)Card is registered but has not completed a payment.No
activeOne or more payments have succeeded on the card.Yes
expiredThe card's expiration date has been reached.No
deactivatedThe card has been manually deactivated.No

Alternative methods

For bank accounts, PayPal, and similar:

StateMeaningCan be auto-charged?
inactive (or verification-needed)Method is registered but has not completed a payment.No
activeOne or more payments have succeeded on the method.Yes
deactivatedThe method has been manually deactivated.No

Alternative methods do not have an expired state — they do not carry an expiration date.

Transitions

  • inactiveactive: a transaction succeeds against the instrument. Payments AI owns this transition; no API endpoint flips it directly.
  • activeexpired (cards only): the card's expiration date is reached.
  • activedeactivated: manual deactivation.

Once an instrument is expired or deactivated, it cannot be re-activated. The customer adds a new instrument instead.

Why this matters for subscriptions

A subscription created against an inactive instrument returns:

  • status: "pending"
  • recentInvoicePaymentFormUrl: the hosted payment form URL

Your frontend redirects the customer to that URL. The hosted form processes the first payment, runs 3DS if needed, activates the instrument, and redirects the customer back. From then on, renewals on that instrument auto-charge without a redirect.

See Create a subscription for the full flow.

API endpoints

Payment instruments are scoped to a customer. The collection endpoint lives under /customers/{customerId}/payment-instruments. Modify endpoints live under /payment-instruments/{paymentInstrumentId}.

Attach a token to a customer

curl -X POST \
"https://staging-api.payments.ai/v1/public-api/organizations/${ORGANIZATION_ID}/customers/${CUSTOMER_ID}/payment-instruments" \
-H 'Content-Type: application/json' \
-H "Authorization: ApiKey ${API_KEY}" \
-d '{ "token": "FZBGEZY8RU8WPUDEFP9Q" }'
FieldRequiredNotes
tokenYesThe token returned from the tokenization endpoint or FramePay. Single-use — consumed by this call.

Returns the created PaymentInstrument with its assigned id. See Attach a token to a customer for the full tokenization → attach sequence.

Get a customer's payment instruments

curl -X GET \
"https://staging-api.payments.ai/v1/public-api/organizations/${ORGANIZATION_ID}/customers/${CUSTOMER_ID}/payment-instruments?search=John" \
-H "Authorization: ApiKey ${API_KEY}"
Query parameterTypeNotes
searchstringFilter results (for example, by name on the billing address).

This is the canonical way to retrieve a customer's payment instruments. The response is masked — creditCard.creditCardLastFourDigits, creditCard.brand, creditCard.expireDate are exposed; the full PAN and CVV are never returned.

There is no GET /payment-instruments/{paymentInstrumentId} endpoint. Payment instruments are always retrieved through the customer-scoped collection above.

Update a payment instrument

All fields are optional — provide only what you want to change.

curl -X PATCH \
"https://staging-api.payments.ai/v1/public-api/organizations/${ORGANIZATION_ID}/payment-instruments/${PAYMENT_INSTRUMENT_ID}" \
-H 'Content-Type: application/json' \
-H "Authorization: ApiKey ${API_KEY}" \
-d '{
"billingAddress": {
"country": "DE",
"firstName": "John",
"lastName": "Doe",
"address": "Blue st 1",
"address2": "Apt 4",
"city": "Berlin",
"region": "BE",
"zip": "10115",
"email": "[email protected]",
"phoneNumber": "5127101111"
},
"creditCard": {
"expireMonth": 12,
"expireYear": 2030
},
"stickyGatewaySlug": "default",
"method": "payment-card",
"useAsBackup": true
}'
FieldNotes
billingAddressObject: country, firstName, lastName, organization, address, address2, city, region, zip, email, phoneNumber. Note: PATCH uses address (flat), unlike the create-customer endpoint which uses address1 nested in primaryAddress.customerAddress.
creditCardObject: only expireMonth and expireYear are mutable. The PAN, CVV, and last-four are immutable for the lifetime of the instrument.
stickyGatewaySlugEnum: default, paypal, klarna, coinbase, nmi, stripe. Pins the instrument to a specific gateway.
methodPaymentMethodEnum value (payment-card, ach, paypal, Apple Pay, GooglePay, etc. — full enum has ~150 values).
useAsBackupBoolean. Marks the instrument as a backup for retry/dunning flows.

Delete a payment instrument

curl -X DELETE \
"https://staging-api.payments.ai/v1/public-api/organizations/${ORGANIZATION_ID}/payment-instruments/${PAYMENT_INSTRUMENT_ID}" \
-H "Authorization: ApiKey ${API_KEY}"

Returns 200 OK with the full PaymentInstrument object and deletedAt set:

{
"id": "c114b68a-432e-4e94-b8ba-1658ef3258e2",
"createdAt": "2026-06-01T08:40:19.000Z",
"updatedAt": "2026-06-09T10:00:00.000Z",
"deletedAt": "2026-06-09T10:00:00.000Z",
"type": "payment-card",
"billingAddress": {
"firstName": "John",
"lastName": "Doe",
"country": "DE",
"address1": "Blue st 1",
"address2": "Apt 4",
"city": "Berlin",
"zip": "10115"
},
"bankAccount": null,
"creditCard": {
"creditCardLastFourDigits": "1234",
"brand": "Visa",
"expireYear": 2030,
"expireMonth": 12,
"expireDate": "12/30",
"stickyGatewaySlug": "default"
}
}

Address field name on response differs from PATCH: the response uses billingAddress.address1 (response-only). The PATCH request body uses billingAddress.address (flat). The two are not the same key — map your code accordingly.

Deletion moves the instrument to deactivated. The instrument cannot be re-activated; the customer adds a new instrument instead.