Skip to main content

Creating a cardholder

Create the record everything else references.

Before you begin​

  • A sandbox or production access token — see authentication.
  • Nothing else. A cardholder is the first object you create, and it needs no prior verification — see below.
Create first, verify after

Creating a cardholder does not require verification. The record exists immediately with kyc_status: not_started, and you can open accounts and even issue cards against it — provided it has an address and an identity_document, which issuance needs but creation does not.

What verification gates is card activation. Creating early means the record, the account, and the card are all in place the moment you report a passing outcome, rather than serializing four steps.

How it works​

A cardholder is the spine of the model: accounts reference it, cards reference it, and nothing can be issued without one. See core concepts.

type is individual. Business or company card will be supported in the future.

type, first_name and last_name are required — a card has to be issued to somebody. Which of the rest you must supply depends on your program's configuration and on what your own verification process needs.

Two fields are required not for creation but for the next step: identity_document and address. A cardholder can be created without either, but a card cannot be issued to one that lacks them.

identity_document — missing it fails issuance with identity_document_required. Only the number and its type are held — no image, no verification result — and the number is never returned; the response reports has_identity_document instead. This one cannot be added later: it is not patchable, so a cardholder created without it must be recreated.

address — missing it fails issuance with address_required. The card is registered to this address, so it is the cardholder's own. Unlike the document, it can be added later with PATCH /v1/cardholders/{cardholder_id}.

Set both at creation unless you are recording a cardholder you will not yet issue a card to.

Steps​

1. Create the cardholder​

curl -X POST "$ALETA_BASE_URL/v1/cardholders" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"type": "individual",
"first_name": "Mei",
"last_name": "Tan",
"email": "mei.tan@example.com",
"phone": "+6591234567",
"date_of_birth": "1990-04-12",
"address": {
"line1": "350 Orchard Road",
"line2": "#12-01",
"city": "Singapore",
"postal_code": "238868",
"country": "SGP"
},
"identity_document": {
"type": "national_id",
"number": "S1234567D"
},
"reference": "user-4471"
}'
Response
{
"id": "chd_01HX8ZQ2K4M7P9RSTVWXYZAB",
"object": "cardholder",
"type": "individual",
"kyc_status": "not_started",
"first_name": "Mei",
"last_name": "Tan",
"email": "mei.tan@example.com",
"reference": "user-4471",
"has_identity_document": true,
"created_at": "2026-07-29T14:32:05Z"
}

kyc_status starts at not_started. It moves only when you report an outcome.

2. Set reference​

reference is your own identifier. It is filterable and appears in every webhook payload for this cardholder:

curl "$ALETA_BASE_URL/v1/cardholders?reference=user-4471" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN"

Without it, every incoming cardholder.kyc_approved needs a lookup table to map back to your user. With it, the event carries your own key. Retrofitting means backfilling every cardholder you have already created.

3. Field rules worth knowing​

FieldRule
phoneE.164, with the +. +6591234567, not 91234567
date_of_birthYYYY-MM-DD. A date, not a timestamp — no _at suffix
address.countryISO 3166-1 alpha-3. SGP, not SG. Checked against the real list, so ZZZ is refused too
emailUsed for program communications where configured

Unknown fields are rejected with unknown_parameter rather than ignored, so a typo surfaces immediately instead of silently dropping data.

4. Updating later​

curl -X PATCH "$ALETA_BASE_URL/v1/cardholders/chd_01HX8ZQ2K4M7P9RSTVWXYZAB" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"email": "mei.tan@newdomain.example"}'

PATCH accepts contact details and reference only. It cannot change kyc_status — that moves solely through reporting an outcome, so the verification record stays a deliberate, attributable act rather than a field update.

Verifying​

curl "$ALETA_BASE_URL/v1/cardholders/chd_01HX8ZQ2K4M7P9RSTVWXYZAB" \
-H "Authorization: Bearer $ALETA_ACCESS_TOKEN"

Expect kyc_status: "not_started" and your reference echoed back. cardholder.created fires on your webhook endpoint.

Common errors​

StatuscodeFix
400missing_parametertype, first_name and last_name are required
400invalid_parametertype must be individual; or phone isn't in E.164 format, email isn't an address, or a country isn't an ISO 3166-1 alpha-3 code. param names the field
400unknown_parameterA field name typo, or business_name, which is no longer accepted. Check param in the error
409cardholder_already_existsAnother cardholder in your organization already has this email or phone
409idempotency_key_reuseSame key, different body

Errors when updating a cardholder​

PATCH /v1/cardholders/{cardholder_id} returns these as well. The fields you sent are validated first, then written, then sent to the processor if it holds them.

StatuscodeFix
400invalid_parameteremail, phone or address is malformed, or reference is over 128 characters. param names the field
400unknown_parameterThe field can't be changed. Name, type and the identity document are fixed; re-verify instead
404cardholder_not_foundThe cardholder doesn't exist, or belongs to another organization
409cardholder_already_existsAnother cardholder in your organization already has this email or phone
422address_change_requires_kycThis program's processor can't take an address change. Report a KYC attestation that carries the new address
422operation_unsupportedThe processor can't take this change
502processor_unavailableYour change is saved here but the processor didn't take it. Send the same change again
502processor_permission_deniedThe program isn't approved to change this at the processor. Send us the request_id