Skip to main content
A card is created with a single POST /cards request and progresses through a fixed lifecycle. This page covers the request shape, what happens after issuance, and the errors you should handle.

Request shape

curl -X POST "$GRID_BASE_URL/cards" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "cardholderId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "platformCardId": "card-emp-aary-001",
    "form": "VIRTUAL",
    "fundingSources": [
      "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
    ]
  }'
FieldRequiredNotes
cardholderIdYesThe Customer that owns the card. Must be kycStatus: APPROVED.
platformCardIdNoYour own identifier. System-generated when omitted, mirroring platformCustomerId.
formYesVIRTUAL in v1. PHYSICAL will be added later.
fundingSourcesYesOrdered array of InternalAccount ids. Each must belong to the cardholder and share one card-eligible currency. The first entry is tried first by Authorization Decisioning.
The card’s currency is derived from the funding sources at issue time and surfaces on the returned Card resource — all bound sources share one currency.

The lifecycle

PROCESSING    ──► ACTIVE ──► FROZEN ──► ACTIVE ──► CLOSED
       │             │                              ▲
       │             └──────────────────────────────┘

       └─► CLOSED (stateReason: ISSUER_REJECTED)
StateWhen you see it
PROCESSINGReturned synchronously from POST /cards. The card cannot transact yet.
ACTIVEIssuer provisioned the card. Reached via CARD.STATE_CHANGE webhook.
FROZENYou called PATCH /cards/{id} with state: "FROZEN".
CLOSEDYou called PATCH /cards/{id} with state: "CLOSED" (or the issuer rejected provisioning). Terminal.
PENDING_KYC is also a valid state but you should not see it in v1 — issuance is gated on KYC up front.

After issuance

POST /cards returns immediately with state: "PROCESSING". The issuer provisions the card asynchronously; on success a CARD.STATE_CHANGE webhook fires with the activated Card resource including the populated last4, expMonth, and expYear. If the issuer rejects provisioning, the same webhook fires with state: "CLOSED" and stateReason: "ISSUER_REJECTED". That card is terminal — issue a new one with a fresh platformCardId to retry.

Revealing the PAN

To show the cardholder their full PAN, CVV, and expiry, request a reveal right before rendering:
curl -X POST "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010/reveal" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
{
  "panEmbedUrl": "https://embed.lithic.com/iframe/...?t=...",
  "expiresAt": "2026-05-08T14:16:00Z"
}
panEmbedUrl is a signed URL for the card processor’s iframe that renders the full credentials directly to the cardholder. The full PAN and CVV never cross your servers or Grid’s.
POST /cards/{id}/reveal is the only way to obtain a reveal URL — the Card resource never carries one (not in responses, not in webhook payloads). The URL expires at expiresAt (within minutes), so request a fresh reveal each time the cardholder asks for their details, immediately before rendering the iframe. Never store, cache, or log the URL. Every reveal is audit-logged.

Errors to handle

StatusCodeWhat it means
400CARDHOLDER_KYC_NOT_APPROVEDCardholder is not kycStatus: APPROVED. Drive KYC to completion before retrying.
400FUNDING_SOURCE_INELIGIBLEThe supplied internal account doesn’t belong to the cardholder or isn’t denominated in a card-eligible currency.
400INVALID_INPUTValidation failure on the request body.

Changing funding sources later

The bound funding sources can be replaced after issuance via PATCH /cards/{id} with a new fundingSources array. See Funding sources for the rules and the signed-retry flow.

Listing cards

curl -X GET "$GRID_BASE_URL/cards?cardholderId=Customer:019542f5-b3e7-1d02-0000-000000000001&limit=20" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
Filter by cardholderId, platformCardId, or state. The response is paginated using the standard cursor shape used by other Grid list endpoints.