> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-dp-card-pending-api.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}
USD:  PROCESSING ──► ACTIVE ◄──► FROZEN
USDB: PROCESSING ──► PENDING ◄──► ACTIVE ◄──► FROZEN
```

Cards can also be closed permanently. Issuer rejection closes a card while
provisioning. An unfinished signing challenge leaves a provisioned USDB card pending.

| Status | When you see it |
| - | - |
| `PROCESSING` | The issuer is still provisioning the card. |
| `PENDING` | The issuer has provisioned the card, but its USDB Embedded Wallet funding source has no usable delegated key. Complete delegated-key setup. |
| `ACTIVE` | Provisioning and required signing authorization are complete. Transactions remain subject to balance and authorization checks. |
| `FROZEN` | The card was frozen through `PATCH /cards/{id}`. Completing delegation does not unfreeze it. |
| `CLOSED` | The card was permanently closed. Completing delegation cannot reopen it. |

`PENDING_KYC` is valid but is not emitted in v1 because issuance is gated
on KYC up front.

## After issuance

`POST /cards` returns `PROCESSING` while provisioning is incomplete. If
provisioning completes synchronously, the response can already be `ACTIVE`
or `PENDING`, with `last4`, `expMonth`, and `expYear` populated.

For USDB Embedded Wallet funding, complete all three calls to
`POST /auth/delegated-keys`, including both signed retries. The card remains
`PENDING` with `statusReason: "DELEGATION_REQUIRED"` until the final retry
returns an `ACTIVE` delegated key. A provisioned pending card then becomes
`ACTIVE` automatically and emits `CARD.STATUS_CHANGE`.

USD funding does not require delegation. If delegation completes before
provisioning, the card can go directly from `PROCESSING` to `ACTIVE`.

Replacing USD funding with USDB lacking an active key can move an active
card back to `PENDING`. Switching back to USD, or completing delegation,
restores readiness. Revoking the required key also moves an active card to
`PENDING`. Frozen and closed cards retain their status.

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.