Skip to main content

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 groupFields
IdentitycardKey (immutable platform identifier), cardMask (e.g. 411111******1111), name, embossedName1, embossedName2
Typetype — VIRTUAL or PHYSICAL; kind — DEBIT, CREDIT, or PREPAID
Statestatus (see lifecycle below), statusCode (provider-native code), expDate (MM/YYYY)
OwnershipownerType (b2b / individual), ownerId, partnerId
Currencycurrency (3-letter ISO), cardCurrency
RestrictionsisRestricted, restrictionType, restrictionReason
Card programcardProgram, cardProgrammeType, cardType, cardScheme, cardBin
DeliverydeliveryAddress, deliveryMethod, deliveryType (one of COURIER, LETTER, REGISTERED_LETTER, COURIER_BRANCH, CARD_DEPARTMENT, ORDERING_COMPANY, NONE)
Personalisationdesign, 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​

StatusMeaning
ORDEREDCard record exists on the platform but the plastic hasn't been produced yet (physical only).
ACTIVECard is live and can transact.

Inactive — recoverable​

StatusMeaning
LOCKEDTemporarily locked by the partner. Reversible via POST /cards/:id/unlock.
INACTIVEIssued but never activated. Reversible via POST /cards/:id/activate.
RESTRICTEDGeneric apply-restriction flag (lost / stolen / fraud); reversible by the issuing party.
BLOCKEDHard-locked by an admin (PayNovus or B2B). AdminBlockRole decides who can lift it.

Locking & unlocking a card that is still ORDERED​

Physical cards in the ORDERED state cannot be unlocked

A 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:

  1. After creating a physical card, poll GET /cards/:id/status until status === "INACTIVE".
  2. Only then allow the holder-facing UI to offer Activate, Unlock, Lock, or any sensitive operation.
  3. If the status is still ORDERED after the expected delivery window (typically 1–3 days), surface a "Card not yet delivered" message rather than calling unlock / activate.

Virtual cards do not pass through ORDERED — they are created directly in INACTIVE and can be activated immediately.

Inactive — terminal​

StatusMeaning
EXPIREDexpDate has passed. Card is no longer usable.
CLOSEDCard is closed (either voluntarily or after a resign/replace). Final.
Restriction types

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:

FieldPurpose
authPhoneNumberThe dedicated phone used to deliver the 3-D Secure OTP.
phoneNumberThe general contact phone (statements, alerts, customer service).

When a 3-D Secure challenge is triggered, the platform picks the OTP destination in this order:

  1. authPhoneNumber — used if it is set on the cardholder record.
  2. phoneNumber — used as a fallback when authPhoneNumber is 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.

Same phone for both?

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:

FieldMeaning
valueCurrent limit on the card
approvalThresholdYou can set up to this value with no approval
maxHighest value you can request (when present). Above this → 400
pendingValueValue 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 forWhat happens
At or below approvalThresholdApplied now. Response 200 with the new limits.
Above approvalThreshold, but at or below maxNeeds 4-eyes approval. Response 201 with pendingApproval: true and a taskId. The card does not change until an admin approves.
Above maxRejected 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, approvalThreshold for 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; a CLOSED or EXPIRED card 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:

FieldValue
appliesfalse
status'OK'
used0 — never a real count
remaininglimit (e.g. 10)
windowDaysunchanged (e.g. 30)
nextSlotFreesAtnull

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​

FieldMeaning
appliesWhether the rolling cap governs this cardholder. Always false for B2B cardholders — see Scope.
statusOK (proceed silently) / WARNING_THRESHOLD_REACHED (allowed, tell them) / LIMIT_EXCEEDED (will be blocked with 429). Always 'OK' when applies is false.
usedCards the cardholder has issued inside the window, regardless of current status. Always 0 when applies is false.
limitMaximum virtual cards issuable in the window.
remainingSlots still available right now (limit - used, floored at 0). Equals limit when applies is false.
windowDaysWindow length in days.
nextSlotFreesAtEarliest 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 returns 404 (same envelope whether the cardholder doesn't exist or belongs to another partner — no enumeration).

Conceptual groups​

GroupWhat it covers
Details & statusReading the card record and current status.
LifecycleLock, unlock, activate, temporary lock.
RestrictionsMark a card as lost, stolen, closed, fraud, mobile-lost, or generic restrict.
Rename/replaceEmbossed name, full card replacement, and resign.
SensitiveCVV, PAN, 3DS management, PIN lifecycle, holder authorize. Rate-limited and audited.
LimitsRead and update per-card spending limits.
Issuance quotasPer-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.