Card endpoints live under /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.
Returns { 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 the Idempotency-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:
This does not apply to the prepaid card flows. On the Tribe prepaid-master and Interlace prepaid paths, creation returns before the idempotency record is written and the card is stored under a fresh random id, so a retry with the same key issues a second card. If your program is on either of those, do not rely on the key here — check whether the card exists before retrying.

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 two 409 cases look identical on the wire, so retry with backoff for ~30s and then treat the key as pinned.
On the pinned path, do not retry unboundedly and do not rotate to a fresh key. A fresh key bypasses the protection and loads the card a second time. Check the card balance and transactions to establish what actually happened, and escalate if it’s unclear.
No other card endpoint reads an idempotency key. The status actions, PIN reset and limit changes are state-setting rather than resource-creating, so a retry converges on the same end state — but note it does re-issue the call to the card provider rather than short-circuiting.

Read

Back-office tokens get the card record verbatim on GET …/cards and GET …/cards/{cardId} — including cardNumber, expiryDate and cvv where the provider returned them. Only customer sessions get the trimmed view (masked PAN, cvv: "***"). Treat these two responses as carrying full card data: don’t log them, and don’t forward them to a browser. The …/secure endpoints below exist so that revealing card details can be gated and stepped-up separately — but they are not the only way full data reaches a back-office caller.

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-challenge then POST …/cards/{cardId}/secure — the step-up + reveal pair for full card details.
  • GET …/cards/{cardId}/pin — the card PIN.

Status actions

All four are POST with no body. Each returns the card’s new status.
On the retired unprefixed surface these were GET requests. On /v1 they are POST. If you are porting an old integration, change the method as well as the path.

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: send limitGroupId + cardType to move the card to an existing limit group, or limit + 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" }; UNLOAD moves funds back to the account. Returns success, 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. Returns success, 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.