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

# Cardholders

> Create the person who carries a card, and follow them through approval

<Warning>
  **Cards is available in sandbox only while we finish development.** Dakota enables Cards per
  account. The Cards endpoints are in the [API reference](/api-reference/introduction), marked
  **Sandbox only**. Endpoints, fields, and flows can still change before release.
</Warning>

A **cardholder** is the person a card is issued to. Every card belongs to one, and a cardholder must be `active` before you can issue them a card. One customer can have many cardholders, each with their own cards and limits.

The customer's Cards capability must be `available` first. Until then, creation returns `403 cards-tos-not-accepted`. See [Activating customers](/documentation/cards/activation).

## Create a cardholder

[`POST /customers/{customer_id}/cardholders`](/api-reference/cards/create-a-cardholder) takes one of three request shapes. The shape you send depends on the customer type, and on whether the person is already on the customer's onboarding application.

| Customer | Request | Name and email come from |
| - | - | - |
| **Business**, from a person already on the application | `person_id`, `phone`. Add `email` only if the person has none on file. | The person |
| **Business**, someone new | `first_name`, `last_name`, `email`, `phone` | The request |
| **Individual** | `phone`. Add `email` only if the customer has none on file. | The customer's onboarding |

`phone` is in E.164 format. `external_id` is optional on every shape: it is your own lookup key.

**Send the cardholder's own mobile number and email address.** The phone number is how the cardholder is verified at online checkouts that ask for 3-D Secure, so a shared or placeholder number makes those purchases fail. Adding the card to Apple Pay or Google Pay can also send a code to the phone or the email.

### From a person already on the application

For a business customer, reuse the people Dakota already verified during onboarding instead of collecting their details again:

```json theme={null}
GET /customers/{customer_id}/persons

200 OK
{
  "data": [
    {
      "id": "2WGC9dLw1R5M9fHaZr7K4Ya8nPq",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "roles": ["control_person"],
      "cardholder_id": null,
      "missing_for_cards": ["phone"]
    }
  ]
}
```

* `cardholder_id` is set when the person already has a cardholder. A declined cardholder still counts. Delete it before you enroll the person again.
* `missing_for_cards` lists what to collect before you create the cardholder. It always includes `phone`, and includes `email` when none is on file.

```json theme={null}
POST /customers/{customer_id}/cardholders
X-Idempotency-Key: 5b1e0c7a-2d4f-4a8e-9c3b-7f6e5d4c3b2a

{
  "person_id": "2WGC9dLw1R5M9fHaZr7K4Ya8nPq",
  "phone": "+14155550123",
  "external_id": "employee-4471"
}
```

The response is the cardholder, with `status: "pending"`.

### When creation is refused

| Response | Cause | What to do |
| - | - | - |
| `409 cardholder-person-exists` | The email you sent belongs to a person already on the application. | Retry with the `person_id` the error names. |
| `409 cardholder-already-exists` | The person already has a cardholder, or the email is in use by one. An individual customer can have only one cardholder. | Use the cardholder the error names. |
| `409 cardholder-person-unavailable` | Dakota has no usable record for this person yet. | Contact Dakota. |
| `403 cardholder-base-not-enabled` | Your account is not activated for this customer type. | Contact Dakota. |

Two people cannot share one email address.

## Lifecycle

**Wait for `cardholder.updated` with `status: "active"`** before you issue a card. Most cardholders become active within seconds.

```mermaid theme={null}
flowchart TD
    P["pending"] -->|seconds| A["active"]
    P --> UR["under_review"]
    UR --> RFI["request_for_information"]
    RFI --> UR
    UR --> A
    UR --> D(["declined"])
    A --> SU["suspended"]
    SU --> A
    A --> C(["closed"])
```

| Status | Meaning |
| - | - |
| `pending` | Screening is running. |
| `under_review` | A reviewer is looking at the cardholder. Nothing is needed from you yet. |
| `request_for_information` | A reviewer needs something from you. See below. |
| `active` | Cards can be issued. |
| `suspended` | All of the cardholder's cards are frozen, and none can be unfrozen until the suspension ends. Only Dakota can lift it. |
| `declined` | Final. The cardholder was not approved. |
| `closed` | Final. The cardholder was deleted. |

<Note>
  **Review requests are not live yet.** When a reviewer needs something, [`GET /cardholders/{cardholder_id}/application`](/api-reference/cards/get-a-cardholders-application) will list it, and you will answer with [`POST /cardholders/{cardholder_id}/application/information`](/api-reference/cards/submit-requested-information-for-a-cardholders-application). The request goes to you, never to the cardholder. Until the review pipeline is on, the list is always empty and a submission returns `501`.
</Note>

## Update a cardholder

[`PATCH /cardholders/{cardholder_id}`](/api-reference/cards/update-a-cardholder) changes `first_name`, `last_name`, `email`, or `phone`. Fields you leave out are unchanged.

## Delete a cardholder

1. Close each of the cardholder's cards. Dakota refuses the delete while any of their cards is not closed, including frozen ones.
2. [`DELETE /cardholders/{cardholder_id}`](/api-reference/cards/delete-a-cardholder).

The delete is a soft delete. A final `cardholder.updated` reports `status: "closed"`, and the cardholder is no longer returned by the API. Purchases already made on their cards still clear or reverse, and their `card_transaction` events keep arriving. You can enroll the same person again afterwards.

## Find a cardholder

[`GET /customers/{customer_id}/cardholders`](/api-reference/cards/list-cardholders) accepts an `external_id` filter. The match is exact and case-sensitive, and it can return more than one cardholder, because `external_id` is not unique.
