diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml
index 0fef08e3..7352105c 100644
--- a/mintlify/openapi.yaml
+++ b/mintlify/openapi.yaml
@@ -868,11 +868,15 @@ paths:
schema:
type: string
post:
- summary: Generate a hosted KYC link for an existing customer
+ summary: Generate a hosted KYC/KYB link for an existing customer
description: |
Generate a single-use hosted URL the customer can complete to verify their identity, and (where supported) a provider-specific `token` for embedding the verification flow directly via the provider's SDK.
- The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's `kycStatus`; the customer remains `PENDING` until they complete (or fail) the hosted flow.
+ The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's verification status; the customer remains at their current status until they complete (or fail) the hosted flow.
+
+ This endpoint generates the link for both customer types; `customerType` selects which flow the provider runs. `INDIVIDUAL` runs identity verification (KYC), tracked on `kycStatus`. `BUSINESS` runs business verification (KYB), tracked on `kybStatus` — the flow confirms the company details, collects formation, ownership, and proof-of-address documents, and gathers the control person and every beneficial owner holding 25% or more. Business information already supplied via `POST /customers` or `PATCH /customers/{customerId}` is prefilled, so send what you have before generating the link.
+
+ The hosted link is one of two ways to verify a customer. To collect the data yourself instead, submit it through `POST /customers`, `POST /beneficial-owners` (business customers), and `POST /documents`, then call `POST /verifications`. Both paths produce the same status transitions and the same `CUSTOMER.KYC_*` / `CUSTOMER.KYB_*` webhooks.
Each call returns a fresh link. Previously-issued links are not invalidated, but they remain single-use and will expire on their own. For request-level retry safety, include an `Idempotency-Key` header.
operationId: createCustomerKycLink
diff --git a/mintlify/snippets/kyc/kyb-sandbox-suffixes.mdx b/mintlify/snippets/kyc/kyb-sandbox-suffixes.mdx
new file mode 100644
index 00000000..8d6a516d
--- /dev/null
+++ b/mintlify/snippets/kyc/kyb-sandbox-suffixes.mdx
@@ -0,0 +1,14 @@
+Business customers are always created with `kybStatus: UNVERIFIED`. The **last 3 characters** of `businessInfo.registrationNumber` decide what happens when the business is verified — whether you submit with `POST /verifications` or send the business through a hosted KYB link:
+
+| Suffix | Outcome |
+|--------|---------|
+| **003** | No auto-decision. Document and UBO verification run for real — the hosted flow asks for company documents and beneficial owners, and `POST /verifications` applies normal validation (`RESOLVE_ERRORS` until the data is complete). **Use this to test the link flow.** |
+| **001** | Same as `003` — no auto-decision, normal validation applies |
+| **002** | Immediate `kybStatus: REJECTED` (`verificationStatus: REJECTED`), skipping data and document validation |
+| **Any other** | Immediate `kybStatus: APPROVED` (`verificationStatus: APPROVED`), skipping data and document validation |
+
+
+A registration number that doesn't end in `001`, `002`, or `003` is **auto-approved on the spot**. The hosted flow then has nothing left to verify, so it asks for no documents and no beneficial owners — which looks exactly like a broken KYB flow but isn't. Always use a `003` suffix when you want to exercise document and UBO collection.
+
+
+Once a business customer is approved or rejected, further `POST /verifications` calls return `400`.
diff --git a/mintlify/snippets/kyc/kyc-unregulated.mdx b/mintlify/snippets/kyc/kyc-unregulated.mdx
index 5fecc17a..6d23c06b 100644
--- a/mintlify/snippets/kyc/kyc-unregulated.mdx
+++ b/mintlify/snippets/kyc/kyc-unregulated.mdx
@@ -1,41 +1,90 @@
import KybDataRequirements from '/snippets/kyc/kyb-data-requirements.mdx'
-**Unregulated platforms** rely on Grid to run KYC for individuals and KYB for businesses. You can onboard customers either through the **hosted KYC/KYB link flow** below, or by **submitting customer data directly through the API**. Both paths produce the same `kycStatus` transitions and emit the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks.
+**Unregulated platforms** rely on Grid to run KYC for individuals and KYB for businesses. You can onboard customers either through the **hosted KYC/KYB link flow** below, or by **submitting customer data directly through the API**. Both paths cover KYC and KYB, produce the same status transitions — on `kycStatus` for individuals, `kybStatus` for businesses — and emit the same `CUSTOMER.KYC_*` / `CUSTOMER.KYB_*` webhooks. You can also mix them: supply what you already hold through the API, then let the hosted flow collect the rest.
Either path works for unregulated platforms:
-- **Hosted flow**: Redirect customers to a Grid-hosted link (or embed the provider SDK) for identity verification. Best when you want Grid to handle the entire collection UX.
+- **Hosted flow**: Redirect customers to a Grid-hosted link (or embed the provider SDK). Covers individuals and businesses alike. Best when you want Grid to handle the entire collection UX.
- **Direct API onboarding**: Collect customer information in your own UI and submit it via the API. For `INDIVIDUAL` customers (KYC), personal information goes through `POST /customers`. For `BUSINESS` customers (KYB), you also register beneficial owners via `POST /beneficial-owners`. Submit for review with `POST /verifications`.
-### Hosted KYC Link Flow
+### Hosted KYC and KYB Link Flow
-The hosted KYC flow provides a secure, hosted interface where customers can complete their identity verification and onboarding process.
+The hosted flow provides a secure, hosted interface where customers complete verification. It serves both customer types — there is no separate KYB link endpoint. `POST /customers/{customerId}/kyc-link` generates the link, and `customerType` selects which flow the provider runs:
-The flow is two steps: create the customer with the information you have, then generate a hosted KYC link for that customer. The customer's `kycStatus` starts at `UNVERIFIED` and moves to `PENDING` once they complete the hosted flow and review begins.
+- **`INDIVIDUAL`** — identity verification (KYC), tracked on `kycStatus`.
+- **`BUSINESS`** — business verification (KYB), tracked on `kybStatus`. The applicant confirms the company details, uploads the formation, ownership, and proof-of-address documents, and declares the control person and every beneficial owner holding 25% or more.
+
+The flow is two steps: create the customer with the information you have, then generate the link. The status starts at `UNVERIFIED` and moves to `PENDING` once the customer completes the flow and review begins.
#### 1. Create the customer
-Create the customer with `POST /customers`, supplying at least `customerType` and any fields you already have. See [Configuring Customers](/payouts-and-b2b/onboarding/configuring-customers) for the full list of optional pre-fill fields.
+
+
+ Supply at least `customerType` and any fields you already have.
-```bash
-curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
- -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
- -H "Content-Type: application/json" \
- -d '{
- "customerType": "INDIVIDUAL",
- "platformCustomerId": "9f84e0c2a72c4fa",
- "region": "US",
- "currencies": ["USD", "USDC"],
- "email": "jane.doe@example.com",
- "fullName": "Jane Doe"
- }'
-```
+ ```bash
+ curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
+ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "customerType": "INDIVIDUAL",
+ "platformCustomerId": "9f84e0c2a72c4fa",
+ "region": "US",
+ "currencies": ["USD", "USDC"],
+ "email": "jane.doe@example.com",
+ "fullName": "Jane Doe"
+ }'
+ ```
+
+
+
+ `customerType: BUSINESS` requires these fields — the request fails without them:
+
+ | Field | Notes |
+ |-------|-------|
+ | `businessInfo.legalName` | Full legal entity name |
+ | `businessInfo.country` | Country of incorporation, ISO 3166-1 alpha-2. Sets the applicant's jurisdiction and the tax-ID format validated against |
+ | `businessInfo.taxId` | Validated against `businessInfo.country` |
+ | `businessInfo.incorporatedOn` | `YYYY-MM-DD` |
+
+ ```bash
+ curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers" \
+ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "customerType": "BUSINESS",
+ "platformCustomerId": "biz-acme-001",
+ "region": "US",
+ "currencies": ["USD", "USDC"],
+ "email": "finance@acme.com",
+ "businessInfo": {
+ "legalName": "Acme Corporation, Inc.",
+ "country": "US",
+ "taxId": "47-1234567",
+ "incorporatedOn": "2018-03-14",
+ "registrationNumber": "5523041",
+ "entityType": "LLC"
+ }
+ }'
+ ```
+
+ Everything else is optional to the schema, but three groups matter in practice:
+
+ - **Contact channels** — `email` and/or `phoneNumber`, plus `businessInfo.primaryContactFirstName` and `primaryContactLastName`, are required in regions that verify a named representative before verification begins (for example the EU).
+ - **Currency-driven fields** — anything listed in `providerRequiredCustomerFields` for a currency the business will transact in.
+ - **Everything the review needs** — `address`, `registrationNumber`, `entityType`, `countriesOfOperation`, `businessType`, `purposeOfAccount`, `sourceOfFunds`, and the expected-activity fields aren't enforced at creation. Whatever you omit, the applicant is asked for in the flow.
+
+
Persist the returned `id` (the Grid customer ID) — you'll need it for the next step.
-#### 2. Generate a KYC link
+
+Business information you supply is prefilled into the hosted flow, so send as much as you have — via `POST /customers` or a later `PATCH /customers/{customerId}` — **before** generating the link. Every field you pass is one the applicant doesn't retype, and one fewer chance for their answer to diverge from your record.
+
+
+#### 2. Generate the link
```bash
curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:019542f5-b3e7-1d02-0000-000000000001/kyc-link" \
@@ -59,18 +108,18 @@ curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:0195
```
-The response always includes `kycUrl` for the hosted flow. For providers that support direct SDK integration (currently SUMSUB), a `token` is also returned — you can pass this to the provider's web SDK to embed verification in your own UI instead of redirecting. Both paths update the customer's `kycStatus` identically.
+The response field is named `kycUrl` for both customer types; for a `BUSINESS` customer it opens the KYB flow. For providers that support direct SDK integration (currently SUMSUB), a `token` is also returned — pass it to the provider's web SDK to embed verification in your own UI instead of redirecting. Both update the customer's status identically.
-#### Complete KYC Process
+#### The flow end to end
- Call `POST /customers` with `customerType` and any pre-fill fields you have. The returned `id` is the customer's Grid ID; their `kycStatus` starts at `UNVERIFIED`, becomes `PENDING` while under review, and lands on `APPROVED` or `REJECTED` when the decision is made.
+ Call `POST /customers` with `customerType` and any pre-fill fields you have. The returned `id` is the customer's Grid ID. Their status — `kycStatus` for an individual, `kybStatus` for a business — starts at `UNVERIFIED`, becomes `PENDING` while under review, and lands on `APPROVED` or `REJECTED` when the decision is made. (`HOLD` means the customer may be asked to supply more information.)
- Some regions (for example, the EU) require the customer's email and/or phone to be verified before KYC can begin. When this applies, the customer carries a `contactVerification` object listing **only the channels that are required** — for example `{ "email": "PENDING", "phone": "PENDING" }`, or just `{ "email": "PENDING" }` if a provider requires email alone. When the object is absent, no contact verification is required and you can skip this step.
+ Some regions (for example, the EU) require the customer's email and/or phone to be verified before verification can begin. For a business customer, these are the primary contact's details. When this applies, the customer carries a `contactVerification` object listing **only the channels that are required** — for example `{ "email": "PENDING", "phone": "PENDING" }`, or just `{ "email": "PENDING" }` if a provider requires email alone. When the object is absent, no contact verification is required and you can skip this step.
For each channel that's present, drive a send-then-confirm pair (the provider delivers a one-time code by email/SMS):
@@ -80,11 +129,11 @@ The response always includes `kycUrl` for the hosted flow. For providers that su
Re-POST the send endpoint to resend a code. Each channel moves `PENDING` → `VERIFIED`. (`verify-email`/`verify-phone` return `409` for a channel the provider doesn't require.)
- Generating the KYC link before every present channel is `VERIFIED` returns `409`.
+ Generating the link before every present channel is `VERIFIED` returns `409`.
-
+
Call `POST /customers/{customerId}/kyc-link`. Each call returns a fresh single-use `kycUrl` and `expiresAt`; previously-issued links remain single-use but aren't invalidated.
@@ -93,7 +142,7 @@ The response always includes `kycUrl` for the hosted flow. For providers that su
- Redirect the customer to `kycUrl`, or — if you want to embed the flow directly — initialize the provider's SDK with the returned `token`.
+ Redirect the customer to `kycUrl`, or — if you want to embed the flow directly — initialize the provider's SDK with the returned `token`. A business applicant completes the company details, uploads the company documents, and declares the beneficial owners here.
The hosted URL is single-use and expires at `expiresAt`. If a customer needs to retry, call the endpoint again to generate a new link.
@@ -104,14 +153,20 @@ The response always includes `kycUrl` for the hosted flow. For providers that su
Reaching your `redirectUri` only means the customer **finished the hosted flow** — not that they were approved. Wait for the final decision in one of two ways:
- **Webhook (recommended):** Subscribe to `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` (and `CUSTOMER.KYB_APPROVED` / `CUSTOMER.KYB_REJECTED` for business customers) to be notified when the customer reaches a terminal status. `CUSTOMER.KYC_PENDING` (and the `KYB_PENDING` sibling) also fires when the customer is submitted for review — subscribe to it as well if you want to surface an "under review" state to the customer.
- - **Polling:** Call `GET /customers/{customerId}` and inspect `kycStatus`.
+ - **Polling:** Call `GET /customers/{customerId}` and inspect `kycStatus` (individuals) or `kybStatus` (businesses).
+
+ `GET /verifications?customerId=...` gives the finer-grained `verificationStatus` and `errors` array. That detail drives direct API onboarding; in the hosted flow the customer resolves it inside the flow, so integrate against `kycStatus` / `kybStatus`.
- On `APPROVED`, the customer is ready to transact — proceed with account setup and unlock funding. On `REJECTED`, surface the appropriate next step (for example, regenerate the link or request manual review).
+ On `APPROVED`, the customer is ready to transact — proceed with account setup and unlock funding. On `REJECTED`, surface the appropriate next step (for example, regenerate the link or request manual review). While the status is `PENDING`, let the customer finish account setup but block funding and money movement.
+
+Testing the business flow in sandbox: a `businessInfo.registrationNumber` **not** ending in `001`, `002`, or `003` auto-approves the customer on the spot, leaving the hosted flow nothing to verify — so it asks for no documents and no beneficial owners. Use a `003` suffix to exercise document and UBO collection. See [Sandbox testing](/api-reference/sandbox-testing).
+
+
### Direct API Onboarding
Prefer to collect identity information in your own UI and submit it to Grid yourself? Use the API directly instead of redirecting to a hosted link. The customer's `kycStatus` transitions the same way and you receive the same `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYC_PENDING` (and `CUSTOMER.KYB_*` equivalents) webhooks.
diff --git a/mintlify/snippets/sandbox-verification.mdx b/mintlify/snippets/sandbox-verification.mdx
index 4e1827fe..17bad60d 100644
--- a/mintlify/snippets/sandbox-verification.mdx
+++ b/mintlify/snippets/sandbox-verification.mdx
@@ -1,3 +1,5 @@
+import KybSandboxSuffixes from '/snippets/kyc/kyb-sandbox-suffixes.mdx'
+
In sandbox, you can trigger specific KYC/KYB verification outcomes using magic suffixes in customer and beneficial owner fields. These let you test different verification flows without waiting for real review.
### Individual customer verification (KYC)
@@ -104,15 +106,9 @@ Fix-and-resubmit example — the standard integration loop:
### Business customer verification (KYB)
-Business customers are always created with `kybStatus: UNVERIFIED` — their suffix applies when you call `POST /verifications`, not at creation. The **last 3 characters** of the `registrationNumber` in `businessInfo` determine the outcome:
-
-| Suffix | Outcome on `POST /verifications` |
-|--------|----------------------------------|
-| **002** | `kybStatus: REJECTED`, `verificationStatus: REJECTED` — immediate, skips data and document validation |
-| **001** / **003** | Normal validation applies: complete business information, business documents, and at least one beneficial owner are required (`RESOLVE_ERRORS` otherwise); a complete submission stays `PENDING` |
-| **Any other** | `kybStatus: APPROVED`, `verificationStatus: APPROVED` — immediate, skips data and document validation |
+
-Once a business customer is approved or rejected, further `POST /verifications` calls return `400`.
+For direct API onboarding, `001` and `003` both require complete business information, business documents, and at least one beneficial owner before `POST /verifications` moves past `RESOLVE_ERRORS`. For the hosted flow, see [Configuring customers](/payouts-and-b2b/onboarding/configuring-customers#hosted-kyc-and-kyb-link-flow).
### Beneficial owner KYC
diff --git a/openapi.yaml b/openapi.yaml
index 0fef08e3..7352105c 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -868,11 +868,15 @@ paths:
schema:
type: string
post:
- summary: Generate a hosted KYC link for an existing customer
+ summary: Generate a hosted KYC/KYB link for an existing customer
description: |
Generate a single-use hosted URL the customer can complete to verify their identity, and (where supported) a provider-specific `token` for embedding the verification flow directly via the provider's SDK.
- The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's `kycStatus`; the customer remains `PENDING` until they complete (or fail) the hosted flow.
+ The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's verification status; the customer remains at their current status until they complete (or fail) the hosted flow.
+
+ This endpoint generates the link for both customer types; `customerType` selects which flow the provider runs. `INDIVIDUAL` runs identity verification (KYC), tracked on `kycStatus`. `BUSINESS` runs business verification (KYB), tracked on `kybStatus` — the flow confirms the company details, collects formation, ownership, and proof-of-address documents, and gathers the control person and every beneficial owner holding 25% or more. Business information already supplied via `POST /customers` or `PATCH /customers/{customerId}` is prefilled, so send what you have before generating the link.
+
+ The hosted link is one of two ways to verify a customer. To collect the data yourself instead, submit it through `POST /customers`, `POST /beneficial-owners` (business customers), and `POST /documents`, then call `POST /verifications`. Both paths produce the same status transitions and the same `CUSTOMER.KYC_*` / `CUSTOMER.KYB_*` webhooks.
Each call returns a fresh link. Previously-issued links are not invalidated, but they remain single-use and will expire on their own. For request-level retry safety, include an `Idempotency-Key` header.
operationId: createCustomerKycLink
diff --git a/openapi/paths/customers/customers_{customerId}_kyc-link.yaml b/openapi/paths/customers/customers_{customerId}_kyc-link.yaml
index e077e5cc..e9fd954a 100644
--- a/openapi/paths/customers/customers_{customerId}_kyc-link.yaml
+++ b/openapi/paths/customers/customers_{customerId}_kyc-link.yaml
@@ -6,11 +6,15 @@ parameters:
schema:
type: string
post:
- summary: Generate a hosted KYC link for an existing customer
+ summary: Generate a hosted KYC/KYB link for an existing customer
description: |
Generate a single-use hosted URL the customer can complete to verify their identity, and (where supported) a provider-specific `token` for embedding the verification flow directly via the provider's SDK.
- The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's `kycStatus`; the customer remains `PENDING` until they complete (or fail) the hosted flow.
+ The customer must already exist — create them with `POST /customers` first. Calling this endpoint does not change the customer's verification status; the customer remains at their current status until they complete (or fail) the hosted flow.
+
+ This endpoint generates the link for both customer types; `customerType` selects which flow the provider runs. `INDIVIDUAL` runs identity verification (KYC), tracked on `kycStatus`. `BUSINESS` runs business verification (KYB), tracked on `kybStatus` — the flow confirms the company details, collects formation, ownership, and proof-of-address documents, and gathers the control person and every beneficial owner holding 25% or more. Business information already supplied via `POST /customers` or `PATCH /customers/{customerId}` is prefilled, so send what you have before generating the link.
+
+ The hosted link is one of two ways to verify a customer. To collect the data yourself instead, submit it through `POST /customers`, `POST /beneficial-owners` (business customers), and `POST /documents`, then call `POST /verifications`. Both paths produce the same status transitions and the same `CUSTOMER.KYC_*` / `CUSTOMER.KYB_*` webhooks.
Each call returns a fresh link. Previously-issued links are not invalidated, but they remain single-use and will expire on their own. For request-level retry safety, include an `Idempotency-Key` header.
operationId: createCustomerKycLink