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