Onboarding a Customer
From creating a customer to getting it approved.
There are six calls and they run in order. Read the requirements, create the customer and fill in the profile. Then send the information, upload the files and submit.
The order matters in two places, and both are marked below.
1. Read what's required
GET /v1/kyc/requirements/{country}
GET /v1/kyc/requirements/{country}?type=INDIVIDUAL
Do this before you collect anything. The requirement set tells you which fields and which documents this country needs, so you build your form from the API rather than from a spec that drifts.
There's no separate individual-requirements endpoint. Use ?type=INDIVIDUAL on the same one.
2. Create the customer
POST /v1/customers
{
"type": "BUSINESS",
"name": "Andes Trading S.A.S.",
"email": "[email protected]"
}Only type is required. It decides whether the customer runs KYC or KYB and can't be changed later, so get it right. There's no parentId, because your API key is the parent.
The customer comes back as NOT_STARTED.
3. Fill in the profile
PUT /v1/customers/{customerId}
This is the call that does the real work. Everything that classifies a customer lives here:
- The classification fields are
customerType,accountType,customerArchetypesanduseCases. The KYC reliance model is not among them. It is set by your partner relationship with Alfred rather than per customer. - Legal identity covers the registered name, trade name and business type. It also covers the incorporation country and date, and the registration number.
- Addresses have four separate roles: registered, physical, mailing and tax.
- Declared activity covers source of funds, source of wealth, expected monthly volume and operating countries. It also covers whether the customer acts on its own behalf and the rest of the risk profile.
accountPurposesdecides which kinds of virtual account you'll be allowed to create later. Work it out now rather than discovering the constraint at instrument time.industryCodesand the risk flags are what eligibility is decided on, as the next part explains.
Nothing verifies until this is populated.
Who can be onboarded
Alfred evaluates each customer against the rules of every market it will operate in, before any rail access. You supply industryCodes and the profile. The decision is Alfred's. Two lists apply in every market.
| List | What's on it |
|---|---|
| Prohibited jurisdictions | Afghanistan, Belarus, Congo, Cuba, the Gaza Strip, Iran, Iraq, Lebanon, Libya, Myanmar, North Korea, the Russian Federation, Somalia, South Sudan, Sudan, Syria, the Ukrainian territories, Venezuela, the West Bank and Yemen |
| Prohibited industries | Unlawful or abusive activity, fraud, gambling and online gaming, intellectual-property infringement, check cashing, bail bonds, collection agencies, counterfeit or unauthorized goods, drugs, adult content, multi-level marketing, undisclosed money services or money transmission, and precious metals |
Alfred may also decline a business it considers to pose elevated financial risk or legal liability, or to breach card-network rules or bank policy. Screen against both lists before you create the customer.
4. Send the information
POST /v1/customers/{customerId}/kyb
POST /v1/customers/{customerId}/kyc
This sends everything except files in one declaration. For a business that means the entity and all its related persons together, so there's no separate roster call.
Each customer has one draft. Posting again before you submit the verification replaces that draft instead of creating a second one. After submission the draft is frozen, and posting again returns submission_frozen.
This has to happen before you upload documents. The response carries a relatedPersonId for each person, and person-level uploads need those IDs.
Getting the roles right
Each related person carries ownershipPercentage and roles. Compliance acts on the two together.
| Role | Meaning |
|---|---|
ULTIMATE_BENEFICIAL_OWNER | Owns at or above the country's threshold |
SHAREHOLDER | Owns below the threshold |
CONTROLLING_PERSON | Control without qualifying ownership |
DIRECTOR | Board role |
LEGAL_REPRESENTATIVE | Authorized to bind the entity |
Every business needs a beneficial owner, a control person and a signer. At least one related person carries ULTIMATE_BENEFICIAL_OWNER, at least one carries CONTROLLING_PERSON and at least one carries LEGAL_REPRESENTATIVE. One person can hold several roles. Until someone carries LEGAL_REPRESENTATIVE, preflight reports persons.signer as outstanding.
The beneficial ownership threshold depends on the country. Ownership drives the UBO test, and the two have to agree, so anyone at or above the threshold is ULTIMATE_BENEFICIAL_OWNER, not SHAREHOLDER. The percentages across the roster don't need to sum to 100.
| Country | A beneficial owner holds |
|---|---|
| Colombia | 5% or more, whatever their nationality |
| Argentina | 10% or more of the capital or voting rights. If no one reaches it, the person in charge of management, administration or representation is the beneficial owner |
| Every other country | 25% or more |
Identifiers
The business and each person are identified separately. An owner's identifier never replaces the business's tax identifier, and one identifier type never stands in for another. An RFC is not a CURP, and a passport is not a tax identifier.
Every beneficial owner needs an identity document and a tax identifier. Whether an owner is a US person is decided by the individual, not by where the business is incorporated.
| Who | Identifier |
|---|---|
| The business | Its tax identifier: RFC, CUIT, NIT, CNPJ, or EIN for a US-formed business |
| A beneficial owner in Colombia | CC or CE. A passport only where the approved profile allows it |
| A beneficial owner in Argentina | CUIT, CUIL or CDI, plus a self-declared PEP status |
| A beneficial owner in Brazil | CPF |
| A US beneficial owner | SSN. An ITIN only where the approved profile allows it |
| Any other beneficial owner | A passport, plus the tax identifier of their own jurisdiction |
Tax identifiers are validated, including the check digit. A malformed RFC, CUIT, NIT, CNPJ or CPF is refused with 422 validation_error. A missing mandatory identifier blocks the affected product, and it cannot be cleared by entering a different document number.
5. Upload the files
POST /v1/customers/{customerId}/documents
POST /v1/persons/{relatedPersonId}/documents
Uploads are multipart and take one file per call. The maximum size is 10 MB, and the accepted formats are PDF, JPEG or PNG.
A document is filed by what it evidences rather than by a single type, so purposes[] takes several values and one incorporation document can satisfy more than one requirement.
Send purposes in lowercase, using the names preflight reports. An outstanding document comes back from preflight as documents.incorporation, so the purpose to send is incorporation. These are the common ones.
| Filed against | Purpose | What it is |
|---|---|---|
| The customer | incorporation | Certificate or deed of incorporation |
| The customer | proof_of_address | Proof of the registered or physical address |
| The customer | proof_of_business_activity | Evidence the business operates as declared |
| A related person | government_id | Front of a government-issued ID |
| A related person | government_id_back | Back of the same ID |
6. Check, then submit
POST /v1/customers/{customerId}/verifications/preflight
POST /v1/customers/{customerId}/verifications
Run preflight every time. It evaluates the draft and the uploads against the requirement set and tells you what's still outstanding, while everything is still editable. Fixing those items before you submit saves a rejection round trip. Preflight changes nothing and takes no idempotency key.
Then submit the verification. There's no body, because the customer has exactly one draft. Submitting freezes that draft, creates the verification and starts the review.
Wait on customer.verification.updated.
Reading where it got to
Watch the customer's own status. It tells you who has to act.
| Status | Means | Who acts |
|---|---|---|
INCOMPLETE | Fields or documents are missing. Preflight names which ones. | You |
AWAITING_UBO | The person roster is incomplete or unverified. | You |
AWAITING_QUESTIONNAIRE | Declared-activity answers are outstanding. | You |
UNDER_REVIEW | Submitted and being decided. | Alfred, unless an RFI is open |
ACTIVE | Approved. | Nobody |
REJECTED | Terminal. | Nobody |
When a reviewer asks for something
That's an RFI, and it's scoped to the customer.
GET /v1/customers/{customerId}/rfis?status=OPEN
POST /v1/rfis/{rfiId}/evidence
Each RFI names exactly what's needed in requirements[]. Answer with fields keyed by those names, plus any documentIds from an upload. That moves it to RECEIVED.
Read rfi_owner before chasing anyone. Under a reliance model the answer is often owed by you instead of by your end customer, and WAITING_ON_PARTNER is what says so. OVERDUE is not a soft status. A missed SLA is blocking.
Wait on rfi.updated.
Changing something afterwards
POST /v1/customers/{customerId}/reevaluate
Use this for corrected data, a new document or a changed archetype. Update through the normal endpoints and then re-evaluate.
Never submit a second verification.
Letting Alfred collect it instead
POST /v1/customers/{customerId}/onboarding-link
GET /v1/customers/{customerId}/onboarding-session
This mints a link your end customer opens themselves. They do identity capture, documents and the selfie directly with Alfred, so you never handle document images.
It's worth defaulting to for individual KYC. The alternative is building document capture and liveness for every country template yourself.
The response gives you a full url to send the customer to, a sessionId, and an expiresAt. The link carries its own expiry, so read the value rather than assuming a window.
A customer can have only one session at a time, and a new link supersedes the previous one.
Reading progress. GET /v1/customers/{customerId}/onboarding-session returns where the customer has got to, at two levels of detail.
| Field | What it tells you |
|---|---|
status | CREATED, OPENED, IN_PROGRESS, SUBMITTED, COMPLETED, EXPIRED or ABANDONED |
steps[] | Each step by name, with its own status, in order |
verificationId | Set once the session has been submitted and a verification exists |
linkExpiresAt | When the link stops working |
lastActivityAt | When the customer last did something, which is how you tell abandoned from slow |
The steps are IDENTITY, DOCUMENTS, SELFIE, QUESTIONNAIRE and REVIEW. Each one is PENDING, IN_PROGRESS, COMPLETED or SKIPPED. Progress is recorded on the session rather than on the link, which is what lets you see how far someone got.
Subscribe to customer.verification.updated rather than polling this.
Updated about 6 hours ago

