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

# Quickstart ⚡

> Register users and upload documents to enable regulated product access.

<CardGroup cols={2}>
  <Snippet file="before-you-start.mdx" />
</CardGroup>

<Snippet file="enterprise-feature.mdx" />

This quickstart guide walks you through registering a user, uploading documents, and fetching document details using Crossmint's APIs. These steps enable your users to access regulated products like onramp, offramp, and payouts.

<Snippet file="kyc-api-enable-note.mdx" />

User locators use the `userId:` format, a unique identifier you choose for the user, for example `userId:johnd-123`.

<Steps>
  <Step title="User accepts Crossmint's Privacy Policy">
    Before sharing any user KYC data with Crossmint, you must ensure your users have accepted Crossmint's [Privacy Policy](https://www.crossmint.com/legal/privacy-policy). This is a prerequisite for companies sharing their customer's KYC data with Crossmint.

    <Accordion title="Requirements for collecting consent">
      Consent must be obtained following the guidelines below:

      1. The user must have the ability to see the terms they must accept
      2. The user must grant consent with a checkbox, unchecked by default
      3. The user doesn't need to open the hyperlink to the privacy policy to view the terms
      4. The user needs to see Crossmint's logo in the flow
      5. The specific text that you must present to your end user is: **"I consent to my KYC data being processed by Crossmint in accordance with its [Privacy Policy](https://www.crossmint.com/legal/privacy-policy)"**
      6. The text can be localized but must link to the same privacy policy link provided above
      7. If the privacy policy is updated, you must ask the user to agree to the above terms again
    </Accordion>

    Once the user has accepted the privacy policy, record their acceptance using the following API call:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/legal-documents' \
          --header 'X-API-KEY: <your-server-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "type": "crossmint-privacy-policy",
              "acceptedAt": "2025-10-05T14:48:00Z"
          }'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123";

      const options = {
          method: 'PUT',
          headers: {'X-API-KEY': '<your-server-api-key>', 'Content-Type': 'application/json'},
          body: JSON.stringify({
              type: "crossmint-privacy-policy",
              acceptedAt: "2025-10-05T14:48:00Z" // ISO 8601 date string
          })
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/legal-documents`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/legal-documents"

      payload = {
          "type": "crossmint-privacy-policy",
          "acceptedAt": "2025-10-05T14:48:00Z"  # ISO 8601 date string
      }
      headers = {
          "X-API-KEY": "<your-server-api-key>",
          "Content-Type": "application/json"
      }

      response = requests.put(url, json=payload, headers=headers)

      print(response.json())
      ```
    </CodeGroup>

    <Note>This call also creates a user with the specified `userLocator` if one does not already exist.</Note>
  </Step>

  <Step title="Register user information">
    Register a user with Crossmint by specifying their `userLocator`, their personal details, and KYC data using the [Create User](/api-reference/users/upsert-user) endpoint

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
          --header 'X-API-KEY: <your-server-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "userDetails": {
                  "firstName": "John",
                  "lastName": "Doe",
                  "dateOfBirth": "1995-01-01",
                  "countryOfResidence": "DE"
              }
          }'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123"; 

      const options = {
          method: 'PUT',
          headers: {'X-API-KEY': '<your-server-api-key>', 'Content-Type': 'application/json'},
          body: JSON.stringify({
              userDetails: {
                  firstName: "John",
                  lastName: "Doe",
                  dateOfBirth: "1995-01-01",
                  countryOfResidence: "DE"
              }
          })
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"

      payload = {
          "userDetails": {
              "firstName": "John",
              "lastName": "Doe",
              "dateOfBirth": "1995-01-01",
              "countryOfResidence": "DE"
          }
      }
      headers = {
          "X-API-KEY": "<your-server-api-key>",
          "Content-Type": "application/json"
      }

      response = requests.put(url, json=payload, headers=headers)

      print(response.json())
      ```
    </CodeGroup>
  </Step>

  <Step title="Run identity verification">
    Once you have registered relevant user information you can trigger checks via the [Trigger Identity Verification](/api-reference/users/trigger-identity-verification) endpoint.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
          --header 'X-API-KEY: <your-server-api-key>'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123";

      const options = {
          method: 'PUT',
          headers: {
              'X-API-KEY': '<your-server-api-key>'
          }
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"

      headers = {
          "X-API-KEY": "<your-server-api-key>"
      }

      response = requests.put(url, headers=headers)

      print(response.json())
      ```
    </CodeGroup>

    The response will include the eligibility status for each verification type (`regulated-transfer`, `onramp`, `onramp-light`, `offramp`):

    * `not-started`: Verification has not been initiated
    * `pending-privacy-policy`: User has not yet accepted Crossmint's Privacy Policy
    * `requires-data`: Additional user data or documents are needed
    * `pending-review`: Verification is in progress
    * `pending-manual-review`: Verification requires manual review by Crossmint's compliance team
    * `verified`: User has passed verification
    * `rejected`: User has failed verification

    Once a product reads `verified`, the user can use it. A `verified` `offramp` status, for example, means the user can cash out through [Offramp](/offramp/overview).

    If any of them return `requires-data`, you must aggregate the `missingData` and `missingDocuments` arrays from those specific eligibility objects. Then, update the user's information using the [Update User](/api-reference/users/upsert-user) endpoint and/or upload additional documents using the [Upload Document](/api-reference/users/upload-document) endpoint, and finally re-trigger the verification.

    ```json theme={null}
    {
        "eligibility": [
            {
                "type": "regulated-transfer",
                "status": "pending-review" // or "verified"
            },
            {
                "type": "onramp",
                "status": "requires-data",
                "missingData": ["kyc-data", "due-diligence", "verification-history"],
                "missingDocuments": ["id", "selfie-front"] // "proof-of-address" and "proof-of-income" may also be requested in case of Enhanced Due Diligence 
            },
            {
                "type": "onramp-light",
                "status": "requires-data",
                "missingData": ["kyc-data"] // light KYC requires less data and no document uploads
            },
            {
                "type": "offramp",
                "status": "requires-data",
                "missingData": ["kyc-data", "due-diligence", "verification-history"],
                "missingDocuments": ["id", "selfie-front"] // "proof-of-address" and "proof-of-income" may also be requested in case of Enhanced Due Diligence 
            }
        ]
    }
    ```
  </Step>

  <Step title="Update user information">
    Register additional user information with Crossmint so the user can access onramp and offramp services.

    <Note> Calling this endpoint again with the same `userLocator` will update the existing user's information. The endpoint is not additive so specify all relevant user information when updating the user.</Note>

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
          --header 'X-API-KEY: <your-server-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "userDetails": {
                  "firstName": "John",
                  "lastName": "Doe",
                  "dateOfBirth": "1995-01-01",
                  "countryOfResidence": "DE"
              },
              "kycData": {
                  "nationality": "DE",
                  "addressOfResidence": {
                      "line1": "123 Hauptstrasse",
                      "line2": "Apt 5",
                      "city": "Berlin",
                      "stateOrRegion": "Berlin",
                      "postalCode": "10115"
                  },
                  "email": "johnd@example.com",
                  "identityDocument": {
                      "type": "passport",
                      "number": "AS12321",
                      "issuingCountryCode": "DE"
                  },
                  "ipAddresses": ["203.0.113.1"]
              },
              "dueDiligence": {
                  "employmentStatus": "full-time",
                  "sourceOfFunds": "salary-disbursement",
                  "industry": "financial-institution",
                  "expectedYearlyTxVolume": "volume-0-25k"
              },
              "verificationHistory": {
                  "idVerificationTimestamp": "2024-01-15T10:30:00Z",
                  "livenessVerificationTimestamp": "2024-01-15T10:32:00Z"
              }
          }'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123"; 

      const options = {
          method: 'PUT',
          headers: {'X-API-KEY': '<your-server-api-key>', 'Content-Type': 'application/json'},
          body: JSON.stringify({
              userDetails: {
                  firstName: "John",
                  lastName: "Doe",
                  dateOfBirth: "1995-01-01",
                  countryOfResidence: "DE"
              },
              kycData: {
                  nationality: "DE",
                  addressOfResidence: {
                      line1: "123 Hauptstrasse",
                      line2: "Apt 5",
                      city: "Berlin",
                      stateOrRegion: "Berlin",
                      postalCode: "10115"
                  },
                  email: "johnd@example.com",
                  identityDocument: {
                      type: "passport",
                      number: "AS12321",
                      issuingCountryCode: "DE"
                  },
                  ipAddresses: ["203.0.113.1"]
              },
              dueDiligence: {
                  employmentStatus: "full-time",
                  sourceOfFunds: "salary-disbursement",
                  industry: "financial-institution",
                  expectedYearlyTxVolume: "volume-0-25k"
              },
              verificationHistory: {
                  idVerificationTimestamp: "2024-01-15T10:30:00Z",
                  livenessVerificationTimestamp: "2024-01-15T10:32:00Z"
              }
          })
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"

      payload = {
          "userDetails": {
              "firstName": "John",
              "lastName": "Doe",
              "dateOfBirth": "1995-01-01",
              "countryOfResidence": "DE"
          },
          "kycData": {
              "nationality": "DE",
              "addressOfResidence": {
                  "line1": "123 Hauptstrasse",
                  "line2": "Apt 5",
                  "city": "Berlin",
                  "stateOrRegion": "Berlin",
                  "postalCode": "10115"
              },
              "email": "johnd@example.com",
              "identityDocument": {
                  "type": "passport",
                  "number": "AS12321",
                  "issuingCountryCode": "DE"
              },
              "ipAddresses": ["203.0.113.1"]
          },
          "dueDiligence": {
              "employmentStatus": "full-time",
              "sourceOfFunds": "salary-disbursement",
              "industry": "financial-institution",
              "expectedYearlyTxVolume": "volume-0-25k"
          },
          "verificationHistory": {
              "idVerificationTimestamp": "2024-01-15T10:30:00Z",
              "livenessVerificationTimestamp": "2024-01-15T10:32:00Z"
          }
      }
      headers = {
          "X-API-KEY": "<your-server-api-key>",
          "Content-Type": "application/json"
      }

      response = requests.put(url, json=payload, headers=headers)

      print(response.json())
      ```
    </CodeGroup>
  </Step>

  <Step title="Upload a document">
    Upload identity or supporting documents for the user using the [Upload Document](/api-reference/users/upload-document) endpoint. Documents are associated with the user via their `userLocator`.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
          --url 'https://staging.crossmint.com/api/2025-06-09/documents' \
          --header 'X-API-KEY: <your-server-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "reference": {
                  "userLocator": "userId:johnd-123"
              },
              "documentType": "id-passport",
              "data": "<base64-encoded-image>",
              "expiresAt": "2030-12-31"
          }'
      ```

      ```javascript Node.js theme={null}
      const options = {
          method: 'POST',
          headers: {'X-API-KEY': '<your-server-api-key>', 'Content-Type': 'application/json'},
          body: JSON.stringify({
              reference: {
                  userLocator: "userId:johnd-123"
              },
              documentType: "id-passport",
              data: "<base64-encoded-image>",
              expiresAt: "2030-12-31"
          })
      };

      fetch('https://staging.crossmint.com/api/2025-06-09/documents', options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      url = "https://staging.crossmint.com/api/2025-06-09/documents"

      payload = {
          "reference": {
              "userLocator": "userId:johnd-123"
          },
          "documentType": "id-passport",
          "data": "<base64-encoded-image>",
          "expiresAt": "2030-12-31"
      }
      headers = {
          "X-API-KEY": "<your-server-api-key>",
          "Content-Type": "application/json"
      }

      response = requests.post(url, json=payload, headers=headers)

      print(response.json())
      ```
    </CodeGroup>

    **Supported document types:**

    * Identity documents: `id-passport`, `id-idcard-front`, `id-idcard-back`, `id-residency-permit` (Spain residents only)
    * Supporting documents: `proof-of-address`, `proof-of-income`
    * Selfie documents: `selfie-front`, `selfie-left`, `selfie-right`

    <Note>US residents must include `kycData.phoneNumber`, and provide their identity via SSN in `kycData.identityDocument`. No identity document upload or selfie is needed for standard on/offramp. However, if Enhanced Due Diligence (EDD) is triggered, US users must also upload a passport (`id-passport`) or driver's license (`id-idcard-front`, `id-idcard-back`). Proof of income is **not** required for US EDD users.</Note>

    <Note>Calling this endpoint again with the same `userLocator` and `documentType` will update the existing document's registered information.</Note>
  </Step>

  <Step title="Run identity verification">
    Once you have registered the user's information and uploaded the required documents, trigger the KYC verification process using the [Trigger Identity Verification](/api-reference/users/trigger-identity-verification) endpoint.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
          --header 'X-API-KEY: <your-server-api-key>'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123";

      const options = {
          method: 'PUT',
          headers: {
              'X-API-KEY': '<your-server-api-key>'
          }
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"

      headers = {
          "X-API-KEY": "<your-server-api-key>"
      }

      response = requests.put(url, headers=headers)

      print(response.json())
      ```
    </CodeGroup>

    The response will include the eligibility status for each verification type (`regulated-transfer`, `onramp`, `onramp-light`, `offramp`):

    ```json theme={null}
    {
        "eligibility": [
            {
                "type": "regulated-transfer",
                "status": "verified"
            },
            {
                "type": "onramp",
                "status": "pending-review"
            },
            {
                "type": "onramp-light",
                "status": "pending-review"
            },
            {
                "type": "offramp",
                "status": "pending-review"
            }
        ]
    }
    ```
  </Step>

  <Step title="Check verification status">
    Verification runs asynchronously. After triggering it, poll the [Get Identity Verification Status](/api-reference/users/get-identity-verification) endpoint until the relevant product status reads `verified`. It usually completes within a minute, and the transient state may be `not-started` or `pending-review`.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request GET \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
          --header 'X-API-KEY: <your-server-api-key>'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123";

      const options = {
          method: 'GET',
          headers: {
              'X-API-KEY': '<your-server-api-key>'
          }
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"

      headers = {
          "X-API-KEY": "<your-server-api-key>"
      }

      response = requests.get(url, headers=headers)

      print(response.json())
      ```
    </CodeGroup>
  </Step>

  <Step title="Fetch user information">
    You can fetch a user's information at any time to see their current status, including when they last accepted valid legal documents. Use the Get User endpoint to retrieve this information:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request GET \
          --url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
          --header 'X-API-KEY: <your-server-api-key>'
      ```

      ```javascript Node.js theme={null}
      const userLocator = "userId:johnd-123";

      const options = {
          method: 'GET',
          headers: {
              'X-API-KEY': '<your-server-api-key>'
          }
      };

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```

      ```python Python theme={null}
      import requests

      user_locator = "userId:johnd-123"

      url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"

      headers = {
          "X-API-KEY": "<your-server-api-key>"
      }

      response = requests.get(url, headers=headers)

      print(response.json())
      ```
    </CodeGroup>

    The response includes the `legalDocuments` array, which shows the status of legal document acceptances:

    ```json theme={null}
    {
        "email": "john.doe@example.com",
        "phoneNumber": "+1234567890",
        "userId": "usr_1234567890",
        "userDetails": true,
        "kycData": false,
        "dueDiligence": false,
        "verificationHistory": false,
        "legalDocuments": [
            {
                "type": "crossmint-privacy-policy",
                "acceptedAt": "2024-01-15T10:30:00Z",
                "validVersion": true
            }
        ]
    }
    ```

    The `validVersion` field indicates whether the user has accepted the current version of the legal document. If `validVersion` is `false`, you should prompt the user to accept the updated terms.
  </Step>
</Steps>

## Launching in Production

For production, the steps are almost identical, but some changes are required:

1. Create a developer account on the [production console](https://www.crossmint.com/console)
2. Create a production server API key on the [API Keys](https://www.crossmint.com/console/projects/apiKeys) page with the API scopes `users.create`, `users.read`
3. Replace your test API key with the production key
4. Replace `staging.crossmint.com` with `www.crossmint.com` in the API URLs

## Learn More

<CardGroup cols={2}>
  <Card title="Data Requirements" icon="table" iconType="duotone" color="#6554C0" href="/identity/data-requirements">
    See what data is required for each activity and region.
  </Card>

  <Card title="Talk to an expert" icon="message" iconType="duotone" color="#ADD8E6" href="https://www.crossmint.com/contact/sales" target="_blank" rel="noopener">
    Contact the Crossmint sales team for support.
  </Card>
</CardGroup>
