> ## 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 <a href="https://www.crossmint.com/legal/privacy-policy" target="_blank">Privacy Policy</a>. This is a prerequisite for companies sharing their customer's KYC data with Crossmint.

    Consent must be collected in your app through a compliant flow (explicit checkbox, exact wording, visible Crossmint logo, re-consent on policy updates). See [Register User Data](/identity/register-user-data) for the full consent-collection requirements.

    <Warning>
      Meeting these requirements is mandatory for sharing user KYC data with Crossmint. Only record acceptance through the API below after the user has granted consent this way. Crossmint audits data-sharing integrations for compliance with these requirements.
    </Warning>

    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`, `offramp-light`):

    * `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 
            },
            {
                "type": "offramp-light",
                "status": "requires-data",
                "missingData": ["kyc-data"] // light KYC requires less data and no document uploads
            }
        ]
    }
    ```
  </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`, `offramp-light`):

    ```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"
            },
            {
                "type": "offramp-light",
                "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>
