Skip to content

Get Card Info

Returns the card's status, balance, masked PAN by default.

When with_sensitive=true is sent, this same endpoint additionally returns the full PAN / CVV / expiry / cardholder name in the same response, via either of two authorization paths:

  1. User second-factor verification (default, self-service) — the same flow as viewing CVV on the web app: submit email_code / pin / two_fa_code / sms_code according to the account's configured transaction-auth methods. Codes are requested via /openapi/card/sensitive/send_code. No admin approval needed.
  2. Verification-exempt credential (approval-gated) — for fully automated server-to-server flows, your credential can be granted an exemption so no per-request second factor is required. Disabled by default; see Sensitive Card Information below.

Use cases:

  • Poll after async open until status=2 (active)
  • Periodically sync balance on the customer side
  • Investigate missed webhooks
  • Display PAN / CVV in your own checkout / wallet UI (via either authorization path)

Endpoint

ItemValue
MethodPOST
Path/api/v1/openapi/card/info
AuthHMAC
IdempotencyNot required

Request Fields

FieldTypeRequiredDescription
card_idstringcard_<id>
with_sensitivebooloptionalWhen true, request full PAN / CVV / expiry / cardholder name. Defaults to false. Requires status=2 (active) plus one of the two authorization paths.
email_codestringconditionalEmail verification code (when the account's auth methods include email). Sent via /openapi/card/sensitive/send_code. Not needed for verification-exempt credentials.
sms_codestringconditionalSMS verification code (when auth methods include SMS).
pinstringconditionalTransaction PIN (when auth methods include PIN; set by the user on the web app).
two_fa_codestringconditional2FA / Google Authenticator TOTP (when auth methods include 2FA).

Example Request — default (status & balance only)

json
{ "card_id": "card_12345" }

Example Request — sensitive fields (user second-factor path)

json
{
  "card_id": "card_12345",
  "with_sensitive": true,
  "email_code": "482913"
}

Example Request — sensitive fields (verification-exempt credential)

json
{
  "card_id": "card_12345",
  "with_sensitive": true
}

Response Fields

Always returned

FieldTypeDescription
card_idstringEcho
card_typestringLowercase: virtual_l / virtual_p / virtual_v / virtual_r / virtual_g / virtual_a
card_brandstringBrand display name
currencystringCard currency
statusint1=pending 2=active 3=failed 4=closing 5=closed 6=frozen
status_descstringEnglish description
masked_card_nostringMasked PAN (first 6 + last 4), e.g. 424242******1234
last_fourstringLast 4 digits
balancestring (decimal)Available balance
frozen_balancestring (decimal)Frozen balance
activated_atstring nullableActivation time (after success)
created_atstringCreation time

Returned only when with_sensitive=true AND authorization passes AND status=2

FieldTypeDescription
card_numberstringFull PAN (no spaces / dashes)
cvvstringCard verification value (3 digits for Visa/Mastercard)
expiry_datestringExpiry, format MM/YY
first_namestringCardholder first name (as provided at apply time)
last_namestringCardholder last name

These fields are entirely omitted from the response (not present at all, not null or empty) when any of the three preconditions is unmet.

Example Response — default

json
{
  "code": 200,
  "message": "OK",
  "data": {
    "card_id": "card_12345",
    "card_type": "virtual_v",
    "card_brand": "VISA",
    "currency": "USD",
    "status": 2,
    "status_desc": "active",
    "masked_card_no": "424242******1234",
    "last_four": "1234",
    "balance": "85.50",
    "frozen_balance": "0",
    "activated_at": "2026-04-29T11:00:30Z",
    "created_at": "2026-04-29T11:00:12Z"
  }
}

Example Response — with sensitive fields

json
{
  "code": 200,
  "message": "OK",
  "data": {
    "card_id": "card_12345",
    "card_type": "virtual_v",
    "card_brand": "VISA",
    "currency": "USD",
    "status": 2,
    "status_desc": "active",
    "masked_card_no": "424242******1234",
    "last_four": "1234",
    "balance": "85.50",
    "frozen_balance": "0",
    "activated_at": "2026-04-29T11:00:30Z",
    "created_at": "2026-04-29T11:00:12Z",
    "card_number": "4242420000001234",
    "cvv": "123",
    "expiry_date": "12/28",
    "first_name": "JOHN",
    "last_name": "DOE"
  }
}

Status Codes

ValueNameMeaning
1pendingOpening
2activeActivated, usable
3failedOpen failed (terminal)
4closingClosing
5closedClosed (terminal)
6frozenFrozen (user / risk)

Sensitive Card Information

Sensitive fields (card_number / cvv / expiry_date / first_name / last_name) are gated behind one of two authorization paths:

PathWho can use itPer-request requirementEnablement
A. User second-factor verificationEvery credential (default)Submit email_code / pin / two_fa_code / sms_code per the account's transaction-auth settings — only when outside the grace window (see below)None — self-service
B. Verification-exempt credentialApproved integrations onlyNone (no codes needed)Admin approval required, off by default

Path A uses the same transaction-auth configuration as the web app (Security Settings → Transaction Authentication), the same verification purpose, the same one-time codes. Path B exists for unattended server-to-server automation where a per-request human second factor is impossible.

Grace window — verify once, then no codes for a while

After one successful second-factor verification through this endpoint, the account enters a grace window (duration set by the platform administrator; default 30 minutes, 0 = disabled). While the window is active, further with_sensitive=true requests for your own cards succeed without any verification fields — so you can programmatically fetch PAN/CVV for multiple cards (e.g. when re-distributing cards to your downstream users) after a single human verification.

  • The window is fixed, not sliding: it starts at the moment of successful verification and is not extended by subsequent no-code requests. When it expires, verify again.
  • The window is per account, covering all cards owned by the credential.
  • send_code returns grace_active=true while the window is live (and skips sending), so your integration can check cheaply whether codes are currently needed. Note: outside the window the same call actually sends a code and consumes the 60 s cooldown — don't poll it as a status probe.
  • The administrative ban always overrides the window.

Path A — user second-factor verification (default)

Step 1: Send the verification code

ItemValue
MethodPOST
Path/api/v1/openapi/card/sensitive/send_code
AuthHMAC
IdempotencyNot required
Request body{} (empty JSON object)
Cooldown1 request per account per 60 s → excess returns 429 + Retry-After header

The verification purpose is fixed server-side (get_card_sensitive) — this endpoint cannot be used to trigger codes for transfers, withdrawals, or any other operation, and codes issued for other purposes are not accepted here.

Response data:

FieldTypeDescription
require_emailboolMust submit email_code in Step 2
require_smsboolMust submit sms_code in Step 2
require_pinboolMust submit pin in Step 2 (no code is sent — the user knows their PIN)
require_2faboolMust submit two_fa_code in Step 2 (no code is sent — generated by the authenticator app)
email_sentboolA 6-digit code was emailed to the account's bound address
sms_sentboolA 6-digit code was texted to the account's bound phone
grace_activeboolThe grace window is live: nothing is sent, the cooldown is not consumed — skip Step 2's verification fields and call /card/info directly

If the account only uses PIN and/or 2FA, nothing is sent (email_sent=false, sms_sent=false) and the cooldown is not consumed — proceed directly to Step 2 with the local factors.

Step 2: Call /card/info with the codes

Send with_sensitive=true together with every field the Step 1 response flagged as required. Codes are:

  • Single-use — consumed on successful verification; request a new one for each verification (not needed while the grace window is active)
  • Time-limited — valid for 10 minutes
  • Brute-force protected — repeated wrong attempts temporarily lock verification (429)

A successful verification opens the grace window — subsequent retrievals within it need no verification fields at all.

Path B — verification-exempt credential (approval-gated)

Off by default — explicit approval required

The exemption is disabled by default for every API credential and additionally guarded by a platform-wide switch. To request it, contact your account manager or submit a support ticket. You will need to:

  • State the business reason (e.g. unattended automation that cannot involve a human second factor)
  • Confirm your environment is PCI-DSS compliant for storing/displaying PAN
  • Provide your AppID (cp_xxxx...) so the exemption can be granted to the correct credential

When both the credential switch and the platform switch are ON, with_sensitive=true succeeds without any verification-code fields. Exempt credentials may still send the code fields; they are ignored.

Access preconditions

All four must be satisfied for sensitive fields to be returned:

  1. Request — caller explicitly sends with_sensitive: true
  2. Authorization — valid user second-factor codes (Path A) or a verification-exempt credential (Path B)
  3. Card state — the card must be status=2 (active); pending / failed / closing / closed / frozen are rejected
  4. No administrative ban — the platform administrator can force-disable sensitive info access per card or per account. A ban overrides both authorization paths (valid codes and exempt credentials alike) and returns 403 card_sensitive_access_banned; an account-level ban also blocks send_code. Contact support if you believe a ban is in error.

If any precondition fails, the response is 403 (or 429 when verification is locked) with the corresponding message_key. Fields are not partially returned — it is all-or-nothing per request.

Compliance & handling recommendations

Handle returned PAN as out-of-scope PCI data

  • Do not log the full card_number / cvv to your application logs or APM
  • Do not persist PAN / CVV to your own database or analytics warehouse
  • Pass through to your front-end via TLS only; render and discard
  • Mask before display when not actively in use; prefer masked_card_no
  • Rotate your OpenAPI secret immediately if you suspect compromise

Operational notes

  • The verification exemption (Path B) is revocable. If support revokes it, subsequent with_sensitive=true calls without codes fail within ~5 minutes (propagation window) — they then fall under Path A and return the corresponding *_required key.
  • The 403 error codes are distinct from the 401 returned by the auth middleware — 403 means the request was authenticated correctly but second-factor verification is missing/failed or the card state refuses sensitive access.
  • This endpoint never returns CVV via webhook payloads; sensitive fields are only available through this synchronous call.

Common Errors

HTTPmessage_keyDescription
400openapi_invalid_card_idBad card ID
400openapi_card_type_not_supportedSensitive fields requested for a non-virtual card type
401openapi_invalid_credentialsAuth failure
403email_code_required / sms_code_required / pin_required / 2fa_code_requiredwith_sensitive=true without the corresponding second-factor field (and the credential is not verification-exempt). Call send_code and resubmit.
403invalid_email_code / invalid_sms_code / invalid_pin / invalid_2fa_codeThe submitted second-factor value is wrong or expired
403email_not_verified / phone_not_verified / no_usable_auth_methodThe account has no usable verification channel for the configured method — fix the account's security settings on the web app
403card_sensitive_access_bannedSensitive info access force-disabled by the administrator for this card or account; no authorization path will succeed
403openapi_sensitive_card_only_activeCard is not in status=2 (active); sensitive fields refused
404card_not_foundCard not found / not owned
429too_many_requestsVerification temporarily locked after repeated failures (or PIN locked); honor the Retry-After header
500openapi_get_card_info_failedServer error
500openapi_internal_errorFailed to load credential context for sensitive branch

/card/sensitive/send_code errors:

HTTPmessage_keyDescription
401openapi_invalid_credentialsAuth failure
403card_sensitive_access_bannedSensitive info access force-disabled for this account by the administrator; no code is sent
429too_many_requestsSend cooldown (60 s per account); honor the Retry-After header
500send_code_failedDelivery failed — the cooldown is released so you may retry immediately
python
import time

def wait_card_active(card_id, max_minutes=10):
    """Poll after async open until active or failure"""
    deadline = time.time() + max_minutes * 60
    backoff = 2
    while time.time() < deadline:
        info = call("/api/v1/openapi/card/info", {"card_id": card_id})
        status = info['data']['status']
        if status == 2:
            return info['data']  # active
        if status in (3, 5):
            raise CardOpenFailed(info['data'])
        time.sleep(backoff)
        backoff = min(backoff * 2, 30)  # exp backoff, max 30s
    raise TimeoutError(f"Card {card_id} not active in {max_minutes} min")


def fetch_pan_for_display(card_id, email_code=None, sms_code=None, pin=None, two_fa_code=None):
    """Fetch sensitive card data for one-shot UI rendering. Never persist.

    Path A (default): first POST /openapi/card/sensitive/send_code, collect the
    required second factor(s) from the account owner, then pass them here.
    Path B (verification-exempt credential): call with no codes at all.
    """
    body = {"card_id": card_id, "with_sensitive": True}
    for k, v in {"email_code": email_code, "sms_code": sms_code,
                 "pin": pin, "two_fa_code": two_fa_code}.items():
        if v:
            body[k] = v
    resp = call("/api/v1/openapi/card/info", body)
    if resp.get('code') == 403:
        key = resp.get('message_key')
        if key and key.endswith('_required'):
            raise PermissionError(f"Second factor missing: {key} — "
                                  "call /openapi/card/sensitive/send_code first")
        if key and key.startswith('invalid_'):
            raise PermissionError(f"Second factor rejected: {key}")
        if key == 'openapi_sensitive_card_only_active':
            raise RuntimeError("Card is not active; wait for activation")
    if resp.get('code') == 429:
        raise RuntimeError("Verification locked; retry after the Retry-After window")
    if resp.get('code') != 200:
        raise RuntimeError(f"Card info failed: {resp.get('message_key')}")
    return resp['data']  # contains card_number / cvv / expiry_date / first_name / last_name


def request_sensitive_code():
    """Step 1 of Path A: trigger email/SMS code + learn which factors are needed."""
    resp = call("/api/v1/openapi/card/sensitive/send_code", {})
    if resp.get('code') != 200:
        raise RuntimeError(f"send_code failed: {resp.get('message_key')}")
    return resp['data']  # require_email / require_sms / require_pin / require_2fa / email_sent / sms_sent

Prefer webhooks

Use webhooks where possible. card/info polling is a fallback for when your webhook receiver is temporarily down or for reconciliation.

Released under MIT-equivalent terms.