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

# Headless KYC

> Submit a user's verification data from your server and own the KYC interface

With headless KYC, your app's server submits a user's verification data to Privy, and Privy forwards it to Bridge. No hosted Bridge link is involved, so your app owns the interface end to end and controls how each field is collected.

Use [hosted KYC](/kyc-kyb/kyc) instead when Bridge should collect the data, including document capture and liveness checks.

At a high level, headless KYC involves three steps:

**1. Accept terms of service**

Collect the user's acceptance of Bridge's terms of service.

**2. Submit verification data**

Your app's server submits the user's identity data to Privy.

**3. Track status**

Bridge reviews the submission asynchronously. Your app polls status or consumes webhooks.

Before verifying users, [register a Bridge API key with Privy](/kyc-kyb/setup) and enable server-side flows for the environment the requests target.

## Accept terms of service

Before submitting verification data, follow the [hosted terms of service](/kyc-kyb/kyc-tos) guide to collect the user's acceptance. If Bridge has approved your app to collect acceptance in your own flow, follow the [terms of service reliance](/kyc-kyb/kyc-tos-reliance) guide instead. The reliance guide also explains when to include `client_agreement_id` in a submission.

## Submit verification data

<View title="NodeJS" icon="node-js">
  Use the `submit` method from the `kyc` resource on `users()`.

  ```ts {skip-check} theme={"system"}
  import {PrivyClient} from '@privy-io/node';

  const privy = new PrivyClient({
    appId: process.env.PRIVY_APP_ID!,
    appSecret: process.env.PRIVY_APP_SECRET!
  });

  const status = await privy.users().kyc.submit('did:privy:xxxxx', {
    provider: 'bridge',
    environment: 'sandbox',
    endorsements: ['base'],
    data: {
      first_name: 'Alex',
      last_name: 'Doe',
      email: 'user@example.com',
      date_of_birth: '1990-01-31',
      residential_address: {
        street_line_1: '123 Market Street',
        city: 'San Francisco',
        subdivision: 'CA',
        postal_code: '94103',
        country: 'USA'
      },
      identifying_information: [
        {
          type: 'ssn',
          issuing_country: 'USA',
          number: '000-00-0000'
        }
      ]
    }
  });

  // status.kyc.status is Bridge's verification status
  // status.requirements_due lists what Bridge is still waiting on
  ```
</View>

<View title="REST API" icon="terminal">
  To submit KYC data for a user, make a `POST` request to:

  ```bash theme={"system"}
  https://api.privy.io/v1/users/{user_id}/kyc/submit
  ```

  See the [API reference](/api-reference/fiat/kyc-server/submit) for the full request and response schema.

  In the body of the request, include the following fields:

  **provider** `'bridge'` *required*

  Provider to verify the user with.

  **environment** `'production' | 'sandbox'`

  Bridge environment to use. Defaults to `production`.

  **endorsements** `string[]`

  [Endorsements](https://apidocs.bridge.xyz/platform/customers/customers/endorsements) to request
  from Bridge. Each endorsement unlocks a set of rails and regions.

  **client\_agreement\_id** `string`

  Agreement identifier to include when using [terms of service reliance](/kyc-kyb/kyc-tos-reliance).
  See the [hosted terms of service](/kyc-kyb/kyc-tos) guide for the default acceptance flow.

  **data** `object` *required*

  Verification data for the user. Must include at least one field.

  **properties**

  **data.first\_name** `string`

  Legal first name.

  **data.last\_name** `string`

  Legal last name.

  **data.email** `string`

  Email address for the Bridge customer. On the first submission, Privy falls back to the user's
  linked email when this is omitted. On a later submission, sending a different address replaces
  the one Bridge has on file.

  **data.phone** `string`

  Phone number in E.164 format.

  **data.date\_of\_birth** `string`

  Date of birth in `YYYY-MM-DD` format.

  **data.residential\_address** `object`

  Residential address.

  **properties**

  **data.residential\_address.street\_line\_1** `string` *required*

  Street address line 1. At least 4 characters.

  **data.residential\_address.street\_line\_2** `string`

  Street address line 2.

  **data.residential\_address.city** `string` *required*

  City.

  **data.residential\_address.subdivision** `string`

  ISO 3166-2 state or province code. Required for US addresses.

  **data.residential\_address.postal\_code** `string`

  Postal code. Required for countries that use them.

  **data.residential\_address.country** `string` *required*

  ISO 3166-1 alpha-3 country code.

  **data.identifying\_information** `object[]`

  Identifying documents for the user. At least one entry.

  **properties**

  **data.identifying\_information\[].type** `string` *required*

  Document type identifier, such as `ssn` or `passport`. See the [Bridge customer API
  reference](https://apidocs.bridge.xyz/platform/customers/customers/api) for accepted
  values.

  **data.identifying\_information\[].issuing\_country** `string` *required*

  ISO 3166-1 alpha-3 issuing country code.

  **data.identifying\_information\[].number** `string`

  Document number.

  **data.identifying\_information\[].description** `string`

  Document description.

  **data.identifying\_information\[].expiration** `string`

  Document expiration date.

  **data.identifying\_information\[].image\_front** `string`

  Base64-encoded front image of the document.

  **data.identifying\_information\[].image\_back** `string`

  Base64-encoded back image of the document.

  **data.nonresident\_alien\_attestation** `boolean`

  Attests the user is a nonresident alien. See [nonresident alien
  attestation](#nonresident-alien-attestation).

  Below is a sample cURL command for this request:

  ```bash theme={"system"}
  curl --request POST https://api.privy.io/v1/users/did:privy:xxxxx/kyc/submit \
    -u "<your-privy-app-id>:<your-privy-app-secret>" \
    -H "privy-app-id: <your-privy-app-id>" \
    -H 'Content-Type: application/json' \
    -d '{
      "provider": "bridge",
      "environment": "sandbox",
      "endorsements": ["base"],
      "data": {
        "first_name": "Alex",
        "last_name": "Doe",
        "email": "user@example.com",
        "date_of_birth": "1990-01-31",
        "residential_address": {
          "street_line_1": "123 Market Street",
          "city": "San Francisco",
          "subdivision": "CA",
          "postal_code": "94103",
          "country": "USA"
        },
        "identifying_information": [
          {
            "type": "ssn",
            "issuing_country": "USA",
            "number": "000-00-0000"
          }
        ]
      }
    }'
  ```

  The response is the user's full KYC status snapshot, in the same shape as [track KYC
  status](/kyc-kyb/kyc-status).

  ```json theme={"system"}
  {
    "provider": "bridge",
    "environment": "sandbox",
    "status": "under_review",
    "tos": {
      "status": "approved"
    },
    "kyc": {
      "status": "under_review"
    },
    "endorsements": [
      {
        "name": "base",
        "status": "incomplete",
        "missing": ["kyc_approval"]
      }
    ],
    "capabilities": {
      "payin_crypto": "pending",
      "payout_crypto": "pending",
      "payin_fiat": "pending",
      "payout_fiat": "pending"
    },
    "requirements_due": ["id_verification"],
    "future_requirements_due": []
  }
  ```
</View>

The first submission must carry enough data for Bridge to begin verification: name, date of birth, residential address, and at least one identifying document.

> [!NOTE]
>
> Later submissions update the same Bridge customer. Omitted top-level fields are left untouched,
> including `email`; sending a different `email` replaces the address Bridge has on file, and since
> Bridge identifies customers by email, the old address stops resolving to this one. Nested objects
> such as a `residential_address` are replaced whole. If a response never reached you, resubmit the
> identical data.

> [!WARNING]
>
> Bridge allows one customer per email address, across individuals and businesses alike. A
> submission whose email already has a Bridge customer returns `409` — Privy will not adopt an
> existing customer, since an email in a request body is not proof the user owns it. Submit a
> different email, or contact [support@privy.io](mailto:support@privy.io) to link an existing Bridge
> customer to a Privy user.

## Nonresident alien attestation

`data.nonresident_alien_attestation` attests that the user is a nonresident alien, which can satisfy identification without a US tax ID.

It is one input to Bridge's nonresident alien eligibility, not all of it, and Bridge must enable the capability for your account before it is accepted. It applies to individuals only and is rejected on business customers, so it is not part of a [KYB submission](/kyc-kyb/kyb-headless).

## Next steps

> **[Track KYC status](/kyc-kyb/kyc-status)**
>
> Poll a user's verification status or subscribe to webhooks

> **[Headless KYB](/kyc-kyb/kyb-headless)**
>
> Submit an organization's verification data from your server


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