> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-ajay-statements-playground.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",
    "fundingSource": "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.                                                                                                                                                                                                                                                                                                       |
| `fundingSource`          | Yes      | The `InternalAccount` that funds the card. Must belong to the customer and be denominated in a card-eligible currency.                                                                                                                                                                                                                                 |
| `maxSpendPerTransaction` | No       | The largest amount the card can authorize on a single transaction, in the smallest unit of the card's currency (cents for USD). An authorization for exactly the limit is allowed; a later clearing can still settle above it (e.g. a restaurant tip). Omit for no limit. Available only on 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 program fixes the card's `currency` at issuance and returns it on
the `Card` resource. USDB-funded cards have `currency: "USD"`, with funding
converted at 1 USDB = 1 USD. Spending limits use the smallest unit of the
card's currency: for a USDB-funded card, `50000` means \$500.00 in USD cents.
Changing the funding source does not change the card's currency or
spending-limit units.

## 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.STATUS_CHANGE` webhook.   |
| `FROZEN`     | You called `PATCH /cards/{id}` with `status: "FROZEN"`.                  |
| `CLOSED`     | You called `PATCH /cards/{id}` with `status: "CLOSED"`. Terminal.        |

`PENDING_KYC` is also a valid status but you should not see it in v1 —
issuance is gated on KYC up front.

## After issuance

`POST /cards` returns immediately with `status: "PROCESSING"`. The
issuer provisions the card asynchronously; on success a
`CARD.STATUS_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>

### Styling the reveal

By default the iframe renders with Grid's own styling. To carry your
branding instead, host a stylesheet and point your platform config at it:

```bash theme={null}
curl -X PATCH "$GRID_BASE_URL/config" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"cardConfigs": {"panRevealCssUrl": "https://acme.com/card-reveal.css"}}'
```

Every reveal minted afterwards links your stylesheet. Send `null` to go
back to the default.

To style one reveal differently — to match the cardholder's light or dark
theme, say — send `cssUrl` in the reveal request instead. It overrides the
platform setting for that call only:

```bash theme={null}
curl -X POST "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010/reveal" \
  -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"cssUrl": "https://acme.com/card-reveal-dark.css"}'
```

Style these ids and classes; the rest of the page structure is not a
contract and can change:

| Selector         | What it is                        |
| ---------------- | --------------------------------- |
| `#card`          | The wrapper around the whole card |
| `#pan`           | The full card number              |
| `.pan-separator` | The gap between PAN digit groups  |
| `#expiry`        | The expiry date                   |
| `#cvv`           | The security code                 |

```css theme={null}
#card {
  background: #1a1a1a;
  border-radius: 16px;
  padding: 24px;
  font-family: "Acme Sans", sans-serif;
}

#pan {
  font-size: 21px;
  letter-spacing: 0.04em;
  color: #ffffff;
}
```

<Warning>
  Both settings load a stylesheet into the page that renders the live PAN,
  expiry, and CVV, so host it somewhere you would trust with card details. It
  must be reachable over HTTPS and must not carry credentials in the URL. Grid
  never fetches it — the cardholder's browser does — so a stylesheet that fails
  to load produces an unstyled card rather than a server error. Reveal a
  sandbox card to check yours before you ship it.
</Warning>

## 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    | `INVALID_INPUT`               | The supplied internal account doesn't belong to the cardholder or isn't denominated in a card-eligible currency, or another field failed validation.                                       |
| 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 the funding source later

The bound funding source can be replaced after issuance via
`PATCH /cards/{id}` with a new `fundingSource`. See
[Funding sources](/cards/card-management/funding-sources) for the rules
and request example.

## 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 `status`. The response is
paginated using the standard cursor shape used by other Grid list
endpoints.
