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

# Cards API Validation Rules and Errors

> Synchronous 422 validation responses for the Lead Cards API across create, retrieve, update, activate, deactivate, and close endpoints, plus enum rules.

All validation failures on the Cards API are returned synchronously. Business logic errors are returned as `422` responses with an `invalid_parameters` array that identifies which fields failed and why, so you can correct the request before retrying.

```json theme={null}
{
  "code": "validation_error",
  "title": "Validation Error",
  "detail": "One or more request parameters failed validation",
  "invalid_parameters": [ 
     { 
      "parameter": "<param>", 
      "reason": "<reason>" 
     } 
  ]
}
```

Every mutating request (`POST`, `PATCH`, and the action endpoints) requires an `Idempotency-Key` header. Omitting it returns `400 operation_not_allowed` (reason: `parameter "Idempotency-Key" in header has an error: value is required but missing`). A request body that is not valid JSON returns `400 bad_request` with `The request body contains malformed JSON`.

## `POST /v0/cards`

Creates a card. The card is always created in `active` status.

### Required Fields

| **Parameter**               | **Validation Rule**      | **Error Reason**                                                                                                |
| :-------------------------- | :----------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `account_id`                | Required object          | property `account_id` is missing                                                                                |
| `entity_id`                 | Required object          | property `entity_id` is missing                                                                                 |
| `details`                   | Required object          | property `details` is missing                                                                                   |
| `details.card_product_name` | Required                 | property `card_product_name` is missing                                                                         |
| `details.is_virtual`        | Required                 | property `is_virtual` is missing                                                                                |
| `details.card_type`         | Required                 | property `card_type` is missing                                                                                 |
| `details.expiry_date`       | Required                 | property `expiry_date` is missing                                                                               |
| `details.last_four`         | Required                 | property `last_four` is missing                                                                                 |
| `Idempotency-Key` header    | Required (all mutations) | `400 operation_not_allowed` — parameter `Idempotency-Key` in header has an error: value is required but missing |

### Field Validations

| **Parameter**             | **Validation Rule**                                                                                                                                    | **Error Reason**                                                                                                                                                                                                             |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `entity_id`               | Must match the pattern `^entity_[^\s]{1,33}$`                                                                                                          | String doesn't match the regular expression `^entity_[^\s]{1,33}$`                                                                                                                                                           |
| `account_id`              | Must match the pattern `^account_\w+$`                                                                                                                 | String doesn't match the regular expression `^account_\w+$`                                                                                                                                                                  |
| `details.last_four`       | Must match the pattern `^[0-9]{4}$`                                                                                                                    | String doesn't match the regular expression `^[0-9]{4}$`                                                                                                                                                                     |
| `details.expiry_date`     | Must be a valid ISO 8601 date (YYYY-MM-DD), expressed as the last day of the expiration month                                                          | String doesn't match the format "date"                                                                                                                                                                                       |
| `details.expiry_date`     | Must not be earlier than the time of object creation (e.g., if created any time on 2026-06-01 UTC, an `expiry_date` before 2026-06-01 is rejected)     | `invalid_date`                                                                                                                                                                                                               |
| `details.card_type`       | Must be one of `hsa`, `fsa`, `consumer`, `commercial`                                                                                                  | value is not one of the allowed values `["hsa","fsa","consumer","commercial"]`                                                                                                                                               |
| `details.is_virtual`      | Must be a boolean value                                                                                                                                | Value must be a boolean                                                                                                                                                                                                      |
| `details.replacement_for` | If provided, must reference a card that exists, is in `closed` status, is not already referenced by another card, and matches the pattern `^card_\w+$` | Referenced card must be in closed status to be replaced<br /><br />Referenced card not found<br /><br />Replacement for card has already been referenced<br /><br />String doesn't match the regular expression `^card_\w+$` |

### Reference Validations

| **Parameter** | **Validation Rule**                                                                     | **Error Reason**                                              |
| ------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `account_id`  | Must reference a valid account whose status is `active` or `inactive`                   | The `account_id` referenced by this card does not exist       |
| `entity_id`   | Must be linked to the provided `account_id` as an `account_holder` or `authorized_user` | The `entity_id` is not associated with the referenced account |

<Note>
  Creating a card against a **closed** account is rejected with `400 operation_not_allowed` (title *"This operation cannot be done on this object."*) rather than a `422`, because it is an operation that cannot be performed on the object.
</Note>

## `GET /v0/cards/{id}`

Retrieves a card by its server-generated ID.

| **Validation**                                     | **Error**                                              |
| :------------------------------------------------- | :----------------------------------------------------- |
| `id` must match the pattern `^card_\w+$`           | `404 not_found`: the card ID passed in cannot be found |
| Card must exist and belong to the caller's program | `404 not_found`: the requested card was not found      |

## `GET /v0/cards?client_card_id={client_card_id}`

Retrieves a card by the client-provided `client_card_id` (for cards created via file ingestion).

| **Validation**                               | **Error**                                                                                               |
| :------------------------------------------- | :------------------------------------------------------------------------------------------------------ |
| `client_card_id` query parameter is required | `422 validation_error`: parameter `client_card_id` in query has an error: value is required but missing |
| `client_card_id` must not be empty           | `400 bad_request`                                                                                       |
| Card must exist for the caller's program     | `404 not_found`: the requested card was not found                                                       |

## `PATCH /v0/cards/{id}`

Partial update. Only `details.card_product_name` and `metadata` are mutable.

| **Parameter / Rule**                                                                                                                         | **Behavior**                                                                                                         | **Error Reason**                                                                                                |
| :------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `details.card_product_name`, `metadata`                                                                                                      | Mutable — updated when present                                                                                       | —                                                                                                               |
| Any other field (`status`, `details.expiry_date`, `details.is_virtual`, `details.card_type`, `details.last_four`, `account_id`, `entity_id`) | **Silently ignored** — the value is not applied, and `updated_at` is only bumped if a mutable field actually changed | — (returns `200`)                                                                                               |
| `details.card_product_name`                                                                                                                  | If provided, must be a non-empty string                                                                              | `422 validation_error`: `details.card_product_name` → `Card is missing required client_card_product_name field` |
| Card `status`                                                                                                                                | A card in `closed` status cannot be updated                                                                          | `422 validation_error`: `status` → `Card cannot be updated when status is closed`                               |
| `metadata`                                                                                                                                   | Must not exceed the DynamoDB item size limit                                                                         | `422 validation_error` (oversized metadata)                                                                     |

<Note>
  Unlike a create, non-mutable fields sent on a `PATCH` are **ignored rather than rejected** (consistent with the Accounts API). A `PATCH` that changes nothing (empty body, or only non-mutable fields) returns `200` without bumping `updated_at`.
</Note>

The status of a card is system-managed and changes only through the three action endpoints below (each takes an `Idempotency-Key` header):

| **Endpoint**                     | **Allowed source states** | **Required body** | **Enforced `status_reason` enum**                                       |
| :------------------------------- | :------------------------ | :---------------- | :---------------------------------------------------------------------- |
| `POST /v0/cards/{id}/activate`   | `inactive`, `closed`      | — (no body)       | —                                                                       |
| `POST /v0/cards/{id}/deactivate` | `active`, `closed`        | `status_reason`   | `dormant`, `frozen`, `other`                                            |
| `POST /v0/cards/{id}/close`      | `active`, `inactive`      | `status_reason`   | `entity_closed`, `client_closed`, `paid_off`, `charged_off`, `canceled` |

## `POST /v0/cards/{id}/activate`

| **Validation Rule**                                                                                                                      | **Error Reason**                                              |
| :--------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ |
| Card must not already be `active`                                                                                                        | `status` → `card is already active`                           |
| A card whose `expiry_date` is in the past cannot be activated                                                                            | `status` → `card is expired and cannot be activated`          |
| A card that has been replaced (i.e., its ID has been referenced in the `replacement_for` field on a separate card) cannot be reactivated | `status` → `card has been replaced and cannot be reactivated` |

Reactivating a `closed` card clears its `closed_at` and `status_reason`.

## `POST /v0/cards/{id}/deactivate`

| **Validation Rule**                                                                                                                      | **Error Reason**                                                      |
| :--------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `status_reason` is required                                                                                                              | property `status_reason` is missing                                   |
| `status_reason` must be a valid enum value                                                                                               | value is not one of the allowed values `["dormant","frozen","other"]` |
| Card must not already be `inactive`                                                                                                      | `status` → `card is already inactive`                                 |
| A card that has been replaced (i.e., its ID has been referenced in the `replacement_for` field on a separate card) cannot be deactivated | `status` → `card has been replaced and cannot be deactivated`         |

Deactivating a `closed` card clears its `closed_at` and sets the new `status_reason`.

## `POST /v0/cards/{id}/close`

| **Validation Rule**                        | **Error Reason**                                                                                               |
| :----------------------------------------- | :------------------------------------------------------------------------------------------------------------- |
| `status_reason` is required                | property `status_reason` is missing                                                                            |
| `status_reason` must be a valid enum value | value is not one of the allowed values `["entity_closed","client_closed","paid_off","charged_off","canceled"]` |
| Card must not already be `closed`          | `status` → `card is already closed`                                                                            |

## Authentication

Authentication and scope are enforced at the gateway.

| **Scenario**                                                             | **Error**                                                                      |
| :----------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
| Using a `card/read` token to call `POST`, `PATCH`, or an action endpoint | `403 insufficient_scope`: you cannot call /v0/cards with the scope `card/read` |
| Using an unrelated scope token                                           | `403 insufficient_scope`                                                       |
| Using a missing, malformed, or expired token                             | `403`                                                                          |
