Skip to content

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: false do not need a profile. Skip this page for them.

Recommended flow:

  1. GET /cardholders/get. If a profile exists, use it; do not create another.
  2. If you get 404 cardholder_not_found, collect the details from the user and call POST /cardholders/create.
  3. Send the user to the returned verificationUrl to complete identity verification.
  4. Poll GET /cardholders/get until kycStatus is approved.

Create a profile ​

POST/api/v2/cardholders/createBearer agent-vc

Creates the account's profile and starts identity verification.

bash
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:

bash
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 02101

Request fields ​

FieldTypeRequiredRules
firstNamestringyesNon-empty, max 100 characters. Must match the identity document.
lastNamestringyesNon-empty, max 100 characters. Must match the identity document.
emailstringyesValid email, max 254 characters.
phonestringyesE.164 format, e.g. +14155552671.
address.countrystringyesISO 3166-1 alpha-2 code, e.g. US.
address.citystringyesMax 250 characters.
address.line1stringyesMax 250 characters.
address.postalCodestringyesMax 250 characters.
address.regionstringnoState / province.
address.line2stringnoApartment, 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.
json
{
  "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 ​

GET/api/v2/cardholders/getBearer agent-vc
bash
curl https://agentcard.fluxapay.xyz/api/v2/cardholders/get \
  -H "Authorization: Bearer $AGENT_VC"

CLI:

bash
fluxa-wallet card holder me

Returns { "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 ​

FieldTypeDescription
idstringCardholder ID.
revisionintegerProfile version. Send it in If-Match on update to avoid overwriting concurrent changes.
kycStatusstringVerification state. See below.
nameLockedbooleantrue once identity is verified. The name can no longer be changed.
profileobjectThe details you submitted.
verificationUrlstring | nullLink where the account owner completes verification. Present only while verification is open.
nextActionstring | nullWhat to do next. See below.
failureReasonobject | nullPresent only when kycStatus is failed: { "code", "message" }.
createdAt, updatedAtstringISO 8601 timestamps.

Verification states ​

kycStatusMeaningnextActionWhat to do
pendingVerification is ready for the user.complete_verification (or wait)Send the user to verificationUrl.
processingVerification is being reviewed.waitPoll GET /cardholders/get.
approvedIdentity verified.nullYou can issue cards for products that require a cardholder.
failedVerification did not pass.updateShow failureReason.message to the user, correct the details with update, and verification restarts.
unverifiedVerification must be done again.updateCall update (an empty body {} is allowed) to start a new verification.

Common failureReason.code values on failed:

codeWhat to tell the user
name_mismatchThe name entered does not match the identity document. Correct firstName / lastName.
address_mismatch, address_name_mismatch, address_evidence_missingThe address could not be confirmed from the proof-of-address document. Check the address and retry.
identity_mismatchThe new verification must be completed by the same person as before.
kyc_failed, verification_evidence_missingVerification 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 ​

POST/api/v2/cardholders/updateBearer agent-vc

Partial 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.

bash
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:

bash
fluxa-wallet card holder update --profile @patch.json --revision 2
HeaderRequiredDescription
If-MatchnoCurrent 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:

kycStatusUpdate behavior
pending, processingNot allowed: 409 kyc_in_progress. Wait for the result.
failed, unverifiedAny field can be corrected. Verification restarts.
approvedemail 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 ​

StatuscodeWhat to do
400revision_invalidIf-Match must be a positive integer, e.g. "2".
404cardholder_not_foundNo profile yet. Create one.
409kyc_in_progressVerification is in progress. Wait, then retry.
409name_lockedThe verified name cannot be changed.
409revision_conflictRe-read the profile and retry with the new revision.
422profile_invalidA field is missing or invalid. details.field names it.
429rate_limitedToo many requests. Wait a minute and retry.
503—Profile service temporarily unavailable. Retry later.

Released under the MIT License.