> ## 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 API

> Collect your customer's identity data and documents in your own UI, push them over the API, and launch the customer only for what is left.

Push identity data and documents you already hold over the API, so the customer isn't asked for them twice. They are still launched into [`/idv`](/guides/user-launch-flows#identity-verification), but arrive with most of the work done.

Three requirements always remain the customer's: **confirming their email**, the **liveness selfie**, and **accepting the customer agreement**. There is no API for those. For a flow where the customer only signs the agreement, see [Sumsub shared token](/guides/kyc-sumsub-shared-token).

## The order matters

Personal data comes first. A customer's requirements are assigned from the country you send, and that assignment is asynchronous, so documents and answers sent before it lands are refused with a `409`. The [`kyc_updated`](/guides/verifying-webhooks) webhook fires when it lands; send once [step 2](#2-read-what-is-being-asked-for) returns a `requirements` list.

<Steps>
  <Step title="Submit personal data">
    [`POST /ramp/customer/{customer_id}/verification`](/api-reference/kyc/submit-verification-data)
  </Step>

  <Step title="Read what is being asked for">
    [`GET /ramp/customer/{customer_id}/kyc?requirements=true`](/api-reference/kyc/get-kyc-status)
  </Step>

  <Step title="Upload the documents being asked for">
    [`POST /ramp/customer/{customer_id}/verification/documents`](/api-reference/kyc/upload-verification-documents)
  </Step>

  <Step title="Submit the questionnaire answers">
    [`POST /ramp/customer/{customer_id}/verification/questionnaire`](/api-reference/kyc/submit-a-verification-questionnaire)
  </Step>

  <Step title="Launch the customer for the rest">
    Email confirmation, the liveness selfie, and the customer agreement.
  </Step>
</Steps>

<Warning>
  **These endpoints answer `202 Accepted`, not `200 OK`.** A `202` means "accepted and being applied", not "done". Never treat it as proof that a requirement is satisfied: learn that from the [`kyc_updated`](/guides/verifying-webhooks) webhook or the [requirements breakdown](/guides/onboarding#the-per-requirement-breakdown).
</Warning>

## 1. Submit personal data

<CodeGroup>
  ```bash Sandbox theme={null}
  curl -X POST https://api.sand.etherfuse.com/ramp/customer/<customer_uuid>/verification \
    -H "Authorization: <api_key>" \
    -H "Content-Type: application/json" \
    -d '{
      "firstName": "Ana",
      "lastName": "Ejemplo",
      "dateOfBirth": "1990-01-01",
      "taxId": "XAXX010101000",
      "country": "MEX",
      "address": {
        "street": "Av. Reforma 100",
        "city": "Ciudad de Mexico",
        "region": "CDMX",
        "postalCode": "06600",
        "country": "MEX"
      }
    }'
  ```

  ```bash Production theme={null}
  curl -X POST https://api.etherfuse.com/ramp/customer/<customer_uuid>/verification \
    -H "Authorization: <api_key>" \
    -H "Content-Type: application/json" \
    -d '{
      "firstName": "Ana",
      "lastName": "Ejemplo",
      "dateOfBirth": "1990-01-01",
      "taxId": "XAXX010101000",
      "country": "MEX",
      "address": {
        "street": "Av. Reforma 100",
        "city": "Ciudad de Mexico",
        "region": "CDMX",
        "postalCode": "06600",
        "country": "MEX"
      }
    }'
  ```
</CodeGroup>

```json 202 Accepted theme={null}
{
  "customerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "in_progress"
}
```

Every field, its format, and whether it is required: [`POST /ramp/customer/{customer_id}/verification`](/api-reference/kyc/submit-verification-data).

<Warning>
  **Send only an address that matches the identity document you upload.** The two are cross-checked. An address, date of birth, or name that contradicts the document holds the customer up for review instead of approving them.
</Warning>

Re-submitting overwrites what you sent before and is safe. It never moves the customer's review status on its own.

## 2. Read what is being asked for

Triggered by the [`kyc_updated`](/guides/verifying-webhooks) webhook rather than a timer, where you can: it fires when the customer is routed and the requirement list becomes available.

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

```json theme={null}
{
  "customerId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "in_progress",
  "requirements": [
    { "type": "personal_data",      "status": "satisfied", "requiresLaunch": false },
    { "type": "identity_document",  "status": "pending",   "requiresLaunch": false },
    { "type": "occupation",         "status": "pending",   "requiresLaunch": false },
    { "type": "tax_document",       "status": "pending",   "requiresLaunch": false },
    { "type": "email_confirmation", "status": "pending",   "requiresLaunch": true  },
    { "type": "selfie",             "status": "pending",   "requiresLaunch": true  },
    { "type": "customer_agreement", "status": "pending",   "requiresLaunch": true  }
  ]
}
```

`requiresLaunch: false` is yours to send; `true` can only be done by the customer. When everything outstanding reads `true`, you have nothing left to send. Full vocabulary: [Tracking verification status](/guides/onboarding#tracking-verification-status).

## 3. Upload documents

`multipart/form-data`, with files sent as files. Nothing is base64-wrapped into JSON.

<CodeGroup>
  ```bash Sandbox theme={null}
  curl -X POST https://api.sand.etherfuse.com/ramp/customer/<customer_uuid>/verification/documents \
    -H "Authorization: <api_key>" \
    -F id_type=ID_CARD \
    -F id_front=@ine-front.jpg \
    -F id_back=@ine-back.jpg
  ```

  ```bash Production theme={null}
  curl -X POST https://api.etherfuse.com/ramp/customer/<customer_uuid>/verification/documents \
    -H "Authorization: <api_key>" \
    -F id_type=ID_CARD \
    -F id_front=@ine-front.jpg \
    -F id_back=@ine-back.jpg
  ```
</CodeGroup>

Send `tax_document` here too, where the customer's country requires one. See [Country-specific requirements](/guides/onboarding#country-specific-requirements).

Every part, the accepted document types, and the file limits: [`POST /ramp/customer/{customer_id}/verification/documents`](/api-reference/kyc/upload-verification-documents).

## 4. Submit the questionnaire answers

Questions only. Documents, including a region's tax document, go through [step 3](#3-upload-documents).

Each submission names the questionnaire it answers in `type`:

| `type`       | Answers                | Applies to     |
| ------------ | ---------------------- | -------------- |
| `occupation` | `jobTitle`, `industry` | Every customer |

<CodeGroup>
  ```bash Sandbox theme={null}
  curl -X POST https://api.sand.etherfuse.com/ramp/customer/<customer_uuid>/verification/questionnaire \
    -H "Authorization: <api_key>" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "occupation",
      "jobTitle": "Software Engineer",
      "industry": "4430200"
    }'
  ```

  ```bash Production theme={null}
  curl -X POST https://api.etherfuse.com/ramp/customer/<customer_uuid>/verification/questionnaire \
    -H "Authorization: <api_key>" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "occupation",
      "jobTitle": "Software Engineer",
      "industry": "4430200"
    }'
  ```
</CodeGroup>

<Note>
  **Send `type` even though there is one value.** More questionnaires may be added, and a request naming one that doesn't exist is rejected listing the ones that do, rather than being applied to the wrong questionnaire. The customer agreement is a questionnaire too and will never appear here: consent is collected from the customer in `/idv`.
</Note>

**`industry` is a code you have to look up.** [`GET /ramp/verification/occupations`](/api-reference/kyc/list-occupation-categories) returns every accepted value as `{ code, label }`. Submit the `code`: the `label` is display copy, can be reworded, and is not accepted. An unrecognized code is refused with a `400` naming it, so a typo fails at the request rather than leaving the requirement quietly outstanding.

```json theme={null}
{
  "occupations": [
    { "code": "1000000", "label": "NOT APPLICABLE" },
    { "code": "4430200", "label": "INFORMATION TECHNOLOGY - IT PROGRAMMING ENGINEERS OR TECHNICIANS" }
  ]
}
```

## 5. Launch the customer

Launch into [`/idv`](/guides/user-launch-flows#identity-verification) exactly as in the [hosted flow](/guides/kyc-websdk#the-flow). The customer is shown only what's left: email confirmation, the liveness selfie, and the customer agreement.

## Error handling

Each endpoint's full response codes are on its API reference page. Requirement and overall statuses are in [Tracking verification status](/guides/onboarding#tracking-verification-status). What follows is what actually bites in this flow.

| What you see                                                    | Meaning                                                                                                                                                                                  | What to do                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `409` `Submit this customer's personal data first`              | Step 1's `202` returns before the customer has a verification profile, so a document or questionnaire call in that window has no stage to land on.                                       | Wait for the requirements breakdown to appear, normally well under a second, then send.                                                                                                                                                                                                                                                                                                                                                     |
| `409` `has not been routed to their country's requirements yet` | Step 1 landed, but the stages that hold documents and questions are assigned from the country you sent, and that assignment is asynchronous.                                             | Wait for the `kyc_updated` webhook (`not_started` to `in_progress`), then re-read step 2 and send. With no webhook registered, read step 2 until the requirements list appears.                                                                                                                                                                                                                                                             |
| `202`, but the requirement stays `pending`                      | Steps 1, 3 and 4 validate your request, answer `202`, then apply it in the background, and that background work failed. The usual cause is a document sent before its stage was reached. | A `kyc_updated` webhook arrives with `needsWork: true` and a `userMessage` naming what to send again, and `status` unchanged. Nothing retries on our side, so send it again. For documents send every part in the same request: one side of a two-sided document is refused on its own. That webhook is the only place the message appears, so without one registered, re-read the requirements list and re-send what is still outstanding. |
| `202`, then the customer stalls in review                       | The personal data contradicts the document it was cross-checked against.                                                                                                                 | Re-submit personal data matching the document, or upload the document that matches.                                                                                                                                                                                                                                                                                                                                                         |
| `409` `already approved` / `already denied`                     | Concluded verifications can't be written to.                                                                                                                                             | A denied customer resubmits by being launched into `/idv`.                                                                                                                                                                                                                                                                                                                                                                                  |

## Sandbox

The verification checks don't run, so document content isn't scrutinised and registry checks are skipped: a placeholder image is accepted where production would reject it. Everything else is identical, including the submission order and the conditional proof-of-address stage. See [Test environment](/test-environment).
