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

# Cards overview

> Issue single-use virtual cards to cardholders under a business customer

Three endpoints: register a cardholder under a business, list them, and issue a
single-use card. Card details are returned **once**, encrypted to your own RSA
public key.

| Endpoint | Purpose |
| - | - |
| [`POST /customer/{businessId}/card-users`](/api-reference/cards/create-cardholder) | Register a cardholder |
| [`GET /customer/{businessId}/card-users`](/api-reference/cards/list-cardholders) | List cardholders and their cards |
| [`POST /customer/{businessId}/card-users/{cardholderId}/cards/scoped`](/api-reference/cards/issue-card) | Issue a single-use card |

## Before you can call these

Two things have to be in place, and neither is self-service:

1. **Your API key needs the `cards` permission.** Ask your Endl contact to
   enable it.
2. **You must register an RSA public key**, or issuing fails with
   `400 ERRCRD_1004`. See [Card details encryption](/api-reference/cards/encryption).

The business customer must also be enabled for cards — otherwise every call
returns `400 ERRCRD_1001`.

## Headers

| Header | Required | Value |
| - | - | - |
| `Api-Key` | Yes | Partner API key with the `cards` permission |
| `Api-Version` | Yes | `2026-09.1` |
| `Content-Type` | On `POST` | `application/json` |
| `Idempotency-Key` | Optional, issue-card only | 1–64 characters |

## Response shape

**Success returns the object directly** — `200` or `201`, no envelope.

**Every error returns this envelope**, with the stable code in `errors[].code`:

```json theme={null}
{
  "code": 400,
  "message": "amountInUSDCents must be an integer from 1 to 10000000",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCRD_1005", "message": "amountInUSDCents must be an integer from 1 to 10000000", "field": null }
  ]
}
```

| Field | Type | Meaning |
| - | - | - |
| `code` | integer | HTTP status |
| `message` | string | Summary |
| `status` | string | Always `ERROR` |
| `data` | null | Always `null` |
| `errors[].code` | string | **The stable code — match on this** |
| `errors[].message` | string | Detail |
| `errors[].field` | string \| null | The offending field or header, when known |

<Note>
  This differs from the rest of `/api/v0`, where a success body is unwrapped and
  errors vary by the layer that raised them. On Cards the split is simple:
  **success bare, every error enveloped.**
</Note>

## Error codes

| Code | HTTP | Meaning |
| - | - | - |
| `ERRCRD_1000` | `400` | Customer is not a business |
| `ERRCRD_1001` | `400` | Business is not enabled for cards |
| `ERRCRD_1002` | `404` | Cardholder not found |
| `ERRCRD_1003` | `400` | Cardholder is not enabled for cards |
| `ERRCRD_1004` | `400` | Card encryption key not registered |
| `ERRCRD_1005` | `400` | A field broke a rule — the message names which |
| `ERRCRD_1006` | `400` | Card request rejected |
| `ERRCRD_1007` | `403` | Card issuing not available |
| `ERRCRD_1008` | `409` | Card already issued for this `Idempotency-Key` |
| `ERRCRD_1009` | `502` | Card issuing temporarily unavailable |
| `ERRCRD_1010` | `400` | Cardholder could not be created — the message says why |
| `ERRCORE_1001` | `400` | Missing or unsupported `Api-Version` (field `Api-Version`) |
| `ERRCORE_1004` | `401` | Partner API key required |
| `ERRCORE_1005` | `403` | Access denied, or the key lacks `cards` |
| `ERRCORE_1006` | `429` | Rate limit exceeded |
| `ERRREF_1001` | `404` | Unknown customer id |
| `IP_NOT_ALLOWLISTED` | `403` | Request IP not registered |
