Cardholder
Some card products are issued in the name of a verified person. For those products (requirements.cardholder: true in card types), the account needs a cardholder profile, and the account owner completes identity verification once.
- There is one cardholder profile per FluxA account, shared by all agents linked to that account.
- Use the account owner's real details. Never invent or guess profile data; ask the user.
- Products with
requirements.cardholder: falsedo not need a profile. Skip this page for them.
Recommended flow:
GET /cardholders/get. If a profile exists, use it; do not create another.- If you get
404 cardholder_not_found, collect the details from the user and callPOST /cardholders/create. - Send the user to the returned
verificationUrlto complete identity verification. - Poll
GET /cardholders/getuntilkycStatusisapproved.
Create a profile
/api/v2/cardholders/createBearer agent-vcCreates the account's profile and starts identity verification.
curl -X POST https://agentcard.fluxapay.xyz/api/v2/cardholders/create \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AGENT_VC" \
-d '{
"firstName": "Alice",
"lastName": "Smith",
"email": "alice@example.com",
"phone": "+14155552671",
"address": {
"country": "US",
"region": "MA",
"city": "Boston",
"line1": "10 Main Street",
"line2": "Unit 2",
"postalCode": "02101"
}
}'CLI:
fluxa-wallet card holder create --profile @profile.json
# or
fluxa-wallet card holder create --first-name Alice --last-name Smith \
--email alice@example.com --phone +14155552671 \
--country US --city Boston --address-line1 "10 Main Street" --postal-code 02101Request fields
| Field | Type | Required | Rules |
|---|---|---|---|
firstName | string | yes | Non-empty, max 100 characters. Must match the identity document. |
lastName | string | yes | Non-empty, max 100 characters. Must match the identity document. |
email | string | yes | Valid email, max 254 characters. |
phone | string | yes | E.164 format, e.g. +14155552671. |
address.country | string | yes | ISO 3166-1 alpha-2 code, e.g. US. |
address.city | string | yes | Max 250 characters. |
address.line1 | string | yes | Max 250 characters. |
address.postalCode | string | yes | Max 250 characters. |
address.region | string | no | State / province. |
address.line2 | string | no | Apartment, suite, etc. |
Unknown fields are rejected with 422 profile_invalid. The request body is limited to 16 KB.
Response
201— profile created; verification started.200— a profile already exists. The existing profile is returned unchanged and the new data is ignored. Use update to change it.
{
"cardholder": {
"id": "6f1c2d4e-0b7a-4c1e-9d3f-2a8b5c7e1f90",
"revision": 1,
"kycStatus": "pending",
"nameLocked": false,
"profile": {
"firstName": "Alice",
"lastName": "Smith",
"email": "alice@example.com",
"phone": "+14155552671",
"address": { "country": "US", "region": "MA", "city": "Boston", "line1": "10 Main Street", "line2": "Unit 2", "postalCode": "02101" }
},
"verificationUrl": "https://…",
"nextAction": "complete_verification",
"failureReason": null,
"createdAt": "2026-10-27T10:00:00.000Z",
"updatedAt": "2026-10-27T10:00:00.000Z"
}
}Get the profile
/api/v2/cardholders/getBearer agent-vccurl https://agentcard.fluxapay.xyz/api/v2/cardholders/get \
-H "Authorization: Bearer $AGENT_VC"CLI:
fluxa-wallet card holder meReturns { "cardholder": { … } } in the same shape as create, or 404 cardholder_not_found if the account has no profile. Status is refreshed periodically; poll every 30 seconds or more while verification is in progress.
Cardholder fields
| Field | Type | Description |
|---|---|---|
id | string | Cardholder ID. |
revision | integer | Profile version. Send it in If-Match on update to avoid overwriting concurrent changes. |
kycStatus | string | Verification state. See below. |
nameLocked | boolean | true once identity is verified. The name can no longer be changed. |
profile | object | The details you submitted. |
verificationUrl | string | null | Link where the account owner completes verification. Present only while verification is open. |
nextAction | string | null | What to do next. See below. |
failureReason | object | null | Present only when kycStatus is failed: { "code", "message" }. |
createdAt, updatedAt | string | ISO 8601 timestamps. |
Verification states
kycStatus | Meaning | nextAction | What to do |
|---|---|---|---|
pending | Verification is ready for the user. | complete_verification (or wait) | Send the user to verificationUrl. |
processing | Verification is being reviewed. | wait | Poll GET /cardholders/get. |
approved | Identity verified. | null | You can issue cards for products that require a cardholder. |
failed | Verification did not pass. | update | Show failureReason.message to the user, correct the details with update, and verification restarts. |
unverified | Verification must be done again. | update | Call update (an empty body {} is allowed) to start a new verification. |
Common failureReason.code values on failed:
code | What to tell the user |
|---|---|
name_mismatch | The name entered does not match the identity document. Correct firstName / lastName. |
address_mismatch, address_name_mismatch, address_evidence_missing | The address could not be confirmed from the proof-of-address document. Check the address and retry. |
identity_mismatch | The new verification must be completed by the same person as before. |
kyc_failed, verification_evidence_missing | Verification was declined, expired, or incomplete. Retry with update. |
Existing (legacy) cardholders
Accounts that set up a cardholder before this profile API existed are returned with "source": "legacy", "kycStatus": "not_recorded" and "revision": null. If nextAction is null, the account can keep issuing the products it already used; you do not need to create a new profile. Do not create a new profile for these accounts unless the user asks.
Update a profile
/api/v2/cardholders/updateBearer agent-vcPartial update. Send only the fields you want to change; address fields are merged one by one. Optional fields (address.region, address.line2) can be cleared with null; required fields cannot.
curl -X POST https://agentcard.fluxapay.xyz/api/v2/cardholders/update \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AGENT_VC" \
-H 'If-Match: "2"' \
-d '{ "phone": "+14155550123" }'CLI:
fluxa-wallet card holder update --profile @patch.json --revision 2| Header | Required | Description |
|---|---|---|
If-Match | no | Current revision, e.g. "2". If the profile changed since, you get 409 revision_conflict; re-read and retry. |
What can be changed depends on kycStatus:
kycStatus | Update behavior |
|---|---|
pending, processing | Not allowed: 409 kyc_in_progress. Wait for the result. |
failed, unverified | Any field can be corrected. Verification restarts. |
approved | email and phone update immediately. The name is locked (409 name_locked). Changing the address starts a new address verification; follow the new verificationUrl. |
Returns 200 with { "cardholder": { … } }.
Errors
| Status | code | What to do |
|---|---|---|
| 400 | revision_invalid | If-Match must be a positive integer, e.g. "2". |
| 404 | cardholder_not_found | No profile yet. Create one. |
| 409 | kyc_in_progress | Verification is in progress. Wait, then retry. |
| 409 | name_locked | The verified name cannot be changed. |
| 409 | revision_conflict | Re-read the profile and retry with the new revision. |
| 422 | profile_invalid | A field is missing or invalid. details.field names it. |
| 429 | rate_limited | Too many requests. Wait a minute and retry. |
| 503 | — | Profile service temporarily unavailable. Retry later. |
