KYB field reference
Building a form from this?
GET /onboarding/schemareturns the same contract as JSON, with the enums inline — render from that rather than transcribing this page.
Every enum, every required field, and the rules that are only enforced at submission. Create a verification accepts partial data — this page is what “complete” means.
Enums
Send the exact string. Anything else is 422.
business.industry
The selection is the risk classification, so there is no free-text option
beyond other.
ecommerce_retail · cannabis · cbd · hemp · seeds · growshops ·
cannabis_clubs · standard_wellness_products · peptides · kratom ·
higher_risk_supplements · difficult_to_place_ecommerce ·
merchants_requiring_expensive_payment_routes ·
merchants_with_elevated_refund_or_chargeback_exposure · functional_mushrooms ·
adult · other
commercial.expected_monthly_processing_volume
A band, not a number. Sending 50000 is 422.
| Value | Range (EUR / month) |
|---|---|
range_0_1k |
up to 1,000 |
range_1k_10k |
1,000 – 10,000 |
range_10k_50k |
10,000 – 50,000 |
range_50k_250k |
50,000 – 250,000 |
range_250k_plus |
250,000 and above |
The rest
| Field | Values |
|---|---|
commercial.card_type |
eea · non_eea · mixed |
commercial.settlement_option |
fiat_eur_iban · crypto_usdt |
commercial.fee_handling |
customer_pays · niftipay_absorbs |
commercial.requested_payment_methods |
array of card · bank_transfer · crypto · other (max 10, at least 1 at submit) |
document_type (on upload) |
certificate_of_incorporation · proof_of_address · director_id · bank_statement · processing_statement |
Required documents
All five, and submission is blocked until every one is present:
certificate_of_incorporation · proof_of_address · director_id ·
bank_statement · processing_statement
Upload them with Upload a document.
There is no delete. Uploads append, and submission takes the latest of each type — so replacing a rejected document means uploading a new one of the same type, and the old one stays in the record. That is deliberate: evidence that was reviewed is part of the audit trail, and a partner able to remove it could quietly rewrite what a decision was made on.
Practically: never wait for a delete that is not coming, and do not treat a second upload of the same type as an error. It is the supported way to correct one.
While any are missing, POST /verifications answers 200 with
"blocked_by": "missing_documents" and lists exactly which in
missing_documents. That is the expected middle of the flow, not an error.
Directors and UBOs
Both are arrays of the same person shape, max 20 each.
{
"full_name": "Ada Lovelace",
"date_of_birth": "1815-12-10",
"nationality": "GB",
"country": "GB",
"email": "ada@example.com",
"phone": "+44…",
"address_line1": "…",
"city": "…",
"postal_code": "…"
}full_name, date_of_birth and nationality are required on every person.
Everything else is optional. date_of_birth must parse as a date.
A UBO carries one extra field:
| Field | Rule |
|---|---|
ownership_percent |
number, 25 to 100. Decimals are accepted (33.3). |
The rules you asked about, stated plainly
Percentages do not have to total 100. There is no sum check. The 25% floor is the whole rule — a UBO is a 25%-or-more shareholder, so anyone below that threshold is not a UBO and should not be listed. A company whose ownership is spread across five 20% holders therefore has no UBOs to report — see the next rule for how to say so.
A company with no qualifying UBO declares it. If nobody holds 25% or more — five 20% holders, a foundation, a widely-held entity — send:
{
"ubos": [],
"ubo_declaration": "no_qualifying_ubo"
}ubo_declaration is has_ubos by default, and that default is what requires at
least one UBO. It is a declaration, not an inference from an empty list: “not
filled in yet” and “genuinely nobody” are different facts, and a file that cannot
tell them apart is worth nothing to the reviewer. Declaring none and listing
someone is refused as the contradiction it is.
At least one director is required either way — with no UBO to report, the senior managing officials are what the file rests on.
The value is returned on reads as ubo_declaration, beside ubos.
Overlap is allowed and expected. directors and ubos are independent
lists with no cross-check and no deduplication. A founder who is both a director
and a 100% shareholder belongs in both arrays, with the same details. We do
not infer one from the other.
Your own fields
Our schema is fixed, and fields outside it are 422. If your underwriting needs
values we do not have, send them in additional_data:
{
"business": {
"legal_name": "Example SL"
},
"additional_data": {
"entity_type": "sarl",
"regulated_since": 2019,
"markets": [
"FR",
"BE"
]
}
}We store, return and relay them. We never render or interpret them — you collected them in your own UI, from your own user, under your own privacy notice, and that is what keeps this simple for both of us.
| Rule | |
|---|---|
| Scope | Yours alone. Another partner on the same merchant cannot read your keys, and you cannot read theirs. |
| Values | string · number · boolean · null · or an array of those |
| Nesting | Not accepted. Flatten the key instead — owner_name, not owner: { name }. |
| Limits | 50 keys · 100 chars per key · 2000 chars per string · 50 items per array |
| Writing | Merged, not replaced. Send null for a key to clear it; omit it to leave it alone. |
| Over-long values | Refused, never truncated — a shortened answer means you believe your underwriter has a value it does not have. |
Returned on GET /verifications as additional_data, containing your slice
only.
The nesting limit is the boundary that keeps this a field bag rather than a document store. If you need structure, flatten.
Required at submission
A verification can be created and updated with any subset. These are what must be present before the case leaves draft:
| Group | Fields |
|---|---|
| Business | legal_name, registration_number, industry, business_registration_country, incorporation_country, incorporation_date |
| Registered address | line1, city, postal_code, country |
| Contact | first_name, last_name, email (must be a valid email) |
| Commercial | expected_monthly_processing_volume, card_type, settlement_option, fee_handling, requested_payment_methods (≥1) |
| People | directors (≥1); ubos (≥1) unless ubo_declaration is no_qualifying_ubo |
| Consent | consent.version |
| Evidence | all five document types |
Optional throughout: trading_name, website (must be a valid URL if sent),
registered_address.line2, contact.phone.
incorporation_date must parse and not be in the future.
consent.marketing_opt_in is genuinely optional and defaults to false. It is
deliberately not a precondition of onboarding — consent that is required is not
freely given, and a partner submitting on a customer’s behalf cannot honestly
assert it. The mandatory gate is
agreement acceptance, recorded at submit.
How completeness is reported
You never have to guess what is outstanding. Every POST /verifications attempts
submission and tells you what stopped it:
{ "data": { "verification": { … },
"submitted": false,
"blocked_by": "validation_failed",
"missing_documents": [],
"incomplete_fields": [ { "path": ["ubos"],
"message": "At least one UBO is required, or declare uboDeclaration = no_qualifying_ubo if no person holds 25% or more" } ] } }
So the integration loop is: send what you have, read incomplete_fields and
missing_documents, send more. An empty POST is a valid way to re-attempt
submission once evidence is uploaded.