> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC Overview

> What Etherfuse collects to verify a customer, how to track where their verification stands, and the extra requirements that apply in specific countries.

Every customer must clear identity verification before they can transact.

## Choosing a flow

| Flow | You submit | Customer submits |
| - | - | - |
| [**KYC WebSDK**](/v2/guides/kyc-websdk) (the default) | Nothing | Everything |
| [**KYC API**](/v2/guides/kyc-api) | Personal data, identity document, questionnaire answers, and any [region-specific documents](#country-specific-requirements) | Liveness selfie, and any questionnaire you do not answer over the API |
| [**Sumsub shared token**](/v2/guides/kyc-sumsub-shared-token) | A share token, then questionnaire answers and any [region-specific documents](#country-specific-requirements) | Any questionnaire you do not answer over the API, plus liveness if the shared session did not cover it |

## Requirements List

| Requirement | What satisfies it | How it can be submitted |
| - | - | - |
| `personalData` | Name, date of birth, tax id, country, and residential address. | WebSDK · API · Shared token |
| `identityDocument` | A government-issued photo ID. Any valid one works. | WebSDK · API · Shared token |
| `proofOfAddress` | A utility bill, bank statement, tax bill, employment certificate, property deed, or invoice, issued within the last three months. Requested only when the identity document carried no address. | WebSDK · API · Shared token |
| `occupation` | Job title and an occupation category. | WebSDK · API |
| `selfie` | A live face capture. | WebSDK · Shared token |
| `customerAgreement` | The customer accepting Etherfuse's agreements. | WebSDK · API |

Some countries add specific additional requirements. See [Country-specific requirements](#country-specific-requirements).

Supply `userInfo.email` when [creating the customer](/v2/guides/kyc-websdk#the-flow). Read the overall status with [`GET /organization/{organizationId}/kyc`](/v2/api-reference/kyc/get-kyc-status).

## Requirements unlock in stages

A customer does not owe every requirement at once. Verification runs in stages, and each stage asks for its own requirements. Today a customer moves through these stages in this order:

1. **Routing.** The country in their personal data decides which requirements apply. Nothing else can be sent until this lands.
2. **Identity.** `personalData`, `identityDocument`, `selfie`, `occupation`, and any [country-specific](#country-specific-requirements) requirement.
3. **Proof of address.** Only when the identity document carried no address.
4. **Agreement.** `customerAgreement`.

Drive your integration from the [requirements breakdown](#the-per-requirement-breakdown), not from this list. We add and reorder stages, and some countries may gain stages of their own.

The rules:

* **The breakdown lists only the current stage, plus requirements already satisfied.** A requirement from a later stage is not in the list at all until the customer reaches that stage.
* **Send a requirement only once it is in the list as `pending` or `actionRequired`.** If you send it earlier, the request is accepted with a `202`, then refused in the background. A [`kycUpdated`](/v2/api-reference/webhooks/kycupdated) webhook arrives with `needsWork: true`, and nothing retries it.
* **A stage moves on by itself.** When everything the current stage asks for has landed, it is submitted for review and the overall `status` becomes `submitted`. When the review clears, the next stage's requirements appear in the list and `status` returns to `inProgress`. Both moves send `kycUpdated`. Review is usually a few seconds; a manual review takes longer.
* **Proof of address is the one exception.** You may send it before its stage, as long as the identity stage has not been submitted yet. Send it in the same request as the identity document. It is used only if the proof of address stage is reached.

So the loop for anything you submit over the API is:

```mermaid theme={null}
flowchart TD
    R["Read the breakdown<br/>GET .../kyc?requirements=true"] --> P{"Anything pending or<br/>actionRequired with<br/>requiresLaunch false?"}
    P -- "yes" --> S["Send those requirements"]
    S --> W["Wait for kycUpdated:<br/>stage submitted, then<br/>the next stage arrives"]
    W --> R
    P -- "no" --> L{"Anything left with<br/>requiresLaunch true?"}
    L -- "yes" --> LA["Launch the customer"]
    L -- "no" --> A["Wait for status approved"]
```

<Steps>
  <Step title="Read the breakdown">
    [`GET /organization/{organizationId}/kyc?requirements=true`](/v2/api-reference/kyc/get-kyc-status)
  </Step>

  <Step title="Send what is pending">
    Everything in the list with `status: "pending"` or `"actionRequired"` and `requiresLaunch: false`.
  </Step>

  <Step title="Wait for the next kycUpdated">
    It arrives when the stage is submitted, and again when the next stage arrives. Without a `kycUpdated` subscription, read the breakdown again after a few seconds.
  </Step>

  <Step title="Repeat until only launch rows are left">
    When everything outstanding reads `requiresLaunch: true`, [launch the customer](/v2/guides/user-launch-flows#identity-verification). When nothing is outstanding, wait for `status: "approved"`.
  </Step>
</Steps>

## Tracking verification status

### The overall status

<CodeGroup>
  ```bash Sandbox theme={null}
  curl -H "Authorization: <api_key>" \
    https://api.sand.etherfuse.com/organization/<organization_uuid>/kyc
  ```

  ```bash Production theme={null}
  curl -H "Authorization: <api_key>" \
    https://api.etherfuse.com/organization/<organization_uuid>/kyc
  ```
</CodeGroup>

```json theme={null}
{
  "organizationId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "submitted",
  "currentRejectionReason": null,
  "selfies": [],
  "documents": [],
  "approvedAt": null,
  "needsWork": false,
  "userMessage": null
}
```

| Status | Description |
| - | - |
| `notStarted` | No verification started |
| `inProgress` | Started, but not everything is submitted. Detail in `needsWork` and `userMessage` |
| `submitted` | Submitted, awaiting review (manual or automated) |
| `approved` | Cleared; the customer can transact |
| `denied` | A rejected verdict, or a previously-approved customer whose approval was revoked. Reason in `currentRejectionReason` |

### The per-requirement breakdown

Add **`?requirements=true`** for a step-by-step account of what the customer still needs. It is opt-in: without it the response leaves `requirements` out.

```bash theme={null}
curl "https://api.sand.etherfuse.com/organization/<organization_uuid>/kyc?requirements=true" \
  -H "Authorization: <api_key>"
```

```json theme={null}
{
  "organizationId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "inProgress",
  "currentRejectionReason": null,
  "selfies": [],
  "documents": [],
  "approvedAt": null,
  "needsWork": false,
  "userMessage": null,
  "requirements": [
    { "type": "personalData",      "status": "satisfied", "requiresLaunch": false },
    { "type": "identityDocument",  "status": "satisfied", "requiresLaunch": false },
    { "type": "occupation",        "status": "pending",   "requiresLaunch": false },
    { "type": "selfie",            "status": "pending",   "requiresLaunch": true  }
  ]
}
```

This customer is on the identity stage. `customerAgreement` is not in the list yet, so it cannot be sent yet. Once the identity stage clears, the same call returns:

```json theme={null}
{
  "organizationId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "inProgress",
  "currentRejectionReason": null,
  "selfies": [],
  "documents": [],
  "approvedAt": null,
  "needsWork": false,
  "userMessage": null,
  "requirements": [
    { "type": "personalData",      "status": "satisfied", "requiresLaunch": false },
    { "type": "identityDocument",  "status": "satisfied", "requiresLaunch": false },
    { "type": "occupation",        "status": "satisfied", "requiresLaunch": false },
    { "type": "selfie",            "status": "satisfied", "requiresLaunch": true  },
    { "type": "customerAgreement", "status": "pending",   "requiresLaunch": false }
  ]
}
```

Each `type` matches a requirement in the [Requirements List](#requirements-list) or a [country-specific](#country-specific-requirements) one. `requiresLaunch: true` marks the rows only the WebSDK can satisfy.

<Warning>
  **`requirements` is absent until the customer has been routed to their country's requirements**, so it is missing immediately after their personal data is submitted. Read it again a moment later. After that it **grows** as the customer moves through the [stages](#requirements-unlock-in-stages): `proofOfAddress` and `customerAgreement` appear only when their stage is reached. Re-read it after every `kycUpdated`; no single response is the full list.
</Warning>

<Note>
  We reserve the right to add new requirements, and will update these docs when we do. Some countries may also gain requirements of their own.
</Note>

### Requirement statuses

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: the current stage asks for it
    pending --> awaitingReview: you provide it
    awaitingReview --> satisfied: accepted
    awaitingReview --> actionRequired: rejected
    actionRequired --> awaitingReview: you send it again, corrected
    satisfied --> [*]
```

| `status` | Meaning | What to do |
| - | - | - |
| `satisfied` | Accepted. | Nothing. |
| `pending` | The current stage asks for this, and nothing has been provided yet. | Supply it. |
| `awaitingReview` | Provided, not yet reviewed. | Wait. |
| `actionRequired` | **Reviewed and rejected.** | Send it again, corrected. |

## Country-specific requirements

<AccordionGroup>
  <Accordion title="🇲🇽 Mexico">
    | Requirement | What satisfies it | How it can be submitted |
    | - | - | - |
    | `taxDocument` | The **Constancia de Situación Fiscal**, the tax-status document issued by SAT. | WebSDK · API |

    It must be legible and belong to the customer being verified. Submit it as the `tax_document` part of [`POST /organization/{organizationId}/verification/documents`](/v2/api-reference/kyc/upload-verification-documents), or let the customer upload it in the widget.

    <Info>
      **In sandbox, the constancia is the one document that has to look real.** Any real constancia with any data in it works. Use [this example constancia](https://stablebonds.s3.us-west-2.amazonaws.com/example-constancia-de-situacion-fiscal.pdf).

      **In sandbox, the customer's name must be `LETICIA HERNANDEZ GONZALEZ`.** The RFC registry check still runs, and the sandbox registry returns only this test person. Any other name is rejected.
    </Info>
  </Accordion>
</AccordionGroup>

## Reference

<Expandable title="Payment accounts">
  After verification is approved, register bank accounts and wallets as payment accounts with [`POST /organization/{organizationId}/payment_account`](/v2/api-reference/payment-accounts/register-payment-account). Payment accounts belong to the organization in the route. Use your own organization's id from [`GET /me`](/v2/api-reference/organizations/get-me), or a customer organization's id from [`POST /organization`](/v2/api-reference/organizations/create-organization) or [`GET /organizations`](/v2/api-reference/organizations/list-organizations). See [Organizations in the route](/v2/overview#organizations-in-the-route). A bank account route needs account holder details under `registrationInfo`, such as `accountHolderName` and `taxId`. We fill each field from that organization's verified identity when we hold it, so you do not send KYC data again. To register a customer's bank account, pass the customer's organization id.

  To learn which fields you still need before you register, call [`GET /organization/{organizationId}/payment_account/requirements`](/v2/api-reference/payment-accounts/get-registration-requirements) with the same organization id, `kind`, `country`, `currency` and `accountDetails.kind` you plan to register:

  ```bash theme={null}
  curl -H "Authorization: <api_key>" \
    "https://api.sand.etherfuse.com/organization/$ORGANIZATION_ID/payment_account/requirements?kind=bank&country=MX&currency=MXN&accountDetailsKind=clabe"
  ```

  ```json theme={null}
  {
    "accountDetails": {
      "kind": "clabe",
      "required": ["#/accountDetails/clabe"]
    },
    "registrationInfo": {
      "required": ["#/registrationInfo/accountHolderName", "#/registrationInfo/taxId"],
      "missing": ["#/registrationInfo/taxId"]
    }
  }
  ```

  `required` lists every field the route needs. `missing` lists the ones we hold no verified value for. Ask the customer only for the `missing` fields and send them under `registrationInfo`. If you already collect more fields, you can send them all; we ignore the ones this route does not use.

  If you register without a field we need, we return `422` `registration-info-required` with the same `required` and `missing` lists. Add the missing fields and send the request again. A `taxId` you send must match the verified one, or we return `422` `holder-tax-id-mismatch`.

  A new account reports its readiness per rail and direction in `rails`. You can quote a direction once its `status` is `ready`. Read the account with [`GET /organization/{organizationId}/payment_account/{id}`](/v2/api-reference/payment-accounts/get-payment-account), or subscribe to [`paymentAccountUpdated`](/v2/api-reference/webhook-events/paymentaccountupdated) to receive the account when it changes. See [Overview](/v2/overview) for the full flow.
</Expandable>

<Expandable title="Webhooks">
  Subscribe to `kycUpdated` events with [`POST /webhook_subscription`](/v2/api-reference/webhook-subscriptions/create-webhook-subscription) to receive status updates. See [Webhooks](/v2/webhooks) for setup and signature verification, and [`kycUpdated`](/v2/api-reference/webhooks/kycupdated) for the payload.

  **Approved:**

  ```json theme={null}
  {
    "event": "kycUpdated",
    "eventId": "0d6f1a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
    "occurredAt": "2026-09-29T19:00:00Z",
    "organizationId": "123e4567-e89b-12d3-a456-426614174000",
    "kyc": { "status": "approved", "approved": true, "needsWork": false, "userMessage": null }
  }
  ```

  **Needs work:**

  ```json theme={null}
  {
    "event": "kycUpdated",
    "eventId": "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b",
    "occurredAt": "2026-09-29T19:05:00Z",
    "organizationId": "123e4567-e89b-12d3-a456-426614174000",
    "kyc": {
      "status": "inProgress",
      "approved": false,
      "needsWork": true,
      "userMessage": "Please upload a current government-issued ID."
    }
  }
  ```

  Business customers get `kybUpdated` instead, with `kyb: { status, approvedAt }`. See [`kybUpdated`](/v2/api-reference/webhook-events/kybupdated).
</Expandable>

<Expandable title="Data isolation">
  Partners can only access KYC data they submitted:

  | Accessor | Data Visibility |
  | - | - |
  | Partner (via API key) | Only data where `source_organization_id` matches their org |
  | Wallet Owner | All data for their wallet |
  | Admin | All data |

  This prevents partners from accessing PII submitted by other partners or directly by users.
</Expandable>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.