Cards
The cards section covers all partner-facing card operations. The endpoints here are shared across B2B and Individual account types — the same set of endpoints works on a card no matter which kind of account issued it.
The data of a card
Each card carries the data you can read or update through the Partner API:
| Field group | Fields |
|---|---|
| Identity | cardKey (immutable platform identifier), cardMask (e.g. 411111******1111), name, embossedName1, embossedName2 |
| Type | type — VIRTUAL or PHYSICAL; kind — DEBIT, CREDIT, or PREPAID |
| State | status (see lifecycle below), statusCode (provider-native code), expDate (MM/YYYY) |
| Ownership | ownerType (b2b / individual), ownerId, partnerId |
| Currency | currency (3-letter ISO), cardCurrency |
| Restrictions | isRestricted, restrictionType, restrictionReason |
| Card program | cardProgram, cardProgrammeType, cardType, cardScheme, cardBin |
| Delivery | deliveryAddress, deliveryMethod, deliveryType (one of COURIER, LETTER, REGISTERED_LETTER, COURIER_BRANCH, CARD_DEPARTMENT, ORDERING_COMPANY, NONE) |
| Personalisation | design, cardImage, cardImageId |
The cardKey is the only stable identifier you should ever use — cardMask and name are mutable.
Card lifecycle
Cards cycle through nine states defined by the card-processing platform. They are grouped into three logical buckets:
Active & usable
| Status | Meaning |
|---|---|
ORDERED | Card record exists on the platform but the plastic hasn't been produced yet (physical only). |
ACTIVE | Card is live and can transact. |
Inactive — recoverable
| Status | Meaning |
|---|---|
LOCKED | Temporarily locked by the partner. Reversible via POST /cards/:id/unlock. |
INACTIVE | Issued but never activated. Reversible via POST /cards/:id/activate. |
RESTRICTED | Generic apply-restriction flag (lost / stolen / fraud); reversible by the issuing party. |
BLOCKED | Hard-locked by an admin (PayNovus or B2B). AdminBlockRole decides who can lift it. |
Locking & unlocking a card that is still ORDERED
ORDERED state cannot be unlockedA freshly issued physical card starts in ORDERED — the record exists on the platform, but the plastic has not been produced/shipped yet. Lock (POST /cards/:id/lock) is accepted in this state, but the card stays effectively locked and cannot be unlocked, activated, or otherwise mutated until the platform transitions the card to INACTIVE.
The card transitions from ORDERED to INACTIVE once either:
- The card has been delivered to the destination (reported by the courier), or
- 1–3 business days have elapsed since issuance (delivery-timeout fallback).
Until that transition happens, any partner call other than GET /cards/:id, GET /cards/:id/status, and GET /cards will return 400 with a status-specific error.
Recommended partner integration:
- After creating a physical card, poll
GET /cards/:id/statusuntilstatus === "INACTIVE". - Only then allow the holder-facing UI to offer Activate, Unlock, Lock, or any sensitive operation.
- If the status is still
ORDEREDafter the expected delivery window (typically 1–3 days), surface a "Card not yet delivered" message rather than callingunlock/activate.
Virtual cards do not pass through ORDERED — they are created directly in INACTIVE and can be activated immediately.
Inactive — terminal
| Status | Meaning |
|---|---|
EXPIRED | expDate has passed. Card is no longer usable. |
CLOSED | Card is closed (either voluntarily or after a resign/replace). Final. |
The RESTRICTED umbrella covers lost, stolen, closed, fraud, and mobile-lost. Each is its own endpoint under /cards/:id/restrict-*. The restrictionType is stored on the card record.
Tenant scoping
Every route under /cards/:id/... is automatically scoped to your partner. If you pass a cardKey (the :id) that does not belong to one of your accounts, the endpoint returns 404 Not Found with no further detail — the response is identical whether the card does not exist or belongs to another partner, so a caller cannot probe your partner's cards.
GET /cards (no :id) returns a partner-wide listing — every card issued under any of your accounts.
The expDate field
Most card actions require an expDate payload field. The format is MM/YYYY (e.g. 07/2029). It acts as a second factor on sensitive operations — a stolen API key alone is never enough.
3-D Secure phone resolution
The cardholder record carries two phone fields:
| Field | Purpose |
|---|---|
authPhoneNumber | The dedicated phone used to deliver the 3-D Secure OTP. |
phoneNumber | The general contact phone (statements, alerts, customer service). |
When a 3-D Secure challenge is triggered, the platform picks the OTP destination in this order:
authPhoneNumber— used if it is set on the cardholder record.phoneNumber— used as a fallback whenauthPhoneNumberis empty.
This means a partner that wants to control where 3-D Secure OTPs land should manage authPhoneNumber separately from the contact phone — leaving authPhoneNumber empty causes the OTP to default to whatever phoneNumber is set to.
Use POST /cards/{cardKey}/change-3ds-phone to update the 3DS phone. The body field is phoneNumber; the server stores it as the cardholder's authPhoneNumber and propagates it to the cardholder record and to the downstream Card Processing service.
If you don't need a separate 3-D Secure phone, set phoneNumber to the value you want OTPs delivered to and leave authPhoneNumber empty. The platform will use phoneNumber for 3-D Secure automatically.
Sensitive operations
Endpoints marked sensitive in this section (CVV, PAN, 3DS, PIN, holder authorize) are rate-limited and audited. They should only be called from trusted backend environments — never from a browser or mobile app. See the Sensitive data → page for the full rationale.
Limits
Every card has spending limits. Each limit has a code (name), for example POS_PUR_LMT or ATM_USE_LMT.
See the full list and plain meanings in Limit types →.
You do not need any internal product settings. The API already gives you the numbers that matter for each limit.
Read limits
Call GET /cards/{cardKey}/limits.
For each limit you get:
| Field | Meaning |
|---|---|
value | Current limit on the card |
approvalThreshold | You can set up to this value with no approval |
max | Highest value you can request (when present). Above this → 400 |
pendingValue | Value waiting for 4-eyes approval (only if a change is pending) |
Use only these fields when you decide what the partner can set.
Change limits
Call PATCH /cards/{cardKey}/limits to change one or more limits in one request.
Compare each new value to approvalThreshold and max from GET:
| What you ask for | What happens |
|---|---|
At or below approvalThreshold | Applied now. Response 200 with the new limits. |
Above approvalThreshold, but at or below max | Needs 4-eyes approval. Response 201 with pendingApproval: true and a taskId. The card does not change until an admin approves. |
Above max | Rejected with 400. Partners cannot go above max. |
Rules to remember:
- Only one pending limit change can exist per card. A new request cancels the old pending one.
- After a higher value is approved,
approvalThresholdfor that card can move up. Next time, the same or lower value applies at once — no new approval.
Remaining amount for one limit
Call GET /cards/{cardKey}/limits/{limitName}/available to see how much is still left for one limit (for example how much of today's POS limit is unused).
Rolling virtual-card issuance quota
A second, independent gate alongside the hold limits (per-account concurrent cap). The two measure different things and both are enforced — whichever binds first blocks:
- Concurrent cap (
GET /cards/hold-limits/{physical|virtual}) — how many cards an account may hold at once (tiered, status-aware; aCLOSEDorEXPIREDcard frees a slot immediately). - Rolling cap (
GET /cards/virtual-card-quota) — how many virtual cards a cardholder may issue per rolling window (default 30 days, default limit 10). Terminating a card does not free a slot.
The rolling cap is deliberately blind to card status. Counting only live cards would let a cardholder burn through unlimited cards by issuing and immediately closing them — the exact abuse this cap exists to stop. A slot only frees when a card ages past the window.
Scope
Applies to non-B2B cardholders only — partner-owned individuals and direct individuals.
For B2B cardholders, the rolling cap is always not applied. When you call this endpoint for a B2B cardholder (one that carries a b2bClient), the response is always:
| Field | Value |
|---|---|
applies | false |
status | 'OK' |
used | 0 — never a real count |
remaining | limit (e.g. 10) |
windowDays | unchanged (e.g. 30) |
nextSlotFreesAt | null |
B2B cardholders are governed instead by the per-account concurrent cap (GET /cards/hold-limits/virtual), which is a separate, independent gate. The quota object is still returned (never omitted) so the partner client never has to branch on scope: any code that reads data.status === 'LIMIT_EXCEEDED' simply never fires for B2B cardholders, and any code that reads data.used sees 0 instead of a misleading real count.
Reading the response
| Field | Meaning |
|---|---|
applies | Whether the rolling cap governs this cardholder. Always false for B2B cardholders — see Scope. |
status | OK (proceed silently) / WARNING_THRESHOLD_REACHED (allowed, tell them) / LIMIT_EXCEEDED (will be blocked with 429). Always 'OK' when applies is false. |
used | Cards the cardholder has issued inside the window, regardless of current status. Always 0 when applies is false. |
limit | Maximum virtual cards issuable in the window. |
remaining | Slots still available right now (limit - used, floored at 0). Equals limit when applies is false. |
windowDays | Window length in days. |
nextSlotFreesAt | Earliest moment a slot will free up — based on the oldest in-window card aging out. null when nothing is in the window or when applies is false. |
Pre-flight vs enforcement
The pre-issuance check (GET /cards/virtual-card-quota[/...]) is read-only. The authoritative block lives in the issuance path (POST /business/cardholders/{id}/cards/virtual, POST /individual/accounts/{id}/cards/virtual, POST /cards/... variants). Both reads use the same underlying quota evaluation — the single source of truth in getVirtualCardIssuanceQuota — so a warning shown in the partner UI can never disagree with what enforcement actually does. If the check passes, issuance proceeds; if the check fails, the issuance POST would have failed with 429 VIRTUAL_CARD_LIMIT_EXCEEDED anyway.
A skipped check can never let an extra card through.
Which variant to call
GET /cards/virtual-card-quota— no id. Resolves the partner's first direct individual onboarding. Useful when the partner UI is showing the partner's own quota status.GET /cards/virtual-card-quota/{cardholderId}— explicit id. Use this for any cardholder that is not the partner's default; the id must belong to the calling partner, either directly (IndividualOnboarding.partner) or hosted by one of the partner's B2B tenants (IndividualOnboarding.b2bClient). Ownership-mismatch returns404(same envelope whether the cardholder doesn't exist or belongs to another partner — no enumeration).
Conceptual groups
| Group | What it covers |
|---|---|
| Details & status | Reading the card record and current status. |
| Lifecycle | Lock, unlock, activate, temporary lock. |
| Restrictions | Mark a card as lost, stolen, closed, fraud, mobile-lost, or generic restrict. |
| Rename/replace | Embossed name, full card replacement, and resign. |
| Sensitive | CVV, PAN, 3DS management, PIN lifecycle, holder authorize. Rate-limited and audited. |
| Limits | Read and update per-card spending limits. |
| Issuance quotas | Per-account concurrent cap (hold limits) and rolling-window cap (virtual-card quota). |
For the exact endpoint shapes, parameters, and the Try it widget, see the List of endpoints → page.