> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-09-11-remove-name-from-card-example.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Issuing Cards

> Create a virtual card and observe its lifecycle

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

```bash theme={null}
curl -X POST "$GRID_BASE_URL/cards" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "form": "VIRTUAL",
    "fundingSources": [
      "InternalAccount:019542f5-b3e7-1d02-0000-000000000002"
    ],
    "maxSpendPerTransaction": 5000,
    "maxSpendPerDay": 25000,
    "maxTransactionsPerDay": 20
  }'
```

| Field                    | Required | Notes                                                                                                                                                                                                 |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId`             | Yes      | The `Customer` that owns the card. Must be `kycStatus: APPROVED`.                                                                                                                                     |
| `platformCardId`         | No       | Ignored if supplied. The value on the returned `Card` is generated by the server.                                                                                                                     |
| `form`                   | Yes      | `VIRTUAL` in v1. `PHYSICAL` will be added later.                                                                                                                                                      |
| `fundingSources`         | Yes      | Ordered 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.                         |
| `maxSpendPerTransaction` | No       | Largest amount a single card transaction may authorize, in the smallest unit of the card's currency. Omit for no limit. Supported only for card programs where Grid makes the authorization decision. |
| `maxSpendPerDay`         | No       | Cumulative new spend allowed per UTC calendar day, in the smallest unit of the card's currency. Refunds, reversals, and expiries do not restore capacity that day.                                    |
| `maxTransactionsPerDay`  | No       | Number of transactions the card may authorize per UTC calendar day. Each approved authorization counts once; reversals and expiries do not restore capacity that day.                                 |

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

```text theme={null}
PROCESSING ──► ACTIVE ◄──► FROZEN
                 │
                 └──► CLOSED
```

| State        | When you see it                                                          |
| ------------ | ------------------------------------------------------------------------ |
| `PROCESSING` | Returned synchronously from `POST /cards`. The card cannot transact yet. |
| `ACTIVE`     | Issuer provisioned the card. Reached via `CARD.STATE_CHANGE` webhook.    |
| `FROZEN`     | You called `PATCH /cards/{id}` with `state: "FROZEN"`.                   |
| `CLOSED`     | You called `PATCH /cards/{id}` with `state: "CLOSED"`. 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`.

## Revealing the PAN

To show the cardholder their full PAN, CVV, and expiry, request a
reveal right before rendering:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010/reveal" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

```json theme={null}
{
  "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.

<Note>
  For cards in programs where Grid makes authorization decisions,
  `POST /cards/{id}/reveal` is the **only** way to obtain a reveal URL;
  cards in programs where the card issuer makes authorization decisions use
  the card issuer's challenge-based hosted reveal flow instead. 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.
</Note>

## Errors to handle

| Status | Code                          | What it means                                                                                                                                                                              |
| ------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `CARDHOLDER_KYC_NOT_APPROVED` | Cardholder is not `kycStatus: APPROVED`. Drive KYC to completion before retrying.                                                                                                          |
| 400    | `FUNDING_SOURCE_INELIGIBLE`   | The supplied internal account doesn't belong to the cardholder or isn't denominated in a card-eligible currency.                                                                           |
| 400    | `INVALID_INPUT`               | Validation failure on the request body.                                                                                                                                                    |
| 409    | `CONFLICT`                    | The card belongs to a program where the card issuer makes authorization decisions. Use the card issuer's challenge-based hosted reveal flow instead; retrying this endpoint will not help. |

## 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](/cards/card-management/funding-sources) for the rules
and the signed-retry flow.

## Listing cards

```bash theme={null}
curl -X GET "$GRID_BASE_URL/cards?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001&limit=20" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
```

Filter by `customerId`, `platformCardId`, or `state`. The response is
paginated using the standard cursor shape used by other Grid list
endpoints.
