/v1/customers/{customerId}/accounts/{accountId}/cards….
Every call needs the programId query parameter — ?programId=abc — on top of the
operator’s bearer token. Your operator role bounds which programs you may act on; the
program itself is chosen by the request. Add &sandbox=true for the test environment.
These are the
/v1 routes. An older, unprefixed surface
(/customers/{customerId}/accounts/{accountId}/cards…, with GET-based status actions)
still answers, but it is being retired — build against /v1.The lifecycle
1
Issue
POST …/cards.The accepted body depends on the program’s card provider, and the schemas are
strict — an unrecognised field is a 400, not a silent ignore:Check which provider your program uses before sending
limitGroupId, prepaid or
forCustomerId.Send an Idempotency-Key header so a retry can’t issue a second
card — issuing is the one card operation you cannot cheaply undo.{ id, customerId, accountId, cardType, status, provider, nickName }.2
Activate
POST …/cards/{cardId}/activate — some providers want the last
four digits as a truncatedPan query parameter.3
Fund (managed cards only)
POST …/balance — see Balance below.Idempotency
Two card endpoints accept an idempotency key, and they behave differently — the distinction matters, so don’t assume the second one protects you the way the first does. Send it as theIdempotency-Key header (preferred) or as an idempotencyKey field
in the body. Keys are scoped to the customer, so two customers reusing the same key never
collide.
POST …/cards — full replay protection
The key must be a UUID; anything else is rejected with 400 Idempotency-Key must be a UUID. The card’s id is derived from the key, so:
POST …/balance — replay-safe
Load/unload claims the key before it calls the card provider, so a retry with the same
key returns the original result rather than moving the funds a second time. The key must
be a UUID (a non-UUID is a 400), and it is scoped to the customer the account belongs to.
Failures split on whether the money could possibly have moved:
- Rejected before the provider is called — a validation error, an inactive account, not enough balance. Nothing happened, so the key is released and is immediately reusable: fix the request and retry with the same key.
- Failed in any way we can’t prove happened before that — a timeout, a dropped
connection, a rejection from the provider itself, or any error we don’t recognise. Only
the pre-dispatch rejections above release the key; everything else fails closed. The
outcome is genuinely unknown, so the claim is pinned and every retry under that key
returns
409 IDEMPOTENT_REQUEST_IN_PROGRESS. On this path that is not transient: it persists until the movement is reconciled, or until the key expires — expiry is best-effort and takes at least 48 hours. The two409cases look identical on the wire, so retry with backoff for ~30s and then treat the key as pinned.
Read
Secure details and PIN
The dedicated reveal endpoints, each separately permissioned:GET …/secure-key— the encryption key for the secure-details response. Note this hangs off the account, not a card.POST …/cards/{cardId}/secure/passkey-challengethenPOST …/cards/{cardId}/secure— the step-up + reveal pair for full card details.GET …/cards/{cardId}/pin— the card PIN.
Status actions
All four arePOST with no body. Each returns the card’s new status.
PIN and limits
-
POST …/cards/{cardId}/pin/reset— body{ "newPin": "1234" }, 4 to 6 digits. -
POST …/cards/{cardId}/pin/unblock— clears a PIN-retry block. -
PATCH …/cards/{cardId}/limits— the fields depend on the program’s card provider: sendlimitGroupId+cardTypeto move the card to an existing limit group, orlimit+type(DAY,WEEK,MONTH,YEAR,LIFETIME) to set an amount directly.
Balance
Only on programs whose card provider holds a separate card balance (the managed / prepaid flow — see Issuing cards for your users).POST …/balance— body{ "amount": "100.00", "action": "LOAD" };UNLOADmoves funds back to the account. Returnssuccess,availableBalance,transactionId,message. The operation is account-scoped: it moves the account’s balance at the card provider, which exists from account provisioning and can hold funds with no cards issued. There is no card id in the path because there is nothing for one to identify.POST …/cards/{cardId}/balance/sync— re-reads the balance from the provider and reconciles it. Returnssuccess,availableBalance,message. Also account-scoped.
Shared cards and cardholders
For programs that let a second person hold a card on the account:3DS
GET /v1/customers/{customerId}/3ds— recent 3DS challenges for the customer.POST …/cards/3ds/confirm— confirm a challenge.