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

# Identity verification (KYC)

> How tenant KYC and issuer KYC work, and how to integrate hosted or bring-your-own verification.

Before you can issue a card, every applicant must pass **two** checks. Apocor orchestrates both — you never integrate with the card program or identity vendors directly.

| Layer          | Who decides                    | API signal                |
| -------------- | ------------------------------ | ------------------------- |
| **Tenant KYC** | You (the integrator)           | `status` → `APPROVED`     |
| **Issuer KYC** | The card program behind Apocor | `issuerKycReady` → `true` |

<Warning>
  `status: APPROVED` alone is **not** enough to issue cards. You must also wait for `issuerKycReady: true`. Card issuance returns `ISSUER_KYC_INCOMPLETE` otherwise.
</Warning>

## What gets shared?

You never upload passports, selfies, or PDFs to Apocor.

Identity providers support a **share token** — a short-lived credential that lets the card program pull an already-verified profile. Apocor accepts that token and forwards it upstream. Raw documents stay inside the identity provider.

```mermaid theme={null}
sequenceDiagram
  participant You as Your app
  participant Apocor as Apocor API
  participant IdP as Identity provider
  participant Issuer as Card program

  You->>Apocor: Create applicant
  You->>IdP: Verify user (hosted or BYOK)
  IdP-->>You: Approved + share token
  You->>Apocor: kyc-share or auto via hosted flow
  Apocor->>Issuer: Register verified identity
  Apocor-->>You: issuerKycReady true
  You->>Apocor: Issue card
```

## Get your integration settings

```bash cURL theme={null}
curl -s 'https://sandbox.apocor.ai/v1/kyc/config' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

<ResponseExample>
  ```json theme={null}
  {
    "data": {
      "hostedVerificationAvailable": true,
      "shareVerificationAvailable": true,
      "shareRecipientId": "celeriz.com_139320",
      "sumsubWebhookMode": "apocor",
      "sumsubWebhookUrl": "https://sandbox.apocor.ai/v1/webhooks/sumsub",
      "verificationSync": "POST /v1/webhooks/sumsub on your Core API host, or POST /v1/applicants/{id}/submit to sync and hand off"
    }
  }
  ```
</ResponseExample>

| Field                         | Meaning                                                                                                                                                    |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hostedVerificationAvailable` | You can use `POST /v1/applicants/{id}/kyc-session` and embed Apocor's hosted widget                                                                        |
| `shareVerificationAvailable`  | BYOK share tokens are accepted via `kyc-share`                                                                                                             |
| `shareRecipientId`            | Apocor's Sumsub client id — use as the share-token **recipient** in your identity provider (BYOK only). Never use the card-program or upstream partner id. |
| `sumsubWebhookMode`           | `apocor` = Core API `/v1/webhooks/sumsub` (target); `seismic` = legacy Railway seismic-cards                                                               |
| `sumsubWebhookUrl`            | Exact URL to register in the Sumsub dashboard for this environment                                                                                         |

<Note>
  **Sumsub webhooks (target):** register Sumsub against the Core API URL from `sumsubWebhookUrl` (`POST /v1/webhooks/sumsub`). Set `SUMSUB_WEBHOOK_MODE=apocor` and `APOCOR_PUBLIC_API_URL` on the API host. After the hosted widget completes, call **`POST /v1/applicants/{id}/submit`** to sync tenant status and **trigger issuer handoff** (webhooks may also update status). Use **`GET …/kyc-status`** to **poll** issuer progress. Legacy `seismic` mode (`/webhooks/sumsub` on seismic-cards) is migration-only — see [Webhooks](/guides/webhooks).
</Note>

***

## Option A — Apocor-hosted verification

Best when you **don't** run your own Sumsub (or similar) account. Apocor hosts the widget and handles issuer handoff automatically.

<Steps>
  <Step title="Create an applicant">
    ```bash cURL theme={null}
    curl -s -X POST 'https://sandbox.apocor.ai/v1/applicants' \
      -H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' \
      -d '{
        "type": "PERSON",
        "email": "jane@example.com",
        "data": {
          "firstName": "Jane",
          "lastName": "Doe",
          "country": "US",
          "addressLine1": "123 Main St",
          "city": "New York",
          "state": "NY",
          "postalCode": "10001"
        }
      }'
    ```
  </Step>

  <Step title="Start a verification session">
    ```bash cURL theme={null}
    curl -s -X POST 'https://sandbox.apocor.ai/v1/applicants/app_abc123/kyc-session' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Response includes `accessToken` — pass it to the hosted WebSDK in your frontend. `externalUserId` is the Apocor applicant id.
  </Step>

  <Step title="User completes the widget">
    When verification succeeds in the widget, call **`POST /v1/applicants/{id}/submit`** to sync tenant status and trigger issuer handoff. With `SUMSUB_WEBHOOK_MODE=apocor`, Sumsub also posts to **`POST /v1/webhooks/sumsub`** on Core API — still call `submit` so issuer handoff is not delayed if the webhook is slow.
  </Step>

  <Step title="Confirm issuer readiness">
    ```bash cURL theme={null}
    curl -s 'https://sandbox.apocor.ai/v1/applicants/app_abc123/kyc-status' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Wait until `issuerKycReady` is `true`, then create a [cardholder](/concepts/applicants) and [issue a card](/concepts/cards).
  </Step>
</Steps>

<Tip>
  You can also call `POST /v1/applicants/{id}/submit` after the widget completes to poll tenant status and trigger issuer handoff in one step.
</Tip>

***

## Option B — Bring your own identity provider (BYOK)

Best for regulated companies that already run their own Sumsub (or compatible) environment. **You never use Apocor's hosted widget.**

<Steps>
  <Step title="Create an applicant in Apocor">
    Save the returned `id` — you will use it as `externalUserId` in your identity provider.
  </Step>

  <Step title="Verify the user in your provider">
    Run your existing KYC flow. Set `externalUserId` to the Apocor applicant id so records stay linked.
  </Step>

  <Step title="Generate a share token">
    Call your provider's share-token API with:

    * **Applicant id** — from your provider
    * **Recipient** — `shareRecipientId` from [`GET /v1/kyc/config`](/guides/kyc-integration#get-your-integration-settings) (Apocor's id, not the card issuer)

    Example (Sumsub):

    ```http theme={null}
    POST /resources/accessTokens/-/shareToken?applicantId={id}&forClientId={shareRecipientId}
    ```

    Apocor imports the shared verification and forwards it to the card program internally. The token expires quickly (typically \~10 minutes). Generate it immediately before calling Apocor.
  </Step>

  <Step title="Submit the token to Apocor">
    ```bash cURL theme={null}
    curl -s -X POST 'https://sandbox.apocor.ai/v1/applicants/app_abc123/kyc-share' \
      -H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' \
      -d '{
        "sumsub_share_token": "YOUR_SHARE_TOKEN",
        "kyc_vendor_ref": "your-provider-applicant-id",
        "status": "APPROVED"
      }'
    ```

    | Field                | Required    | Description                                                  |
    | -------------------- | ----------- | ------------------------------------------------------------ |
    | `sumsub_share_token` | Yes         | Short-lived share token from your provider                   |
    | `kyc_vendor_ref`     | No          | Your provider's applicant id (stored internally)             |
    | `status`             | Recommended | Set to `APPROVED` to attest verification in the same request |
  </Step>

  <Step title="Poll until ready">
    ```bash cURL theme={null}
    curl -s 'https://sandbox.apocor.ai/v1/applicants/app_abc123/kyc-status' \
      -H 'Authorization: Bearer YOUR_TOKEN'
    ```

    Issue cards only when `issuerKycReady` is `true`.
  </Step>
</Steps>

<Note>
  BYOK tenants do **not** need Apocor-hosted verification credentials. They only need an Apocor API key and the `shareRecipientId` from `/v1/kyc/config`.
</Note>

***

## Applicant response fields

Every applicant includes white-labeled issuer fields:

```json theme={null}
{
  "data": {
    "id": "app_abc123",
    "status": "APPROVED",
    "issuerKycStatus": "SUBMITTED",
    "issuerKycReady": false
  }
}
```

| `issuerKycStatus` | Meaning                                                   |
| ----------------- | --------------------------------------------------------- |
| `PENDING`         | Issuer account exists; identity not submitted yet         |
| `SUBMITTED`       | Share token forwarded; issuer processing                  |
| `APPROVED`        | Issuer accepted identity — cards can be issued            |
| `REJECTED`        | Issuer declined identity                                  |
| `UNKNOWN`         | Could not determine status — call `kyc-status` to refresh |

***

## Endpoints summary

| Method | Path                              | Purpose                                                                      |
| ------ | --------------------------------- | ---------------------------------------------------------------------------- |
| `GET`  | `/v1/kyc/config`                  | Integration settings + share recipient id                                    |
| `POST` | `/v1/applicants`                  | Create applicant                                                             |
| `POST` | `/v1/applicants/{id}/kyc-session` | Hosted widget session (Option A)                                             |
| `POST` | `/v1/applicants/{id}/submit`      | Poll tenant KYC; when approved, provision issuer account and trigger handoff |
| `POST` | `/v1/applicants/{id}/kyc-share`   | Submit BYOK share token (Option B)                                           |
| `GET`  | `/v1/applicants/{id}/kyc-status`  | Poll issuer KYC fields only — does **not** submit share tokens               |

***

## Troubleshooting

| Symptom                                               | Likely cause                                                                         | Fix                                                                                                            |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `issuerKycReady` stays `false` after mock sandbox KYC | Mock verification does not produce real share tokens                                 | Use hosted verification or BYOK share tokens against a live environment                                        |
| `ISSUER_KYC_INCOMPLETE` on card issue                 | Issuer still processing or share token never submitted                               | Call `POST …/submit` or `kyc-share`, then poll `GET …/kyc-status` until `issuerKycReady` is `true`             |
| Card issue fails with address / state error           | Residential address in KYC is missing **state** (state of birth alone is not enough) | Re-run verification with full address including state/subdivision, then re-submit issuer handoff               |
| Share token rejected                                  | Wrong `shareRecipientId`, reusable KYC not enabled, or token not addressed to Apocor | Confirm `GET /v1/kyc/config` returns Apocor's client id; ask Sumsub to enable sharing tenant → Apocor → issuer |
| `KYC_NOT_APPROVED` on `kyc-share`                     | Tenant status not approved                                                           | Include `"status": "APPROVED"` when attesting BYOK verification                                                |

See [Error codes](/guides/errors) for the full list of `error.code` values.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Applicants" icon="user-check" href="/concepts/applicants">
    Applicant lifecycle and statuses
  </Card>

  <Card title="Cards" icon="credit-card" href="/concepts/cards">
    Issue cards after issuer KYC is ready
  </Card>

  <Card title="Quickstart" icon="rocket" href="/quickstart">
    End-to-end sandbox walkthrough
  </Card>

  <Card title="API Reference" icon="square-terminal" href="/api-reference">
    Try endpoints in the playground
  </Card>
</CardGroup>
