Payment Instruments
Save cards on file as payment instruments and reuse them for repeat charges, subscriptions, and other merchant-initiated transactions.
A payment instrument is a saved payment method — typically a card — kept on file for a customer so you can charge it again without re-collecting card data. Instruments are the backbone of repeat purchases, one-click upsells, and merchant-initiated charges such as subscription renewals.
Tokeflow stores only a non-sensitive reference and metadata for each instrument (pi_…, brand, last 4, expiry). The card itself is encrypted at collection time and is never handled by your servers.
Tokeflow is a payment orchestration platform. It does not process or settle funds — your connected payment providers do. A payment instrument is an orchestration-level reference that lets Tokeflow route a saved-card charge to the right provider on your behalf.
How instruments come to exist
You never create a payment instrument by sending raw card numbers to the API. The card is always collected by the Bridge SDK in the browser, and the ciphertext it returns must be minted for enrollment — purpose: 'instrument_enrollment' (standalone) or saveCard: true (inside a checkout session). See Tokenization → Options. From there, an instrument is created in one of three ways:
| Path | When to use it | What happens |
|---|---|---|
Zero-dollar verification — POST /sessions/:sessionId/payment-instruments or a transaction with charge_type: "setup_verification" | Save a card without billing it — a billing portal, a free trial. | The provider verifies the card (and runs 3DS where applicable) without capturing money, then the instrument is stored. |
Save while charging — POST /transactions with save_payment_instrument: true | A one-time purchase after which you want to offer one-click upsells. | The instrument is created before routing, the submitted ciphertext is charged, and the provider's reusable credential is kept on the approving connector. The response carries payment_instrument_id. |
Checkout session — the session's save_card flag, or a recurring offer | Hosted or SDK-driven checkouts. | The session enrolls the card itself; the charge response carries payment_instrument_id. |
The POST /merchants/:merchant_id/payment-instruments endpoint below stores an enrollment ciphertext directly, without a verification or a charge. An instrument created that way exists, but holds no provider credential until one successful cardholder-present charge or card verification runs through it — so it cannot be charged with credential_on_file yet.
The card-collection and enrollment flow is a client-side concern handled entirely by the Bridge SDK. See the SDK overview and card enrollment guides for the browser implementation. This page covers the server-side REST API for managing and using instruments after they exist.
:::tip Secure card collection Tokeflow's secure fields run inside isolated, cross-origin iframes. Card data is encrypted in the browser and never touches your servers, so a payment instrument is the only card reference your backend ever sees. :::
How instruments are used
Once an instrument exists, reference it by id wherever a saved card can be charged:
- One-click charges — pass
payment_instrument_idwithcredential_on_file: truetoPOST /api/v1/transactions. The charge uses the provider credential kept at enrollment — no card data, no CVV. See Stored-credential charges. - Saved card with a re-typed CVV — pass
payment_instrument_idtogether with a freshcard_ciphertext_idfor the same card, when you want a cardholder-present charge (3DS possible). - Subscriptions — set or change the card a subscription bills against via
change-payment-instrument. Future renewals run as merchant-initiated charges against the new instrument.
{
"payment_method": "credit_card",
"charge_type": "payment",
"country": "BR",
"customer_id": "cust_john_01",
"payment_instrument_id": "pi_a1b2c3",
"credential_on_file": true,
"amount": 15000,
"currency": "BRL"
}payment_instrument_id alone is refused with 400: the request must say how the card is presented — credential_on_file: true for a one-click charge, or a fresh card_ciphertext_id for a re-typed CVV.
The payment instrument object
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier, prefixed pi_. |
merchant_id | string | Merchant that owns the instrument (mrc_…). |
customer_id | string | Customer the instrument belongs to (cust_…). |
instrument_type | string | Instrument family. Currently card. |
card_brand | string | null | Card brand, e.g. visa, mastercard, amex. |
card_type | string | null | credit or debit. |
last4 | string | null | Last 4 digits of the card. |
bin | string | null | Issuer identification number (6- or 8-digit). |
issuer_country | string | null | ISO 3166-1 alpha-2 country of the issuer. |
exp_month | integer | null | Card expiration month (1–12). |
exp_year | integer | null | Card expiration year (4-digit). |
status | string | active, expired, or revoked. |
created_at | string | Creation timestamp (ISO 8601 UTC). |
Full card data is never stored by you. A payment instrument is only a stable reference (pi_…) plus non-sensitive metadata — brand, last 4, expiry, and issuer details. The primary account number cannot be read back through the API.
Status values
| Status | Meaning |
|---|---|
active | Usable for charges. |
expired | Past the card's expiration date; cannot be charged. |
revoked | Disabled for security or compliance reasons; cannot be charged. |
Example object
{
"id": "pi_a1b2c3",
"merchant_id": "mrc_123",
"customer_id": "cust_john_01",
"instrument_type": "card",
"card_brand": "visa",
"card_type": "credit",
"last4": "4242",
"bin": "424242",
"issuer_country": "BR",
"exp_month": 12,
"exp_year": 2028,
"status": "active",
"created_at": "2026-01-15T12:30:00.000Z"
}Endpoints
All instrument endpoints are merchant-scoped and live under a merchant path.
- Merchant key — the merchant is inferred from the key; supply your merchant id in the path.
- Organization key — pass the target merchant id in the path; the org key must have access to that merchant.
Required scopes: instruments:read for reads, instruments:write for mutations.
POST/api/v1/merchants/:merchant_id/payment-instruments
Auth: Organization key (with merchant_id) or Merchant key.
Stores a tokenized instrument against a customer. Enrollment is deduplicated: re-sending a token that already resolves to a stored instrument returns the existing one with deduplicated: true and a 200 instead of creating a duplicate.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Yes | — |
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | FK → customers. Must belong to this merchant. |
card_ciphertext_id | string | Yes | The encrypted card reference (tok_…) returned by the Bridge SDK, minted with purpose=instrument_enrollment (or saveCard: true inside a checkout session). A one_time ciphertext is refused with 400. |
metadata | object | No | Optional customer or billing-address details for the enrollment. |
Example request
curl -X POST https://api.tokeflow.com/api/v1/merchants/mrc_8a3f12d9/payment-instruments \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_2f9a71d4",
"card_ciphertext_id": "tok_8f3c2a1b9d4e",
"metadata": {}
}'Example response — 200
{
"id": "pi_123",
"merchant_id": "mrc_123",
"customer_id": "cust_john_01",
"card_brand": "visa",
"card_type": "credit",
"instrument_type": "card",
"last4": "4242",
"bin": "424242",
"issuer_country": "BR",
"exp_month": 12,
"exp_year": 2028,
"status": "active",
"deduplicated": false,
"created_at": "2025-11-20T10:00:00Z"
}GET/api/v1/merchants/{merchant_id}/payment-instruments
List a merchant's payment instruments. Supports pagination and filtering.
Auth: Organization key (scoped to the merchant) or Merchant key.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
customer_id | string | No | Filter to one customer's instruments. |
status | string | No | Filter by active, expired, or revoked. |
card_brand | string | No | Filter by brand, e.g. visa. |
card_type | string | No | Filter by credit or debit. |
last4 | string | No | Filter by last 4 digits. |
bin | string | No | Filter by issuer identification number. |
issuer_country | string | No | Filter by ISO 3166-1 alpha-2 issuer country. |
page | integer | No | Page number (default 1, min 1). |
limit | integer | No | Items per page (default 20, max 100). |
curl "https://api.tokeflow.com/api/v1/merchants/mrc_123/payment-instruments?customer_id=cust_john_01&status=active&limit=20" \
-H "Authorization: Bearer sk_live_mer_xxx"Response 200 OK
{
"success": true,
"data": [
{
"id": "pi_a1b2c3",
"merchant_id": "mrc_123",
"customer_id": "cust_john_01",
"instrument_type": "card",
"card_brand": "visa",
"card_type": "credit",
"last4": "4242",
"bin": "424242",
"issuer_country": "BR",
"exp_month": 12,
"exp_year": 2028,
"status": "active",
"created_at": "2026-01-15T12:30:00.000Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
},
"request_id": "req_8f3c2a",
"timestamp": "2026-01-15T12:30:00.000Z"
}Use customer_id plus status=active to render a customer's wallet — for example, the saved cards shown at checkout or in a billing portal.
PATCH/api/v1/merchants/{merchant_id}/payment-instruments/{payment_instrument_id}
Update an instrument's status. The status is the only mutable field.
Auth: Organization key (scoped to the merchant) or Merchant key.
Body
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | New status: active, expired, or revoked. |
curl -X PATCH \
"https://api.tokeflow.com/api/v1/merchants/mrc_123/payment-instruments/pi_a1b2c3" \
-H "Authorization: Bearer sk_live_mer_xxx" \
-H "Content-Type: application/json" \
-d '{ "status": "revoked" }'Response 200 OK
{
"success": true,
"data": {
"id": "pi_a1b2c3",
"merchant_id": "mrc_123",
"customer_id": "cust_john_01",
"instrument_type": "card",
"card_brand": "visa",
"card_type": "credit",
"last4": "4242",
"bin": "424242",
"issuer_country": "BR",
"exp_month": 12,
"exp_year": 2028,
"status": "revoked",
"created_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_91ad04",
"timestamp": "2026-01-15T12:31:00.000Z"
}DELETE/api/v1/merchants/{merchant_id}/payment-instruments/{payment_instrument_id}
Revoke an instrument. After revocation it can no longer be charged. Active subscriptions billing against it must be moved to another instrument first.
Auth: Organization key (scoped to the merchant) or Merchant key.
curl -X DELETE \
"https://api.tokeflow.com/api/v1/merchants/mrc_123/payment-instruments/pi_a1b2c3" \
-H "Authorization: Bearer sk_live_mer_xxx"Response 204 No Content
HTTP/1.1 204 No ContentRevoking an instrument is permanent. If a subscription still bills against it, move the subscription to another instrument with change-payment-instrument before revoking, or its next renewal will fail.
Errors
Failures use the standard error envelope.
| HTTP | Type | When |
|---|---|---|
| 401 | authentication_error | Missing or invalid API key. |
| 403 | authorization_error | Key lacks the required scope, or the org key cannot access this merchant. |
| 404 | not_found_error | Instrument or merchant not found. |
| 422 | business_rule_error | Status transition not allowed (e.g. reactivating a revoked instrument). |
{
"error": {
"type": "not_found_error",
"code": "PAYMENT_INSTRUMENT_NOT_FOUND",
"message": "Payment instrument pi_a1b2c3 was not found for this merchant.",
"details": {},
"request_id": "req_8f3c2a",
"timestamp": "2026-01-15T12:30:00.000Z"
}
}Related
- One-click upsells & downsells — save a card while charging, then charge it with one click.
- Bridge SDK overview — collect and enroll cards client-side.
- Transactions — charge a saved instrument.
- Subscriptions — bill recurring charges against an instrument.
- Customers — the owner of each instrument.
- Authentication — keys, scopes, and merchant resolution.