1Money launched the Business Onboarding v2 API in June 2026, which brings:
- Higher application data quality, leading to faster processing — upfront validation gives immediate feedback at submission, catching problems before an application reaches review and reducing manual back and forth over email/Slack.
- Automated RFIs, leading to faster feedback cycles — common RFI cases are now handled automatically, with no manual review step in between. RFIs are sent via webhook and can be replied to via API.
This guide explains how to migrate your Business Onboarding API integration from v1 to v2.
v2 Onboarding Flow
- Terms of Service Acceptance — the customer being onboarded accepts 1Money's Terms of Service through one of the acceptance flows.
- Business Account Initiation — the partner initiates the business sub-account via the Initiate Business Account API, passing either the
signature_idorsigned_atcaptured in step 1. - Document Upload — the partner uploads all required documents returned in step 2, using the Upload Company Document API for the business and the Upload Controller Document API for each controller.
- Submission for Review — once all documents from step 3 are uploaded, the partner submits the business sub-account for review, using the Business Account Submission API.
- Account Status Updates — throughout and after the onboarding flow, the partner listens to account webhooks for status changes including approval, rejection, and RFI issuance.
- Request for Information (RFI) — if additional documents are required during screening or compliance review, 1Money notifies the partner via email and/or RFI webhooks. See RFI Handling for details.
See more details in the Onboarding Overview page.
Reference
Endpoint map
| Purpose | v1 | v2 |
|---|---|---|
| Terms of Service | POST /v1/customers/tos_links | POST /v2/terms/BUSINESS_ONBOARDING/signatures |
| Create the application | POST /v1/customers | POST /v2/accounts/{main_account_id}/businesses |
| Upload document | Base 64 inside POST /v1/customers | Company Documents:PUT /v2/accounts/{main_account_id}/businesses/{sub_account_id}/documents/{document_type}Controller Documents: PUT /v2/accounts/{main_account_id}/businesses/{sub_account_id}/controllers/{controller_id}/documents/{document_type} |
| Submit for review | Implicit on create | POST /v2/accounts/{main_account_id}/businesses/{sub_account_id}/submit |
| Self-onboarding link | POST /v1/customers/lightweight, then POST /v1/customers/{customer_id}/onboarding_links | POST /v2/accounts/{main_account_id}/businesses/onboarding-sessions |
Status map
v1 status | v2 status |
|---|---|
INIT | DRAFT |
PENDING_REVIEW | PROCESSING |
PENDING_RESPONSE | ACTION_REQUIRED |
UNDER_REVIEW | UNDER_REVIEW |
APPROVED | APPROVED |
REJECTED | REJECTED |
Track A — API-driven onboarding
Use this track when you collect the KYB data yourself. If you would rather hand the customer a 1Money-hosted form, skip to Track B. Pick one track per business; do not mix them.
A1. Terms of Service
Only needed for the 1Money-hosted acceptance flow. Partner-hosted (reliance) acceptance skips this
endpoint entirely and sends timestamps directly on Initiate — see Accepting terms.
v1
POST /v1/customers/tos_links
Content-Type: application/json
{ "redirect_url": "https://www.partner.com/post-tos-acceptance" }Returns 200 OK with a pending session:
{
"url": "https://sandbox.1money.com/app/sign-agreement?session_token=4d5d8c45-9feb-422a-bb5e-0fd32e3b3c53&redirect_url=https%3A%2F%2Fwww.partner.com%2Fpost-tos-acceptance",
"session_token": "4d5d8c45-9feb-422a-bb5e-0fd32e3b3c53",
"expires_in": 3600
}You only get a usable id after the customer accepts: either from the redirect back to your
redirect_url with ?signed_agreement_id={id} appended, or, in sandbox, from
POST /v1/customers/tos_links/{session_token}/sign, which returns
{ "signed_agreement_id": "550e8400-..." }.
v2
POST /v2/terms/BUSINESS_ONBOARDING/signatures
Content-Type: application/json
{ "redirect_url": "https://www.partner.com/post-tos-acceptance" }Returns 201 Created:
{
"url": "https://sandbox.1money.com/app/sign-agreement?session_token=5761166d-7187-41d1-bc48-e64a87274516&redirect_url=https%3A%2F%2Fwww.partner.com%2Fpost-tos-acceptance",
"signature_id": "5761166d-7187-41d1-bc48-e64a87274516",
"expires_at": "2026-04-09T21:50:30Z",
"terms": [
{ "url": "https://www.1money.com/legal-and-privacy/privacy-policy", "type": "PRIVACY_POLICY", "name": "Privacy Policy", "status": "pending" },
{ "url": "https://www.1money.com/legal-and-privacy/cookie-policy", "type": "COOKIE_POLICY", "name": "Cookie Policy", "status": "pending" },
{ "url": "https://www.1money.com/legal-and-privacy/e-sign-consent", "type": "E_SIGN_CONSENT", "name": "E-Sign Consent", "status": "pending" },
{ "url": "https://www.1money.com/legal-and-privacy/commercial-account-terms-and-conditions", "type": "COMMERCIAL_ACCOUNT_TERMS_AND_CONDITIONS", "name": "Commercial Account Terms and Conditions", "status": "pending" },
{ "url": "https://www.1money.com/legal-and-privacy/international-customer-onboarding-disclosure", "type": "INTERNATIONAL_CUSTOMER_ONBOARDING_DISCLOSURE", "name": "International Customer Onboarding Disclosure", "status": "pending" }
]
}Three behavioural differences that will change your code:
signature_idis issued on create, not on acceptance. Stop parsing an id out of the redirect query
string; that pattern is v1-only. v2 has no.../signendpoint.session_tokenis no longer a response field. It appears insideurl, but it is not the value you
send to Initiate. Sendsignature_id.terms[]tells you which agreements the signature covers, with liveurlandnamevalues. Render
from this array rather than hard-coding agreement links.
A2. Initiate the business account
v1 collected the entire application in one call and submitted it for review on success. v2 spreads the same
data across a create, one upload per document, and an explicit submit.
v1
POST /v1/customers
Content-Type: application/json{
"business_legal_name": "Acme Corporation",
"business_description": "B2B SaaS platform for expense management",
"business_registration_number": "C1234567",
"email": "[email protected]",
"business_type": "corporation",
"business_industry": "541511",
"primary_website": "https://acme.com",
"date_of_incorporation": "2020-01-15",
"registered_address": {
"street_line_1": "123 Main Street",
"street_line_2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"subdivision": "CA",
"country": "USA",
"postal_code": "94102"
},
"physical_address": {
"street_line_1": "123 Main Street",
"street_line_2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"subdivision": "CA",
"country": "USA",
"postal_code": "94102"
},
"tax_country": "USA",
"tax_id": "12-3456789",
"tax_type": "EIN",
"account_purpose": "PURCHASE_GOODS_AND_SERVICES",
"source_of_funds": ["SALES_OF_GOODS_AND_SERVICES", "OWNERS_CAPITAL"],
"source_of_wealth": ["BUSINESS_DIVIDENDS_OR_PROFITS"],
"estimated_annual_revenue_usd": "1000000_9999999",
"expected_monthly_fiat_deposits": "100000_999999",
"expected_monthly_fiat_withdrawals": "100000_999999",
"publicly_traded": false,
"signed_agreement_id": "550e8400-e29b-41d4-a716-446655440000",
"associated_persons": [
{
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"birth_date": "1980-01-15",
"primary_nationality": "USA",
"residential_address": {
"street_line_1": "456 Oak Avenue",
"city": "San Francisco",
"state": "CA",
"subdivision": "CA",
"country": "USA",
"postal_code": "94103"
},
"country_of_tax": "USA",
"tax_type": "SSN",
"tax_id": "123-45-6789",
"has_ownership": true,
"ownership_percentage": 60,
"has_control": true,
"is_signer": true,
"is_director": false,
"identifying_information": [
{
"type": "passport",
"issuing_country": "USA",
"national_identity_number": "A12345678",
"image_front": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
"image_back": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
}
],
"poa": "data:application/pdf;base64,JVBERi0xLjQKJeLj...",
"poa_type": "utility_bill"
}
],
"intermediaries": [
{ "entity_name": "Global Holdings Ltd", "country": "KY", "ownership": 40 }
],
"documents": [
{ "doc_type": "registration_document", "file": "data:application/pdf;base64,JVBERi0xLjQKJeLj..." },
{ "doc_type": "constitutional_document", "file": "data:application/pdf;base64,JVBERi0xLjQKJeLj..." },
{ "doc_type": "ownership_chart", "file": "data:application/pdf;base64,JVBERi0xLjQKJeLj..." },
{ "doc_type": "proof_of_address", "file": "data:application/pdf;base64,JVBERi0xLjQKJeLj..." }
]
}Returns 201 Created:
{
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]",
"business_legal_name": "Acme Corporation",
"business_type": "corporation",
"business_industry": "541511",
"status": "PENDING_REVIEW",
"submitted_at": "2026-07-07T12:35:00Z",
"created_at": "2026-07-07T12:35:00Z",
"updated_at": "2026-07-07T12:35:00Z"
}Two things follow from that response, and all two change in v2:
- The status was already
PENDING_REVIEW. There was no draft to inspect and no second call to make. - Files rode along as base64
data:URIs, so a single application could be tens of megabytes of JSON.
v2
POST /v2/accounts/{main_account_id}/businesses
Content-Type: application/jsonReturns 201 Created with the business in DRAFT. This does not submit anything for review.
Request
{
"external_id": "partner-biz-98765",
"legal_name": "Acme Corporation",
"trading_name": "Acme Coffee",
"description": "B2B SaaS platform for expense management",
"type": "CORPORATION",
"industry": "SOFTWARE_DEVELOPMENT",
"business_email": "[email protected]",
"website": "https://acme.com",
"registration": {
"incorporated_at": "2020-01-15",
"address": {
"street_line_1": "123 Main Street",
"street_line_2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94102"
},
"tax_id": "12-3456789"
},
"operating_address": {
"street_line_1": "123 Main Street",
"street_line_2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94102"
},
"profile": {
"account_purpose": "PURCHASE_GOODS_AND_SERVICES",
"source_of_funds": ["SALES_OF_GOODS_AND_SERVICES", "OWNERS_CAPITAL"],
"source_of_wealth": ["BUSINESS_DIVIDENDS_OR_PROFITS"],
"estimated_annual_revenue_usd": "1000000_9999999",
"anticipated_monthly_fiat_activity": "100000_999999",
"ownership_structure": "PRIVATELY_OWNED",
"is_regulated_vasp": false
},
"controllers": [
{
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"dob": "1980-01-15",
"primary_nationality": "US",
"residential_address": {
"street_line_1": "456 Oak Avenue",
"city": "San Francisco",
"state": "CA",
"country": "US",
"postal_code": "94103"
},
"tax_info": { "country": "US", "number": "123-45-6789" },
"identification": { "type": "PASSPORT", "issuing_country": "US", "id_number": "A12345678" },
"profile": {
"has_ownership": true,
"ownership_percentage": 60,
"has_control": true,
"is_signer": true,
"is_director": false
}
}
],
"intermediaries": [
{ "entity_name": "Global Holdings Ltd", "country": "KY", "ownership": 40 }
],
"terms": [
{ "type": "BUSINESS_ONBOARDING", "signature_id": "5761166d-7187-41d1-bc48-e64a87274516" }
]
}controllers must contain at least one entry, and at least one controller must have
profile.has_control = true. Both are rejected at the request boundary, before any risk assessment.
Response
{
"account_id": "BZ-CDEF-5678",
"parent_account_id": "BZ-ABCD-1234",
"external_id": "partner-biz-98765",
"legal_name": "Acme Corporation",
"trading_name": "Acme Coffee",
"type": "CORPORATION",
"industry": "SOFTWARE_DEVELOPMENT",
"business_email": "[email protected]",
"status": "DRAFT",
"controllers": [
{
"id": "CT-WXYZ-5678",
"first_name": "John",
"last_name": "Smith",
"email": "[email protected]",
"documents": [
{ "type": "ID_FRONT", "status": "PENDING", "required": true },
{ "type": "POA", "status": "PENDING", "required": true }
]
}
],
"intermediaries": [
{ "entity_name": "Global Holdings Ltd", "country": "KY", "ownership": 40 }
],
"terms": [
{ "url": "https://www.1money.com/legal-and-privacy/privacy-policy", "type": "PRIVACY_POLICY", "name": "Privacy Policy", "status": "signed" }
],
"documents": [
{ "type": "CERT_OF_INC", "status": "PENDING", "required": true },
{ "type": "ART_OF_INC", "status": "PENDING", "required": true },
{ "type": "OWN_STRUCT_CORP", "status": "PENDING", "required": true },
{ "type": "AUTH_REP_LIST", "status": "PENDING", "required": true },
{ "type": "PROOF_OF_BUS_ADDR", "status": "PENDING", "required": true }
],
"created_at": "2026-07-07T12:35:00Z",
"updated_at": "2026-07-07T12:35:00Z"
}Truncated above — the response echoes registration, operating_address, profile, and the full
controllers[] and terms[] arrays, and adds submitted_at, errors[], and deleted_at.
Two fields drive everything downstream: account_id is {sub_account_id} in every later call, and
documents / controllers[].documents are your upload worklist.
Accepting terms
terms[] takes exactly one of two forms. Which key you send is determined by type, not by preference:
BUSINESS_ONBOARDING requires signature_id and rejects signed_at; the five standalone types
require signed_at and reject signature_id. Sending both on one entry is always an error.
1Money-hosted — a single entry referencing the signature from A1:
"terms": [{ "type": "BUSINESS_ONBOARDING", "signature_id": "5761166d-7187-41d1-bc48-e64a87274516" }]Partner-hosted — all five standalone agreements, each with its own acceptance timestamp. This is the
migration path for v1's terms_signed_at and requires 1Money compliance approval:
"terms": [
{ "type": "PRIVACY_POLICY", "signed_at": "2026-07-07T12:34:56Z" },
{ "type": "COOKIE_POLICY", "signed_at": "2026-07-07T12:34:56Z" },
{ "type": "E_SIGN_CONSENT", "signed_at": "2026-07-07T12:34:56Z" },
{ "type": "COMMERCIAL_ACCOUNT_TERMS_AND_CONDITIONS", "signed_at": "2026-07-07T12:34:56Z" },
{ "type": "INTERNATIONAL_CUSTOMER_ONBOARDING_DISCLOSURE", "signed_at": "2026-07-07T12:34:56Z" }
]Each type may appear at most once. The response terms[] is a different shape from the request —
{url, type, name, status} with status in lowercase (pending / signed) — and never echoes
signed_at or signature_id.
Field mapping: business
| v1 | v2 | Notes |
|---|---|---|
business_legal_name | legal_name | |
| — | trading_name | New. Optional, max 100 characters. |
| — | external_id | New. Optional partner reference; filterable on List. |
business_description | description | |
business_type | type | Lowercase → uppercase. See Enum conversions. |
business_industry | industry | NAICS code → closed enum. Lossy. |
email | business_email | Must be unique across your active customers. |
primary_website | website | |
date_of_incorporation | registration.incorporated_at | Same YYYY-MM-DD. |
registered_address | registration.address | Country alpha-3 → alpha-2. v1's subdivision is dropped; use state. |
physical_address | operating_address | Optional in v1 (defaulted to the registered address); required in v2 even when identical. |
tax_id + tax_type + tax_country | registration.tax_id | v2 has no tax_type and infers the country from registration.address.country. |
business_registration_number | — | No v2 equivalent. |
Field mapping: profile
| v1 | v2 | Notes |
|---|---|---|
account_purpose | profile.account_purpose | Same enum. Send account_purpose_other when OTHER. |
source_of_funds | profile.source_of_funds | At least one value. |
source_of_wealth | profile.source_of_wealth | At least one value. |
estimated_annual_revenue_usd | profile.estimated_annual_revenue_usd | Same band enum. |
expected_monthly_fiat_deposits + expected_monthly_fiat_withdrawals | profile.anticipated_monthly_fiat_activity | Two v1 bands collapse into one. Pick the band covering combined flow. |
publicly_traded (boolean) | profile.ownership_structure (enum) | See Enum conversions. |
| — | profile.is_regulated_vasp | New. Optional; omitting it means "not regulated". |
| — | profile.source_of_funds_details / source_of_wealth_details | Max 1000 characters each. Required for government entities and whenever enhanced due diligence applies. |
high_risk_activities (+ explanation) | — | No v2 equivalent. |
Field mapping: controllers
v1 associated_persons[] becomes v2 controllers[], with the role flags moved under profile and the
identity document flattened from an array to a single object.
| v1 | v2 | Notes |
|---|---|---|
associated_persons[] | controllers[] | Every entry requires residential_address, tax_info, identification, and profile. |
birth_date | dob | Same format. Must be 18 or older. |
middle_name | — | Dropped. |
primary_nationality, dual_nationality | same names | alpha-3 → alpha-2 on the request. |
has_ownership, ownership_percentage, has_control, is_signer, is_director | profile.* | Same flags, nested. ownership_percentage required when has_ownership is true. |
country_of_tax + tax_type + tax_id | tax_info: { country, number } | No type. US controllers still need an SSN in number. |
identifying_information[] (array) | identification (object) | One document per controller. national_identity_number → id_number. |
identifying_information[].image_front / image_back | ID_FRONT / ID_BACK uploads | See A3. |
poa + poa_type | POA upload | The poa_type classification is gone. |
identification.type values are PASSPORT, DRIVERS_LICENSE, NATIONAL_ID, and RESIDENCE_PERMIT. v1's PERMANENT_RESIDENCY_ID maps to RESIDENCE_PERMIT.
Enum conversions
Two conversions cannot be done mechanically and need a decision from you before you migrate.
business_industry (NAICS) → industry. v1 accepted any NAICS code as a string, e.g. 541511. v2
takes a closed enum of 68 values such as SOFTWARE_DEVELOPMENT, SCIENTIFIC_TECHNICAL, REAL_ESTATE,CRYPTO_MINING, MSB_PSP. The mapping is many-to-one and not derivable from the code itself. Build an
explicit lookup for the NAICS codes you actually send, and fail loudly on an unmapped code rather than
defaulting — industry feeds the risk assessment and the document checklist.
The remaining conversions are mechanical:
| Field | Conversion |
|---|---|
business_type → type | corporation → CORPORATION. Values: CORPORATION, LLC, PARTNERSHIP, SOLE_PROPRIETORSHIP, INVESTMENT_FUND, SOCIETIES, TRUST, GOVERNMENT, DAO. |
publicly_traded → ownership_structure | true → PUBLICLY_LISTED_COMPANY; false → PRIVATELY_OWNED. Other values available when you know better: FUND, OWNED_BY_TRUST, WHOLLY_OWNED_SUBSIDIARY_OF_PUBLICLY_LISTED_COMPANY, MAJORITY_OWNED_SUBSIDIARY_OF_PUBLICLY_LISTED_COMPANY, WHOLLY_OWNED_SUBSIDIARY_OF_FI_OR_NBFI, MAJORITY_OWNED_SUBSIDIARY_OF_FI_OR_NBFI, BEARER_SHARES_OR_UNDISCLOSED_NOMINEE_ARRANGEMENT. |
A3. Upload documents
In v1 every file travelled inside the create body as a data:...;base64,... string. In v2 each file is its
own request carrying raw bytes.
# Company document
curl -X PUT "https://api.1money.com/v2/accounts/BZ-ABCD-1234/businesses/BZ-CDEF-5678/documents/CERT_OF_INC" \
-H "Authorization: OneMoney-HMAC-SHA256 $API_KEY:$TIMESTAMP:$SIGNATURE" \
-H "Content-Type: application/octet-stream" \
-H 'Content-Disposition: attachment; filename="certificate_of_incorporation.pdf"' \
--data-binary @certificate_of_incorporation.pdf
# Controller document
curl -X PUT "https://api.1money.com/v2/accounts/BZ-ABCD-1234/businesses/BZ-CDEF-5678/controllers/CT-WXYZ-5678/documents/ID_FRONT" \
-H "Authorization: OneMoney-HMAC-SHA256 $API_KEY:$TIMESTAMP:$SIGNATURE" \
-H "Content-Type: application/octet-stream" \
-H 'Content-Disposition: attachment; filename="id_front.jpg"' \
--data-binary @id_front.jpgBoth return 200 OK:
{
"type": "ID_FRONT",
"status": "UPLOADED",
"required": true,
"controller_id": "CT-WXYZ-5678",
"filename": "id_front.jpg",
"uploaded_at": "2026-07-07T12:40:00Z"
}controller_id is present only on controller documents. Re-uploading the same document_type replaces the
previous file, which is how you answer a document RFI.
Constraints
- 10 MB maximum per file, for both company and controller documents.
- Company documents accept
.pdf,.jpeg,.jpg,.png,.csv,.xls,.xlsx. - Controller documents accept
.pdf,.jpeg,.jpg,.pngonly. Content-Dispositionwith afilenameis required — the extension selects the expected format.- The body is validated against that extension by inspecting the file's actual bytes, so a
.pdffilename
on a JPEG payload is rejected with400. Empty bodies are rejected. - Uploads are only accepted while the business is in
DRAFTorACTION_REQUIRED.
Controller documents
ID_FRONT, ID_BACK, POA — that is the whole set. There is no selfie or liveness requirement for
business controllers.
ID_BACK is required for DRIVERS_LICENSE, NATIONAL_ID, and RESIDENCE_PERMIT, and not required for
PASSPORT. v1 asked for image_back even on passports, so if you unconditionally sent two ID images, you
can drop the second one for passport holders. You may still upload it; it will be accepted as optional.
Company documents
The required set is computed per business from its type and the country in registration.address, so
read it from the response rather than hard-coding it. For reference, the baseline is:
type | Incorporated in the US | Incorporated elsewhere |
|---|---|---|
CORPORATION | CERT_OF_INC, ART_OF_INC, OWN_STRUCT_CORP, AUTH_REP_LIST, PROOF_OF_BUS_ADDR | CERT_OF_INC, MEMORANDUM_OF_ASSOCIATION, OWN_STRUCT_CORP, AUTH_REP_LIST, PROOF_OF_BUS_ADDR |
LLC | CERT_OF_FORM, ART_OF_ORG, OP_LP_AGREE, OWN_STRUCT_LLC, AUTH_REP_LIST, PROOF_OF_BUS_ADDR | same |
PARTNERSHIP | CERT_OF_FORM, PART_AGREE, OWN_STRUCT_PART, AUTH_REP_LIST, PROOF_OF_BUS_ADDR | same |
INVESTMENT_FUND | CERT_OF_INC, PROS_OFF_MEMO, FUND_STRUCT_CHART, AML_ATTEST_LET, OP_LP_AGREE, OWN_STRUCT_FUND, AUTH_REP_LIST, PROOF_OF_BUS_ADDR | same |
SOCIETIES | CERT_OF_INC, ART_OF_INC_BYLAW, AUTH_REP_LIST, PROOF_OF_BUS_ADDR | same |
TRUST | TRUST_AGREE, AUTH_REP_LIST, OWN_FORM_DOCS, PROOF_OF_BUS_ADDR | same |
SOLE_PROPRIETORSHIP | BUS_LIC, TAX_FILINGS, PROOF_OF_BUS_ADDR | same |
GOVERNMENT | CONST_DOCS, OWN_STRUCT_GOV, AUTH_REP_LIST, PROOF_OF_BUS_ADDR, PROOF_OF_FUNDS | same |
DAO | ART_OF_ORG, LIST_MGR, VOTING_RECS, OWN_STRUCT_DAO | same |
A4. Submit for review
POST /v2/accounts/{main_account_id}/businesses/{sub_account_id}/submitReturns 200 OK:
{
"account_id": "BZ-CDEF-5678",
"status": "PROCESSING",
"submitted_at": "2026-07-07T12:45:00Z"
}Submit succeeds only when every required term is signed, every required company and controller document is UPLOADED, and at least one controller has profile.has_control = true.
Track B — Self-onboarding
Use this track to hand the customer a 1Money-hosted form instead of collecting KYB data yourself. v1 needed two calls; v2 needs one.
v1
POST /v1/customers/lightweight
{
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"company_name": "Acme Corporation",
"customer_type": "BUSINESS"
}POST /v1/customers/{customer_id}/onboarding_links
{ "redirect_url": "https://www.partner.com/onboarding-complete", "locale": "en-US" }v2
POST /v2/accounts/{main_account_id}/businesses/onboarding-sessions
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"legal_name": "Acme Corporation",
"redirect_url": "https://www.partner.com/onboarding-complete"
}Returns 201 Created:
{
"account_id": "BZ-CDEF-5678",
"status": "DRAFT",
"onboarding_url": "https://sandbox.1money.com/api/hosted/public/onboarding/exchange?grant_token=550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2026-07-07T13:05:00Z"
}Webhooks
| v1 event | v2 event | Meaning |
|---|---|---|
kyb.completed | account.business.ready | Approved; the sub-account is usable. |
kyb.rejected | account.business.rejected | Rejected; terminal. |
kyb.additional_info_required | account.business.rfi | Information or documents needed from you. |
The envelope:
{
"event_name": "account.business.ready",
"resource": "business_account",
"data": {
"account_id": "BZ-CDEF-5678",
"status": "APPROVED"
}
}account.business.rfi carries the same errors[]:
{
"event_name": "account.business.rfi",
"resource": "business_account",
"data": {
"account_id": "BZ-CDEF-5678",
"status": "ACTION_REQUIRED",
"errors": [
{
"type": "DOCUMENT_MISSING",
"description": "Proof of business address is illegible.",
"target": {
"scope": "BUSINESS",
"data": {
"document_type": "PROOF_OF_BUS_ADDR"
}
}
}
]
}
}Signature verification, retry policy, and endpoint configuration are unchanged from v1.
Responding to an RFI
The v1 answer was to resend the whole payload. In v2:
- Read
data.errors[]— or re-GETthe business, whoseerrors[]holds the same open items. - Fix each issue at the address in
target:PATCHfor aproperty, re-PUTfor adocument_type
(scoped bycontroller_idwhen the target is a controller). - Call
submitagain.
submit is how the business re-enters review. Fixing the data alone leaves it in ACTION_REQUIRED.
