KYB Level 1 - API
This guide describes the fully server-to-server flow to complete KYB Level 1. Use it when you want to collect company data, UBOs and documents on your own frontend and submit everything via the API instead of redirecting the user to the Web SDK flow.
KYB is always performed against a COMPANY subaccount. Pass its ID as ?subAccountId= on every call below.
The complete flow is:
- Create a
COMPANYsubaccount. - Upload the corporate documents (Certificate of Incorporation, Tax Identification Document) and one identification document per UBO.
- Register each Ultimate Beneficial Owner (UBO).
- Submit KYB Level 1 and wait for approval.
Additional documents (Proof of Financial Capacity, Proof of Revenue) are only required for the follow-up rails - see KYB USD and KYB EUR.
Step 1 - Create a COMPANY Subaccount
HTTP POST Request
https://api.sandbox.avenia.io:10952/v2/account/sub-accounts
Sample JSON Body
{
"accountType": "COMPANY",
"name": "ACME Digital Solutions Ltd"
}
JSON Response
{
"id": "a3f2c891-7b4d-4e6a-9d15-c82e0f3a1b57"
}
Save this id and pass it as ?subAccountId=a3f2c891-7b4d-4e6a-9d15-c82e0f3a1b57 on every subsequent request.
See Subaccount Management for the full subaccount reference.
Step 2 - Upload Required Documents
At minimum, three documents must be uploaded before KYB Level 1 can be submitted:
| Document | documentType | When required |
|---|---|---|
| Certificate of Incorporation | CERTIFICATE-OF-INCORPORATION | KYB Level 1 |
| Tax Identification Document | COMPANY-TAX-IDENTIFICATION-DOCUMENT | KYB Level 1 |
| UBO identification (per UBO) | ID, DRIVERS-LICENSE, PASSPORT, RESIDENCE-PERMIT | KYB Level 1 |
Each upload is a two-phase operation: first create the document record, then PUT the file bytes to the returned pre-signed URL.
Step 2a - Create the Document Record
HTTP POST Request
https://api.sandbox.avenia.io:10952/v2/documents?subAccountId={subAccountId}
Sample JSON Body
{
"documentType": "CERTIFICATE-OF-INCORPORATION",
"isDoubleSided": false
}
JSON Response
{
"id": "2d7f4b91-e3a8-4c1f-9e62-5b0d7a8f3c16",
"uploadURLFront": "https://s3.amazonaws.com/bucket/path?X-Amz-Signature=...",
"uploadURLBack": ""
}
Only identification documents (ID, DRIVERS-LICENSE, PASSPORT, RESIDENCE-PERMIT) support isDoubleSided: true. All corporate documents are single-sided.
Step 2b - Upload the File
curl -X PUT "<uploadURLFront>" \
-H "Content-Type: application/pdf" \
-H "If-None-Match: *" \
--data-binary "@/path/to/certificate.pdf"
A 200 OK or 204 No Content response means the upload succeeded. Corporate documents accept application/pdf; identification documents accept image/png or image/jpeg.
Step 2c - Poll Until Ready
GET https://api.sandbox.avenia.io:10952/v2/documents/{documentId}?subAccountId={subAccountId}
{
"document": {
"id": "2d7f4b91-e3a8-4c1f-9e62-5b0d7a8f3c16",
"documentType": "CERTIFICATE-OF-INCORPORATION",
"uploadStatusFront": "PROCESSED",
"ready": true
}
}
Only documents with ready: true can be referenced in Steps 3 and 4. Listen for the matching document webhook if you prefer not to poll.
Step 3 - Create UBOs (Ultimate Beneficial Owners)
At least one UBO is required, and at least one UBO must have hasControl set to a valid corporate role.
HTTP POST Request
https://api.sandbox.avenia.io:10952/v2/account/ubos?subAccountId={subAccountId}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
fullName | string | Yes | Full legal name (max 256 chars). |
dateOfBirth | string | Yes | YYYY-MM-DD. Minimum age 18. |
countryOfTaxId | string | Yes | ISO 3166-1 alpha-3 (e.g. BRA, USA). |
taxIdNumber | string | Yes | Tax identification number. For BRA a valid CPF is required; for USA a 9-digit number is required. |
percentageOfOwnership | string | Yes | Ownership share as a numeric string between "0" and "100" (e.g. "25.5"). |
hasControl | string | Cond. | Corporate role. Required for at least one UBO in the set. See the table below for valid values. |
uploadedIdentificationId | string | Yes | ID from Step 2 of this UBO's identification document (must have ready: true). |
documentCountry | string | Yes | Country that issued the identification document (ISO alpha-3). |
streetLine1 | string | Yes | Street address, first line (max 256 chars). |
streetLine2 | string | No | Street address, second line. |
streetLine3 | string | No | Street address, third line. |
city | string | Yes | City of residence. |
state | string | Yes | ISO 3166-2 subdivision code (e.g. SP, NY). |
zipCode | string | Yes | Postal code. |
country | string | Yes | Country of residence (ISO alpha-3). |
Valid hasControl roles
CEO, CFO, COO, CTO, President, Vice President, Director, Managing Director, Managing Partner, General Partner, Partner, Secretary, Treasurer, Chairman, Board Member, Authorized Signatory, General Counsel, Owner, Founder, Manager, Member, Comptroller, Chief Compliance Officer.
email and phone are not accepted by this endpoint. Contact details for the UBO are collected elsewhere.
Sample JSON Body
{
"fullName": "USERS FULL NAME",
"dateOfBirth": "1988-07-22",
"countryOfTaxId": "BRA",
"taxIdNumber": "11182159111",
"percentageOfOwnership": "100",
"hasControl": "CEO",
"uploadedIdentificationId": "8b3e1a72-5c9f-4d2e-b847-xxxxxxxxxxxxx",
"documentCountry": "BRA",
"streetLine1": "RUA AURORA 456 APTO 12",
"streetLine2": "",
"streetLine3": "",
"city": "SAO PAULO",
"state": "SP",
"zipCode": "01209-001",
"country": "BRA"
}
JSON Response
{
"id": "3b7e9f24-1c5a-4d8e-a613-xxxxxxx"
}
Save every UBO id - all of them must be listed in Step 4.
A single company can register up to 50 UBOs.
Step 4 - Submit KYB Level 1
HTTP POST Request
https://api.sandbox.avenia.io:10952/v2/kyc/new-level-1/api?subAccountId={subAccountId}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
uboIds | array | Yes | UUIDs of all UBOs created in Step 3. At least one must have hasControl set. |
companyLegalName | string | Yes | Company legal name (stored uppercase). |
companyRegistrationNumber | string | Yes | Company registration number. |
taxIdentificationNumberTin | string | Yes | Company TIN/CNPJ. |
businessActivityDescription | string | Yes | Free-text description (1-2000 chars). |
reasonForAccountOpening | string | Yes | Enum (see below). |
sourceOfFundsAndIncome | string | Yes | Enum (see below). |
numberOfEmployees | string | Yes | Enum (see below). |
estimatedAnnualRevenueUsd | string | Yes | Enum (see below). |
estimatedMonthlyVolumeUsd | string | Yes | Positive integer as string (e.g. "2000"). |
countryTaxResidence | string | Yes | ISO alpha-3, or the literal "N/A". |
countrySubdivisionTaxResidence | string | No | ISO 3166-2 subdivision code (e.g. SP). |
companyStreetLine1 | string | Yes | Company address, first line (max 256 chars). |
companyStreetLine2 | string | No | Company address, second line. |
companyStreetLine3 | string | No | Company address, third line. |
companyCity | string | Yes | Company city (max 256 chars). |
companyState | string | Yes | ISO subdivision for the company country. |
companyZipCode | string | Yes | Company postal code (max 256 chars). |
companyCountry | string | Yes | Company country name or ISO alpha-3 (normalized server-side). |
certificateOfIncorporationDocumentId | string | Yes | ID of a CERTIFICATE-OF-INCORPORATION document uploaded in Step 2 (must have ready: true). |
taxIdentificationDocumentId | string | Yes | ID of a COMPANY-TAX-IDENTIFICATION-DOCUMENT uploaded in Step 2 (must have ready: true). |
website | string | No | Company website URL. |
socialMedia | string | No | Social media URL. |
emailPixKey | string | No | Email PIX key, when applicable. |
sandboxReject | boolean | No | Set to true to simulate a rejected KYB in the sandbox environment. Defaults to false. |
Enum Values
| Field | Valid Values |
|---|---|
reasonForAccountOpening | charitable_donations, ecommerce_retail_payments, investment_purposes, other, payments_to_friends_or_family_abroad, payroll, personal_or_living_expenses, protect_wealth, purchase_goods_and_services, receive_payments_for_goods_and_services, tax_optimization, third_party_money_transmission, treasury_management |
sourceOfFundsAndIncome | business_loans, grants, inter_company_funds, investment_proceeds, legal_settlement, owners_capital, pension_retirement, sale_of_assets, sales_of_goods_and_services, third_party_funds, treasury_reserves |
numberOfEmployees | 1-10, 11-50, 51-200, 201-500, 501-1000, 1001+ |
estimatedAnnualRevenueUsd | less_than_100k, 100k_to_1m, 1m_to_10m, 10m_to_50m, 50m_to_100m, more_than_100m |
Sample JSON Body
{
"uboIds": ["3b7e9f24-1c5a-4d8e-a613-8f2b0d4e7c91"],
"companyLegalName": "ACME SOLUCOES DIGITAIS LTDA",
"companyRegistrationNumber": "42731085000167",
"taxIdentificationNumberTin": "42.731.085/0001-67",
"businessActivityDescription": "Custom software development and IT consulting services. The company provides SaaS solutions for financial institutions.",
"website": "https://www.nexoradigital.com.br",
"socialMedia": "https://linkedin.com/company/nexoradigital",
"reasonForAccountOpening": "receive_payments_for_goods_and_services",
"sourceOfFundsAndIncome": "sales_of_goods_and_services",
"numberOfEmployees": "1-10",
"estimatedAnnualRevenueUsd": "less_than_100k",
"estimatedMonthlyVolumeUsd": "2000",
"countryTaxResidence": "BRA",
"countrySubdivisionTaxResidence": "BR-SP",
"companyStreetLine1": "AV PAULISTA 1000 CONJ 204",
"companyStreetLine2": "",
"companyStreetLine3": "",
"companyCity": "SAO PAULO",
"companyState": "SP",
"companyZipCode": "01310-100",
"companyCountry": "Brazil",
"certificateOfIncorporationDocumentId": "2d7f4b91-e3a8-4c1f-9e62-5b0d7a8f3c16",
"taxIdentificationDocumentId": "6a1c8e35-9b4f-4d7a-e291-3f8c5b0e1a74"
}
JSON Response
{
"id": "9c4a2f71-8d3e-4b6c-e157-2a0f9b5d3e84"
}
The returned id is the KYB Level 1 attempt ID.
Step 5 - Poll the KYB Attempt
Track progress via the shared KYC attempts endpoints. Polling is optional when webhooks are configured.
HTTP GET Request
https://api.sandbox.avenia.io:10952/v2/kyc/attempts/{attemptId}?subAccountId={subAccountId}
Sample JSON Response
{
"attempt": {
"id": "9c4a2f71-8d3e-4b6c-e157-2a0f9b5d3e84",
"levelName": "kyb-level-1",
"status": "COMPLETED",
"result": "APPROVED",
"resultMessage": "",
"retryable": false,
"createdAt": "2026-03-19T22:09:52.629984Z",
"updatedAt": "2026-03-19T22:09:52.629984Z"
}
}
result is APPROVED or REJECTED. If REJECTED and retryable is true, fix the data and resubmit Step 4.
See KYC Level 1 - List attempts for the full list/filter reference.
Quick Reference
| Step | Method | Endpoint | Output |
|---|---|---|---|
| 1 | POST | /v2/account/sub-accounts | subAccountId |
| 2 | POST + PUT | /v2/documents?subAccountId={id} + S3 URL | Document IDs (Cert. of Inc., Tax ID, UBO IDs) |
| 3 | POST | /v2/account/ubos?subAccountId={id} | UBO ID |
| 4 | POST | /v2/kyc/new-level-1/api?subAccountId={id} | KYB Level 1 attempt ID |
| 5 | GET | /v2/kyc/attempts/{attemptId}?subAccountId={id} | Poll until APPROVED, or wait for webhook |
After KYB Level 1 is approved, move on to KYB USD and/or KYB EUR to unlock the corresponding fiat rails.