> ## Documentation Index
> Fetch the complete documentation index at: https://docs.planewallet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue your first card

> Create a holder, quote the price, and track an asynchronous card issuance.

<Warning>
  Live card issuance is not yet enabled for general access. Use an approved sandbox environment. Publishing this guide does not activate live credentials or the production API.
</Warning>

## Before you start

Create a sandbox API key in Developers with `holders:write`, `holders:read`, `services:read`, `cards:read`, `cards:write`, and `operations:read`. Keep the key on your server. Obtain your approved API base URL from Plane; the production address is `https://business.planewallet.org`.

Set `PLANE_API_BASE` to that base URL and `PLANE_API_KEY` to your key. All examples below run from your backend.

## 1. Fund the sandbox

```bash theme={null}
curl "$PLANE_API_BASE/api/v1/sandbox/funding" \
  -H "Authorization: Bearer $PLANE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: sandbox-funding-001' \
  -d '{"amount":"100.00"}'
```

This endpoint only changes a test balance. It cannot credit a live account.

## 2. Create a cardholder

```bash theme={null}
curl "$PLANE_API_BASE/api/v1/holders" \
  -H "Authorization: Bearer $PLANE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Alex Example","email":"alex@example.com","kind":"customer","externalId":"customer-001"}'
```

Save the returned `id` as `HOLDER_ID`. Sandbox holders are verified automatically; this is not a live KYC decision. An external ID must be unique within your company and environment. Holder creation does not use an idempotency header: after an uncertain response, look up the holder before creating another.

## 3. Select a product and quote

Read `GET /api/v1/products`. Choose a product with `kind: "card"` and the `issue` capability. Respect its minimum and maximum. The sandbox product is `virtual-usd`.

```bash theme={null}
curl "$PLANE_API_BASE/api/v1/quotes" \
  -H "Authorization: Bearer $PLANE_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"issue","productId":"virtual-usd","amount":"10.00"}'
```

The response separates `amount`, `fee`, and `total`. Copy `total` exactly into `expectedTotal`. Do not hardcode a fee; company pricing can differ.

## 4. Submit once

```bash theme={null}
curl "$PLANE_API_BASE/api/v1/operations" \
  -H "Authorization: Bearer $PLANE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: card-order-001' \
  -d '{"type":"issue","productId":"virtual-usd","holderId":"HOLDER_ID","amount":"10.00","expectedTotal":"QUOTED_TOTAL","name":"Advertising"}'
```

Replace `HOLDER_ID` and `QUOTED_TOTAL` before sending. Persist the idempotency key and returned operation `id` in your database. Retrying an uncertain request must use the same key and body.

## 5. Confirm the result

Read `GET /api/v1/operations/{id}` until the status becomes `completed` or `failed`, or handle an `operation.completed` webhook. A completed issuance contains `result.cardId`. Retrieve the masked card through `GET /api/v1/cards`.

`queued`, `processing`, and `provider_pending` are not successful issuance. `manual_review` requires reconciliation; do not create a replacement order. See [operation lifecycle](/operation-lifecycle) and [card details](/card-details).
