Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions mintlify/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions mintlify/snippets/kyc/kyb-sandbox-suffixes.mdx
Original file line number Diff line number Diff line change
@@ -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 |

<Warning>
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.
</Warning>

Once a business customer is approved or rejected, further `POST /verifications` calls return `400`.
113 changes: 84 additions & 29 deletions mintlify/snippets/kyc/kyc-unregulated.mdx
Original file line number Diff line number Diff line change
@@ -1,41 +1,90 @@
import KybDataRequirements from '/snippets/kyc/kyb-data-requirements.mdx'

<Note>
**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.
</Note>

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.
<Tabs>
<Tab title="Individual (KYC)">
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"
}'
```
</Tab>

<Tab title="Business (KYB)">
`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.
</Tab>
</Tabs>

Persist the returned `id` (the Grid customer ID) — you'll need it for the next step.

#### 2. Generate a KYC link
<Tip>
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.
</Tip>

#### 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" \
Expand All @@ -59,18 +108,18 @@ curl -X POST "https://api.lightspark.com/grid/2025-10-13/customers/Customer:0195
```

<Tip>
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.
</Tip>

#### Complete KYC Process
#### The flow end to end

<Steps>
<Step title="Create the customer">
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.)
</Step>

<Step title="Verify contact channels (only where required)">
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):

Expand All @@ -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.)

<Note>
Generating the KYC link before every present channel is `VERIFIED` returns `409`.
Generating the link before every present channel is `VERIFIED` returns `409`.
</Note>
</Step>

<Step title="Generate the KYC link">
<Step title="Generate the link">
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.

<Note>
Expand All @@ -93,7 +142,7 @@ The response always includes `kycUrl` for the hosted flow. For providers that su
</Step>

<Step title="Send the customer through verification">
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.

<Warning>
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.
Expand All @@ -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`.
</Step>

<Step title="Handle completion">
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.
</Step>
</Steps>

<Warning>
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).
</Warning>

### 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.
Expand Down
12 changes: 4 additions & 8 deletions mintlify/snippets/sandbox-verification.mdx
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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 |
<KybSandboxSuffixes />

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

Expand Down
Loading
Loading