diff --git a/mintlify/api-reference/kyc-kyb-verification.mdx b/mintlify/api-reference/kyc-kyb-verification.mdx
new file mode 100644
index 000000000..5e2f9ba46
--- /dev/null
+++ b/mintlify/api-reference/kyc-kyb-verification.mdx
@@ -0,0 +1,10 @@
+---
+title: "KYC & KYB verification"
+icon: "/images/icons/suitcase-work.svg"
+description: "Verify individual and business customers through a hosted link or directly through the API — options, required fields, status transitions, and webhooks"
+"og:image": "/images/og/og-api-reference.png"
+---
+
+import VerificationOptions from '/snippets/kyc/verification-options.mdx';
+
+
diff --git a/mintlify/docs.json b/mintlify/docs.json
index 41d5b8ec8..f69b161de 100644
--- a/mintlify/docs.json
+++ b/mintlify/docs.json
@@ -394,6 +394,7 @@
"api-reference/terminology",
"api-reference/authentication",
"api-reference/webhooks",
+ "api-reference/kyc-kyb-verification",
"api-reference/sandbox-testing",
"api-reference/sdks"
]
diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml
index 0fef08e37..7352105c1 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 000000000..8d6a516d2
--- /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/verification-options.mdx b/mintlify/snippets/kyc/verification-options.mdx
new file mode 100644
index 000000000..52ab9724f
--- /dev/null
+++ b/mintlify/snippets/kyc/verification-options.mdx
@@ -0,0 +1,127 @@
+import KybDataRequirements from '/snippets/kyc/kyb-data-requirements.mdx'
+import KybSandboxSuffixes from '/snippets/kyc/kyb-sandbox-suffixes.mdx'
+
+Grid verifies individual customers with KYC and business customers with KYB. Which applies is decided by `customerType`, and the result lands on `kycStatus` for individuals and `kybStatus` for businesses.
+
+**Regulated platforms** run verification through their own compliance systems and create customers directly with `POST /customers`. **Unregulated platforms** have Grid verify, using either of the two paths below.
+
+## Your options
+
+Both paths cover KYC and KYB, produce the same status transitions, and emit the same webhooks. You can mix them — supply what you already hold through the API, then let the hosted flow collect the rest.
+
+| | Hosted link | Direct API |
+|---|---|---|
+| Who collects the data | Grid's hosted flow, or the provider SDK embedded in your UI | You, in your own UI |
+| Identity documents | Uploaded by the customer in the flow | `POST /documents` |
+| Beneficial owners (KYB) | Declared by the applicant in the flow | `POST /beneficial-owners` |
+| Submission | Automatic when the customer finishes | `POST /verifications` |
+| Resolving missing data | The customer, inside the flow | You, from the returned `errors` array |
+| Best when | You want Grid to own the collection UX | You already collect this data, or need it in your own UI |
+
+## Hosted link
+
+Create the customer, then call `POST /customers/{customerId}/kyc-link`. There's no dedicated KYB link endpoint — this one serves both types, and `customerType` selects which flow the provider runs.
+
+```bash
+curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:019542f5-b3e7-1d02-0000-000000000001/kyc-link" \
+ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
+ -H "Content-Type: application/json" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -d '{ "redirectUri": "https://yourapp.com/onboarding-complete" }'
+```
+
+```json
+{
+ "kycUrl": "https://kyc.lightspark.com/onboard/abc123def456",
+ "expiresAt": "2027-01-15T14:32:00Z",
+ "provider": "SUMSUB",
+ "token": "_act-sbx-jwt-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+}
+```
+
+Send the customer to `kycUrl`, or pass `token` to the provider's SDK to embed the flow in your own UI. The field is named `kycUrl` for both customer types; for a `BUSINESS` customer it opens the KYB flow, where the applicant confirms the company details, uploads the formation and ownership documents, and declares the control person and every beneficial owner holding 25% or more.
+
+
+
+
+
+- Links are single-use and expire at `expiresAt`. Each call mints a fresh one; earlier links aren't invalidated. `redirectUri` is optional and must be `https://`.
+- Generating a link doesn't change the customer's status — that happens when they submit.
+- A `409` means contact verification is incomplete. When the customer carries a `contactVerification` object, every channel it lists must reach `VERIFIED` first, via `POST /customers/{customerId}/verify-email` / `verify-phone` and their `/confirm` sub-routes. When the object is absent, nothing is required.
+- Business information you supply via `POST /customers` and `PATCH /customers/{customerId}` is prefilled into the flow, so send as much as you have **before** generating the link.
+
+
+Reaching your `redirectUri` means the customer finished the flow, not that they were approved. Wait for the decision.
+
+
+## Direct API
+
+Submit the data yourself: `POST /customers`, then `POST /beneficial-owners` for business customers, `POST /documents` for identity and company documents, and finally `POST /verifications`. If anything is missing, `verificationStatus` comes back as `RESOLVE_ERRORS` with one `errors` entry per problem — fix them and resubmit.
+
+For the full walkthrough, see [Configuring customers](/payouts-and-b2b/onboarding/configuring-customers).
+
+## Creating a business customer
+
+Either path starts here. `POST /customers` with `customerType: BUSINESS` requires:
+
+| 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` |
+
+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** — `GET /config` returns `supportedCurrencies`, each with a `providerRequiredCustomerFields` list. Anything listed for a currency the business will use must be supplied.
+- **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 hosted flow.
+
+Individual customers need `customerType: INDIVIDUAL`; `fullName` must contain both a given and a family name.
+
+## Status transitions
+
+`kycStatus` and `kybStatus` share the same values and the same path: `UNVERIFIED` → `PENDING` → `APPROVED` / `REJECTED`.
+
+| Status | Meaning |
+|--------|---------|
+| `UNVERIFIED` | Created, not yet submitted. Generating a link does not move it off this value |
+| `PENDING` | Submitted; review under way |
+| `APPROVED` | Passed — unlock funding and money movement |
+| `REJECTED` | Failed |
+| `HOLD` | On hold; the customer may be asked to supply more information |
+
+While the status is `PENDING`, let the customer finish account setup but block funding and money movement.
+
+`GET /verifications?customerId=...` gives the finer-grained `verificationStatus` (`RESOLVE_ERRORS`, `IN_PROGRESS`, `PENDING_MANUAL_REVIEW`, `APPROVED`, `REJECTED`, `READY_FOR_VERIFICATION`) and the `errors` array. That detail drives the direct API path; in the hosted flow the customer resolves it inside the flow, so integrate against `kycStatus` / `kybStatus`.
+
+## Webhooks
+
+| Event | Fires when |
+|-------|-----------|
+| `CUSTOMER.KYC_PENDING` / `CUSTOMER.KYB_PENDING` | Submitted for review — use it to show an "under review" state |
+| `CUSTOMER.KYC_APPROVED` / `CUSTOMER.KYB_APPROVED` | Terminal: passed |
+| `CUSTOMER.KYC_REJECTED` / `CUSTOMER.KYB_REJECTED` | Terminal: failed |
+
+The `KYB_*` events fire only for `customerType: BUSINESS`. `data` is the full customer resource, identical to `GET /customers/{customerId}`:
+
+```json
+{
+ "id": "Webhook:019542f5-b3e7-1d02-0000-000000000007",
+ "type": "CUSTOMER.KYB_APPROVED",
+ "timestamp": "2025-08-15T14:32:00Z",
+ "data": {
+ "id": "Customer:019542f5-b3e7-1d02-0000-000000000001",
+ "customerType": "BUSINESS",
+ "kybStatus": "APPROVED"
+ }
+}
+```
+
+Verify `X-Grid-Signature` against the raw request body and deduplicate on the webhook `id`. The `VERIFICATION.*` events carry the verification-level detail if you want it. To poll instead, read the status off `GET /customers/{customerId}`.
+
+## Sandbox
+
+
+
+For individual customers and beneficial owners, the equivalent suffixes live on `fullName` and on each owner's last name — see [Sandbox testing](/api-reference/sandbox-testing).
diff --git a/mintlify/snippets/sandbox-verification.mdx b/mintlify/snippets/sandbox-verification.mdx
index 4e1827fed..c13d1cd9d 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 [KYC & KYB verification](/api-reference/kyc-kyb-verification).
### Beneficial owner KYC
diff --git a/openapi.yaml b/openapi.yaml
index 0fef08e37..7352105c1 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 e077e5ccd..e9fd954ad 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