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

# User Registration

> Complete step-by-step guide to implementing the multi-step registration flow with email/phone verification and KYC data collection

## Overview

The registration process is an 8-step flow that collects required KYC (Know Your Customer) information from new users and establishes formal consent records. Each step must be completed in order, with the `onboardingId` passed between steps to maintain session continuity.

**Steps 1-2:** Email and phone verification with `onboardingId` generation.

**Step 3:** KYC identity verification via Veriff using `onboardingId` (no authentication required).

**Steps 4-5:** Personal details and consent record creation using `onboardingId` (consent required for regulatory compliance - GDPR, CCPA, E-Sign Act).

**Steps 6-7:** Address collection that finalizes registration and returns `userId` and `accessToken`.

**Step 8:** Link consent set to `userId` to complete the audit trail.

<Note>
  All registration endpoints require the `x-client-key` header. For US environment routing, include `x-us-env: true` header or `region=us` query parameter.
</Note>

<Warning>
  **Consent Must Be Created Before Address Submission:** Step 5 creates the formal consent record required for regulatory compliance. The address endpoints (Steps 6-7) finalize registration and return the `userId`, which is then used to link the consent (Step 8). Do not skip consent creation for production deployments handling user data under GDPR, CCPA, or E-Sign Act requirements. See [Consent Management](/guides/consent/overview) for details.
</Warning>

## Registration Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant Client as Frontend/Client
    participant API as Backend/API

    Note over Client,API: Step 1: Email Verification - Send Code
    Client->>API: POST /v1/auth/register/email/send<br/>{email}
    API-->>Client: 200 OK<br/>{contactVerificationId}
    Note over Client: Store contactVerificationId

    Note over Client,API: Step 2: Email Verification - Verify
    Client->>API: POST /v1/auth/register/email/verify<br/>{email, password, code, contactVerificationId}
    API-->>Client: 200 OK<br/>{onboardingId, user}
    Note over Client: Store onboardingId for all remaining steps

    Note over Client,API: Step 3: Phone Verification - Send Code
    Client->>API: POST /v1/auth/register/phone/send<br/>{phoneCountryCode, phoneNumber, contactVerificationId}
    API-->>Client: 200 OK (SMS sent)

    Note over Client,API: Step 4: Phone Verification - Verify
    Client->>API: POST /v1/auth/register/phone/verify<br/>{phoneCountryCode, phoneNumber, code, onboardingId}
    API-->>Client: 200 OK

    Note over Client,API: Step 5: KYC Verification (Veriff)
    Client->>API: POST /v1/auth/register/verification<br/>{onboardingId}
    API-->>Client: 200 OK<br/>{sessionUrl}
    Note over Client: Redirect user to Veriff sessionUrl
    Note over Client: User completes ID verification
    Note over Client: Poll verification status

    Note over Client,API: Step 6: Personal Details
    alt US User
        Client->>API: POST /v1/auth/register/personal-details<br/>{onboardingId, firstName, lastName, DOB, nationality, SSN}
        API-->>Client: 200 OK
    else Non-US User
        Client->>API: POST /v1/auth/register/personal-details<br/>{onboardingId, firstName, lastName, DOB, nationality}
        API-->>Client: 200 OK
    end

    Note over Client,API: Step 7: Create Formal Consent Record<br/>(Required BEFORE address submission)
    Client->>API: POST /v2/consent/onboarding<br/>{onboardingId, policy, consents, metadata}
    API-->>Client: 200 OK<br/>{consentSetId}
    Note over Client: Store consentSetId for Step 10

    Note over Client,API: Step 8: Physical Address (Finalizes Registration)
    alt US User
        Client->>API: POST /v1/auth/register/address<br/>{onboardingId, addressLine1, city, zip, usState, isSameMailingAddress}
        alt isSameMailingAddress = true
            API-->>Client: 200 OK<br/>{accessToken, userId, user}
            Note over Client: Registration Finalized!
        else isSameMailingAddress = false
            API-->>Client: 200 OK<br/>{accessToken: null, user}
            Note over Client: Proceed to Step 9
        end
    else Non-US User
        Client->>API: POST /v1/auth/register/address<br/>{onboardingId, addressLine1, city, zip, isSameMailingAddress: true}
        API-->>Client: 200 OK<br/>{accessToken, userId, user}
        Note over Client: Registration Finalized!
    end

    Note over Client,API: Step 9: Mailing Address (US only, if needed)
    Client->>API: POST /v1/auth/register/mailing-address<br/>{onboardingId, mailingAddressLine1, mailingCity, mailingZip, mailingUsState}
    API-->>Client: 200 OK<br/>{accessToken, userId, user}
    Note over Client: Registration Finalized!

    Note over Client,API: Step 10: Link Consent to User<br/>(Complete audit trail)
    Client->>API: PATCH /v2/consent/onboarding/{consentSetId}<br/>{userId}
    API-->>Client: 200 OK<br/>{linked consent set}
    Note over Client: Full Registration Complete!<br/>Access token valid for 6 hours<br/>Consent audit trail established
```

## Complete Flow Overview

```mermaid theme={null}
sequenceDiagram
    participant User
    participant App
    participant API

    User->>App: Enter email
    App->>API: POST /v1/auth/register/email/send
    API->>User: Email with code

    User->>App: Enter code + password
    App->>API: POST /v1/auth/register/email/verify
    API->>App: onboardingId

    User->>App: Enter phone number
    App->>API: POST /v1/auth/register/phone/send
    API->>User: SMS with code
    User->>App: Enter verification code
    App->>API: POST /v1/auth/register/phone/verify
    API->>App: Success

    App->>API: POST /v1/auth/register/verification
    API->>App: sessionUrl
    App->>User: Redirect to Veriff
    User->>App: Complete ID verification
    Note over User,App: Wait for verification processing (5-30 min)

    User->>App: Enter personal details
    App->>API: POST /v1/auth/register/personal-details
    API->>App: User updated

    Note over App,API: Create consent record BEFORE address
    App->>API: POST /v2/consent/onboarding
    API->>App: consentSetId

    User->>App: Enter address
    App->>API: POST /v1/auth/register/address
    API->>App: userId + accessToken or continue

    Note over App,API: US users only (if mailing ≠ physical)
    App->>API: POST /v1/auth/register/mailing-address
    API->>App: userId + accessToken

    Note over App,API: Link consent to user
    App->>API: PATCH /v2/consent/onboarding/{consentSetId}
    API->>App: Consent linked

    User->>App: Registration complete with audit trail
```

## Step 1: Email Verification (Send)

Initiate registration by sending a verification code to the user's email address.

### Endpoint

```bash theme={null}
POST /v1/auth/register/email/send
```

**API Reference:** [`POST /v1/auth/register/email/send`](/api-reference/auth/register-email-send)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/email/send \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/email/send', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'user@example.com'
    })
  });

  const data = await response.json();
  console.log('Verification ID:', data.contactVerificationId);
  ```

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

  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/email/send',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'email': 'user@example.com'
      }
  )

  data = response.json()
  print(f"Verification ID: {data['contactVerificationId']}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "contactVerificationId": "US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
}
```

<Warning>
  Store the `contactVerificationId` securely. This ID is required for the email verification step and phone verification step.
</Warning>

### Field Descriptions

| Field   | Type   | Required | Description                                       |
| ------- | ------ | -------- | ------------------------------------------------- |
| `email` | string | Yes      | User's email address (must be valid email format) |

### Common Errors

<AccordionGroup>
  <Accordion title="Email Already Registered">
    ```json theme={null}
    {
      "message": "Email already exists"
    }
    ```

    **Resolution**: User should login instead, or use password recovery
  </Accordion>

  <Accordion title="Invalid Email Format">
    ```json theme={null}
    {
      "message": "Invalid email format"
    }
    ```

    **Resolution**: Validate email format before submission
  </Accordion>
</AccordionGroup>

## Step 2: Email Verification (Verify)

Verify the code received via email and create the user onboarding session.

### Endpoint

```bash theme={null}
POST /v1/auth/register/email/verify
```

**API Reference:** [`POST /v1/auth/register/email/verify`](/api-reference/auth/register-email-verify)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/email/verify \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "email": "user@example.com",
      "password": "SecurePassword123!",
      "verificationCode": "123456",
      "contactVerificationId": "US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb",
      "countryOfResidence": "GB",
      "allowMarketing": true,
      "allowSms": true
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/email/verify', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'user@example.com',
      password: 'SecurePassword123!',
      verificationCode: '123456',
      contactVerificationId: 'US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb',
      countryOfResidence: 'GB',
      allowMarketing: true,
      allowSms: true
    })
  });

  const data = await response.json();
  console.log('Onboarding ID:', data.onboardingId);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/email/verify',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'email': 'user@example.com',
          'password': 'SecurePassword123!',
          'verificationCode': '123456',
          'contactVerificationId': 'US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb',
          'countryOfResidence': 'GB',
          'allowMarketing': True,
          'allowSms': True
      }
  )

  data = response.json()
  print(f"Onboarding ID: {data['onboardingId']}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "hasAccount": false,
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "user": {
    "id": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
    "email": "user@example.com",
    "verificationState": "UNVERIFIED"
  }
}
```

<Info>
  The `onboardingId` returned here must be passed to all subsequent registration steps. Store it securely in your session.
</Info>

### Field Descriptions

| Field                   | Type    | Required | Description                                                                                                 |
| ----------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `email`                 | string  | Yes      | User's email address (must match Step 1)                                                                    |
| `password`              | string  | Yes      | User's chosen password (8+ characters recommended)                                                          |
| `verificationCode`      | string  | Yes      | 6-digit code received via email                                                                             |
| `contactVerificationId` | string  | Yes      | ID from Step 1 response                                                                                     |
| `countryOfResidence`    | string  | Yes      | ISO 3166-1 alpha-2 country code (e.g., "GB", "US")                                                          |
| `allowMarketing`        | boolean | Yes      | User consent for marketing communications (see [Consent Management](/guides/consent/overview) for tracking) |
| `allowSms`              | boolean | Yes      | User consent for SMS communications (see [Consent Management](/guides/consent/overview) for tracking)       |

<Tip>
  **Best Practice:** After registration completes, use the [Consent Management API](/guides/consent/overview) to create a formal consent record linking the user to their consent choices. This provides an immutable audit trail for regulatory compliance (GDPR, CCPA, E-Sign Act) and allows users to review or revoke their consent later.
</Tip>

### Password Requirements

<Note>
  While specific requirements may vary, follow these best practices:

  * Minimum 8 characters
  * Include uppercase and lowercase letters
  * Include numbers and special characters
  * Avoid common passwords
</Note>

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Verification Code">
    ```json theme={null}
    {
      "message": "Invalid verification code"
    }
    ```

    **Resolution**: Request a new code via Step 1 and try again
  </Accordion>

  <Accordion title="Code Expired">
    ```json theme={null}
    {
      "message": "Verification code has expired"
    }
    ```

    **Resolution**: Codes typically expire after 10-15 minutes. Request a new code via Step 1
  </Accordion>

  <Accordion title="Invalid Contact Verification ID">
    ```json theme={null}
    {
      "message": "Invalid or expired contact verification ID"
    }
    ```

    **Resolution**: Restart from Step 1 to get a new verification ID
  </Accordion>
</AccordionGroup>

## Step 3: Phone Verification (Send)

Send SMS verification code to the user's phone number.

### Endpoint

```bash theme={null}
POST /v1/auth/register/phone/send
```

**API Reference:** [`POST /v1/auth/register/phone/send`](/api-reference/auth/register-phone-send)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/phone/send \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "phoneCountryCode": "+44",
      "phoneNumber": "7400846282",
      "contactVerificationId": "US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/phone/send', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      phoneCountryCode: '+44',
      phoneNumber: '7400846282',
      contactVerificationId: 'US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb'
    })
  });

  const data = await response.json();
  console.log('SMS sent:', data.success);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/phone/send',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'phoneCountryCode': '+44',
          'phoneNumber': '7400846282',
          'contactVerificationId': 'US_100a99cf-f4d3-4fa1-9be9-2e9828b20ebb'
      }
  )

  data = response.json()
  print(f"SMS sent: {data['success']}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "success": true
}
```

### Field Descriptions

| Field                   | Type   | Required | Description                                                  |
| ----------------------- | ------ | -------- | ------------------------------------------------------------ |
| `phoneCountryCode`      | string | Yes      | International dialing code with + prefix (e.g., "+44", "+1") |
| `phoneNumber`           | string | Yes      | Phone number without country code or formatting              |
| `contactVerificationId` | string | Yes      | ID from Step 1 response (same one used in Step 2)            |

<Tip>
  Phone number should be provided without the country code, spaces, or special characters. The country code is sent separately in `phoneCountryCode`.
</Tip>

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Phone Number">
    ```json theme={null}
    {
      "message": "Invalid phone number format"
    }
    ```

    **Resolution**: Ensure phone number is valid for the specified country code and contains only digits
  </Accordion>

  <Accordion title="Phone Number Already Registered">
    ```json theme={null}
    {
      "message": "Phone number already exists"
    }
    ```

    **Resolution**: User may already have an account with this number
  </Accordion>
</AccordionGroup>

## Step 4: Phone Verification (Verify)

Verify the SMS code and link the phone number to the user's onboarding session.

### Endpoint

```bash theme={null}
POST /v1/auth/register/phone/verify
```

**API Reference:** [`POST /v1/auth/register/phone/verify`](/api-reference/auth/register-phone-verify)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/phone/verify \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "phoneCountryCode": "+44",
      "phoneNumber": "7400846282",
      "verificationCode": "123456",
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/phone/verify', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      phoneCountryCode: '+44',
      phoneNumber: '7400846282',
      verificationCode: '123456',
      onboardingId: 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb'
    })
  });

  const data = await response.json();
  console.log('Phone verified:', data.success);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/phone/verify',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'phoneCountryCode': '+44',
          'phoneNumber': '7400846282',
          'verificationCode': '123456',
          'onboardingId': 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb'
      }
  )

  data = response.json()
  print(f"Phone verified: {data['success']}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "success": true
}
```

### Field Descriptions

| Field              | Type   | Required | Description                        |
| ------------------ | ------ | -------- | ---------------------------------- |
| `phoneCountryCode` | string | Yes      | Must match Step 3                  |
| `phoneNumber`      | string | Yes      | Must match Step 3                  |
| `verificationCode` | string | Yes      | 6-digit code received via SMS      |
| `onboardingId`     | string | Yes      | Onboarding ID from Step 2 response |

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Verification Code">
    ```json theme={null}
    {
      "message": "Invalid verification code"
    }
    ```

    **Resolution**: Request a new code via Step 3 and try again
  </Accordion>

  <Accordion title="Phone Number Mismatch">
    ```json theme={null}
    {
      "message": "Phone number does not match verification session"
    }
    ```

    **Resolution**: Ensure phone number and country code exactly match Step 3
  </Accordion>
</AccordionGroup>

## Step 5: KYC Verification (Veriff)

Generate a verification session URL for identity verification. This step initiates the KYC process where users upload their government ID and complete biometric verification.

<Note>
  **No Authentication Required**: This endpoint uses `onboardingId` for validation and does not require a bearer token. The user doesn't have an access token yet at this stage of registration.
</Note>

### Endpoint

```bash theme={null}
POST /v1/auth/register/verification
```

**API Reference:** [`POST /v1/auth/register/verification`](/api-reference/auth/register-verification)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/verification \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/verification', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      onboardingId: sessionStorage.getItem('onboardingId')
    })
  });

  const { sessionUrl } = await response.json();

  // Redirect user to Veriff
  window.location.href = sessionUrl;
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/verification',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'onboardingId': onboarding_id
      }
  )

  data = response.json()
  session_url = data['sessionUrl']

  print(f"Redirect user to: {session_url}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "sessionUrl": "https://magic.veriff.me/v/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

### Field Descriptions

| Field          | Type   | Required | Description               |
| -------------- | ------ | -------- | ------------------------- |
| `onboardingId` | string | Yes      | Onboarding ID from Step 2 |

### What Happens Next

After receiving the `sessionUrl`:

1. **Redirect the user** to the Veriff session URL
2. **User completes verification** by:
   * Uploading a government-issued ID (passport, driver's license, etc.)
   * Taking a selfie for biometric matching
   * Following on-screen instructions
3. **Verification processing** (typically 5-30 minutes)
4. **User returns** to your application
5. **Poll for status** using `GET /v1/auth/register?onboardingId={id}` to check verification state

### Verification States

The user's `verificationState` will be updated to:

* **PENDING**: Verification submitted and under review
* **VERIFIED**: Verification approved (ready to proceed)
* **REJECTED**: Verification rejected (can retry later)

### Polling for Completion

Implement polling to detect when verification completes:

```typescript theme={null}
async function waitForVerification(onboardingId: string): Promise<boolean> {
  const maxAttempts = 60; // 5 minutes

  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(
      `https://dev.api.baanx.com/v1/auth/register?onboardingId=${onboardingId}`,
      {
        headers: { 'x-client-key': 'YOUR_CLIENT_KEY' }
      }
    );

    const user = await response.json();

    if (user.verificationState === 'VERIFIED' || user.verificationState === 'PENDING') {
      return true;
    } else if (user.verificationState === 'REJECTED') {
      throw new Error('Verification rejected');
    }

    await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
  }

  throw new Error('Verification timeout');
}
```

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Onboarding ID">
    ```json theme={null}
    {
      "message": "Invalid onboarding ID"
    }
    ```

    **Resolution**: Verify the onboardingId from Step 2 is correct and hasn't expired
  </Accordion>

  <Accordion title="Session Generation Failed">
    ```json theme={null}
    {
      "message": "Failed to generate verification session"
    }
    ```

    **Resolution**: Verification provider may be temporarily unavailable. Retry after a brief delay
  </Accordion>
</AccordionGroup>

<Tip>
  **User Experience Tip**: Consider opening the Veriff session in a modal or in-app browser rather than a full redirect. This provides a better user experience and makes it easier to detect when the user returns.
</Tip>

## Step 6: Personal Details

Collect the user's personal information required for KYC compliance.

### Endpoint

```bash theme={null}
POST /v1/auth/register/personal-details
```

**API Reference:** [`POST /v1/auth/register/personal-details`](/api-reference/auth/register-personal-details)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/personal-details \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
      "firstName": "John",
      "lastName": "Doe",
      "countryOfNationality": "GB",
      "dateOfBirth": "1990-01-01"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/personal-details', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      onboardingId: 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
      firstName: 'John',
      lastName: 'Doe',
      countryOfNationality: 'GB',
      dateOfBirth: '1990-01-01'
    })
  });

  const data = await response.json();
  console.log('Personal details updated');
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/personal-details',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'onboardingId': 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
          'firstName': 'John',
          'lastName': 'Doe',
          'countryOfNationality': 'GB',
          'dateOfBirth': '1990-01-01'
      }
  )

  data = response.json()
  print("Personal details updated")
  ```
</CodeGroup>

### Request (US Users)

For users with `countryOfResidence = "US"`, SSN is required:

```json theme={null}
{
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "firstName": "John",
  "lastName": "Doe",
  "countryOfNationality": "US",
  "ssn": "123456789",
  "dateOfBirth": "1990-01-01"
}
```

### Response

```json theme={null}
{
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "user": {
    "id": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
    "firstName": "John",
    "lastName": "Doe",
    "dateOfBirth": "1990-01-01",
    "email": "user@example.com",
    "verificationState": "UNVERIFIED",
    "phoneNumber": "7400846282",
    "phoneCountryCode": "+44",
    "countryOfNationality": "GB"
  }
}
```

### Field Descriptions

| Field                  | Type   | Required    | Description                                  |
| ---------------------- | ------ | ----------- | -------------------------------------------- |
| `onboardingId`         | string | Yes         | Onboarding ID from Step 2                    |
| `firstName`            | string | Yes         | User's legal first name                      |
| `lastName`             | string | Yes         | User's legal last name                       |
| `countryOfNationality` | string | Yes         | ISO 3166-1 alpha-2 country code              |
| `dateOfBirth`          | string | Yes         | Format: YYYY-MM-DD (must be 18+ years old)   |
| `ssn`                  | string | Conditional | Required only if `countryOfResidence = "US"` |

<Warning>
  **US Users**: The `ssn` field is mandatory when `countryOfResidence = "US"`. Requests will fail validation without it.
</Warning>

### Age Validation

Users must meet minimum age requirements (typically 18+). The API will validate the `dateOfBirth` against this requirement.

### Common Errors

<AccordionGroup>
  <Accordion title="User Too Young">
    ```json theme={null}
    {
      "message": "User must be at least 18 years old"
    }
    ```

    **Resolution**: Verify date of birth is correct. Users under minimum age cannot register
  </Accordion>

  <Accordion title="Missing SSN (US Users)">
    ```json theme={null}
    {
      "message": "SSN is required for US residents"
    }
    ```

    **Resolution**: Include `ssn` field when `countryOfResidence = "US"`
  </Accordion>
</AccordionGroup>

## Step 7: Physical Address

Collect the user's residential address information.

### Endpoint

```bash theme={null}
POST /v1/auth/register/address
```

**API Reference:** [`POST /v1/auth/register/address`](/api-reference/auth/register-address)

### Request (Non-US Users)

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/address \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
      "addressLine1": "23 Werrington Bridge Rd",
      "addressLine2": "Milking Nook",
      "city": "Peterborough",
      "zip": "PE6 7PP",
      "isSameMailingAddress": true
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/address', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      onboardingId: 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
      addressLine1: '23 Werrington Bridge Rd',
      addressLine2: 'Milking Nook',
      city: 'Peterborough',
      zip: 'PE6 7PP',
      isSameMailingAddress: true
    })
  });

  const data = await response.json();
  console.log('Access Token:', data.accessToken);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/address',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'onboardingId': 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
          'addressLine1': '23 Werrington Bridge Rd',
          'addressLine2': 'Milking Nook',
          'city': 'Peterborough',
          'zip': 'PE6 7PP',
          'isSameMailingAddress': True
      }
  )

  data = response.json()
  print(f"Access Token: {data['accessToken']}")
  ```
</CodeGroup>

### Request (US Users)

For US users, include the `usState` field:

```json theme={null}
{
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "addressLine1": "123 Main Street",
  "addressLine2": "Apt 4B",
  "city": "San Francisco",
  "zip": "94102",
  "usState": "CA",
  "isSameMailingAddress": true
}
```

### Response (Registration Complete)

When `isSameMailingAddress = true` OR `countryOfResidence != "US"`:

```json theme={null}
{
  "accessToken": "US_100a99cf-f4d3-4fa1-c321-2e9833440ebb",
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "user": {
    "id": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
    "firstName": "John",
    "lastName": "Doe",
    "dateOfBirth": "1990-01-01",
    "email": "user@example.com",
    "verificationState": "UNVERIFIED",
    "phoneNumber": "7400846282",
    "phoneCountryCode": "+44",
    "addressLine1": "23 Werrington Bridge Rd",
    "addressLine2": "Milking Nook",
    "city": "Peterborough",
    "zip": "PE6 7PP",
    "countryOfResidence": "GB"
  }
}
```

<Info>
  **Registration Complete**: When `accessToken` is returned, registration is complete. Store the token for authenticated API calls.
</Info>

### Response (Mailing Address Required)

When `isSameMailingAddress = false` AND `countryOfResidence = "US"`:

```json theme={null}
{
  "accessToken": null,
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "user": {
    // ... user details
  }
}
```

<Note>
  If `accessToken` is `null`, proceed to Step 8 to collect mailing address (US users only).
</Note>

### Field Descriptions

| Field                  | Type    | Required    | Description                                                                                       |
| ---------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------- |
| `onboardingId`         | string  | Yes         | Onboarding ID from Step 2                                                                         |
| `addressLine1`         | string  | Yes         | Primary address line (street address)                                                             |
| `addressLine2`         | string  | No          | Secondary address line (apartment, suite, etc.)                                                   |
| `city`                 | string  | Yes         | City or town                                                                                      |
| `zip`                  | string  | Yes         | Postal/ZIP code                                                                                   |
| `usState`              | string  | Conditional | Required only if `countryOfResidence = "US"`. Two-letter state code (e.g., "CA", "NY")            |
| `isSameMailingAddress` | boolean | Yes         | Must be `true` for non-US users. For US users, set to `false` to collect separate mailing address |

<Warning>
  **US Users**: The `usState` field is mandatory when `countryOfResidence = "US"`. Use two-letter state abbreviations (e.g., "CA", "NY", "TX").
</Warning>

### Common Errors

<AccordionGroup>
  <Accordion title="Missing US State">
    ```json theme={null}
    {
      "message": "US state is required for US residents"
    }
    ```

    **Resolution**: Include `usState` field when `countryOfResidence = "US"`
  </Accordion>

  <Accordion title="Invalid ZIP Code">
    ```json theme={null}
    {
      "message": "Invalid ZIP code format"
    }
    ```

    **Resolution**: Ensure ZIP code matches the expected format for the country
  </Accordion>
</AccordionGroup>

## Step 8: Mailing Address (Optional - US Only)

For US users who have a different mailing address than their physical address, collect the mailing address information.

<Note>
  This step is **only required** when:

  * `countryOfResidence = "US"`, AND
  * `isSameMailingAddress = false` in Step 7
</Note>

### Endpoint

```bash theme={null}
POST /v1/auth/register/mailing-address
```

**API Reference:** [`POST /v1/auth/register/mailing-address`](/api-reference/auth/register-mailing-address)

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dev.api.baanx.com/v1/auth/register/mailing-address \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "x-us-env: true" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
      "mailingAddressLine1": "PO Box 12345",
      "mailingAddressLine2": "",
      "mailingCity": "Los Angeles",
      "mailingZip": "90001",
      "mailingUsState": "CA"
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  const response = await fetch('https://dev.api.baanx.com/v1/auth/register/mailing-address', {
    method: 'POST',
    headers: {
      'x-client-key': 'YOUR_CLIENT_KEY',
      'x-us-env': 'true',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      onboardingId: 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
      mailingAddressLine1: 'PO Box 12345',
      mailingAddressLine2: '',
      mailingCity: 'Los Angeles',
      mailingZip: '90001',
      mailingUsState: 'CA'
    })
  });

  const data = await response.json();
  console.log('Access Token:', data.accessToken);
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://dev.api.baanx.com/v1/auth/register/mailing-address',
      headers={
          'x-client-key': 'YOUR_CLIENT_KEY',
          'x-us-env': 'true',
          'Content-Type': 'application/json'
      },
      json={
          'onboardingId': 'US_100a99cf-f4d3-4fa1-c123-2e9833440ebb',
          'mailingAddressLine1': 'PO Box 12345',
          'mailingAddressLine2': '',
          'mailingCity': 'Los Angeles',
          'mailingZip': '90001',
          'mailingUsState': 'CA'
      }
  )

  data = response.json()
  print(f"Access Token: {data['accessToken']}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "accessToken": "US_100a99cf-f4d3-4fa1-c321-2e9833440ebb",
  "user": {
    "id": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
    "email": "user@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "verificationState": "UNVERIFIED",
    "addressLine1": "123 Main Street",
    "city": "San Francisco",
    "zip": "94102",
    "usState": "CA",
    "mailingAddressLine1": "PO Box 12345",
    "mailingCity": "Los Angeles",
    "mailingZip": "90001",
    "mailingUsState": "CA"
  }
}
```

<Info>
  **Registration Complete**: The `accessToken` returned here completes the registration process. Store it for authenticated API calls.
</Info>

### Field Descriptions

| Field                 | Type   | Required | Description                               |
| --------------------- | ------ | -------- | ----------------------------------------- |
| `onboardingId`        | string | Yes      | Onboarding ID from Step 2                 |
| `mailingAddressLine1` | string | Yes      | Primary mailing address line              |
| `mailingAddressLine2` | string | No       | Secondary mailing address line            |
| `mailingCity`         | string | Yes      | Mailing address city                      |
| `mailingZip`          | string | Yes      | Mailing address ZIP code                  |
| `mailingUsState`      | string | Yes      | Two-letter state code for mailing address |

## Step 9: Consent Management (Required for Production)

After core registration completes (Steps 1-8), create a formal consent record for regulatory compliance. This step is **essential for production** deployments handling user data under GDPR, CCPA, or E-Sign Act.

<Warning>
  **Why This Step Matters:** Step 9 creates a formal, immutable audit trail required for regulatory compliance. This provides:

  * Legal proof of consent with timestamps
  * IP address and user agent tracking
  * Complete consent change history
  * Region-specific policy enforcement (US vs Global)
  * Revocation tracking for "right to be forgotten" requests
</Warning>

<Warning>
  **E-Sign Act Legal Requirement (US Users):** For US users, the E-Sign Act **legally requires** that you present the E-Sign Act disclosure document to users and provide an opportunity to review it **before** they can consent to `eSignAct`. This is not optional - it's a federal legal requirement.

  Your client application must:

  * Display the full E-Sign Act disclosure document
  * Allow users to read and review it before proceeding
  * Obtain affirmative consent after presentation

  Simply showing a checkbox labeled "I agree to E-Sign Act" without providing access to the disclosure document does not meet legal requirements and could invalidate the consent.

  **Best Practice for Other Consents:** While not legally mandated in the same way, it's also good practice to provide access to Terms of Service, Privacy Policy, and Marketing Communications policies before collecting those consents.
</Warning>

### Workflow

The consent flow requires two API calls:

1. **Create onboarding consent** - Create consent set using the `onboardingId` from registration
2. **Link user to consent** - Associate the permanent `userId` with the consent set

### Endpoint 1: Create Onboarding Consent

```bash theme={null}
POST /v2/consent/onboarding
```

**API Reference:** [`POST /v2/consent/onboarding`](/api-reference/consent/create-onboarding-consent)

### Request

<CodeGroup>
  ```bash cURL (US User) theme={null}
  curl -X POST https://prod.com/v2/consent/onboarding \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
      "policy": "us",
      "consents": {
        "eSignAct": "granted",
        "termsAndPrivacy": "granted",
        "marketingNotifications": "granted",
        "smsNotifications": "granted",
        "emailNotifications": "granted"
      },
      "metadata": {
        "ipAddress": "192.168.1.1",
        "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)",
        "timestamp": "2024-01-15T10:30:00Z"
      }
    }'
  ```

  ```bash cURL (Global User) theme={null}
  curl -X POST https://prod.com/v2/consent/onboarding \
    -H "x-client-key: YOUR_CLIENT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
      "policy": "global",
      "consents": {
        "termsAndPrivacy": "granted",
        "marketingNotifications": "granted",
        "smsNotifications": "granted",
        "emailNotifications": "granted"
      },
      "metadata": {
        "ipAddress": "82.45.12.201",
        "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
        "timestamp": "2024-01-15T10:30:00Z"
      }
    }'
  ```

  ```javascript JavaScript/TypeScript theme={null}
  async function createConsentRecord(
    onboardingId: string,
    userId: string,
    userCountry: string,
    consentChoices: {
      allowMarketing: boolean;
      allowSms: boolean;
    }
  ): Promise<void> {
    const isUSUser = userCountry === 'US';

    const consents = {
      termsAndPrivacy: 'granted',
      marketingNotifications: consentChoices.allowMarketing ? 'granted' : 'denied',
      smsNotifications: consentChoices.allowSms ? 'granted' : 'denied',
      emailNotifications: consentChoices.allowMarketing ? 'granted' : 'denied'
    };

    if (isUSUser) {
      consents['eSignAct'] = 'granted';
    }

    const metadata = {
      ipAddress: window.location.hostname,
      userAgent: navigator.userAgent,
      timestamp: new Date().toISOString()
    };

    const createResponse = await fetch('https://prod.com/v2/consent/onboarding', {
      method: 'POST',
      headers: {
        'x-client-key': 'YOUR_CLIENT_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        onboardingId,
        policy: isUSUser ? 'us' : 'global',
        consents,
        metadata
      })
    });

    if (!createResponse.ok) {
      throw new Error(`Consent creation failed: ${createResponse.statusText}`);
    }

    const { consentSetId } = await createResponse.json();

    const linkResponse = await fetch(
      `https://prod.com/v2/consent/onboarding/${consentSetId}`,
      {
        method: 'PATCH',
        headers: {
          'x-client-key': 'YOUR_CLIENT_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ userId })
      }
    );

    if (!linkResponse.ok) {
      throw new Error(`User linking failed: ${linkResponse.statusText}`);
    }

    console.log('Consent record established with audit trail');
  }
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": "consent_set_123abc",
  "consentSetId": "consent_set_123abc",
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "policy": "us",
  "consents": [
    {
      "id": "consent_456def",
      "type": "eSignAct",
      "status": "granted",
      "metadata": {
        "ipAddress": "192.168.1.1",
        "userAgent": "Mozilla/5.0...",
        "timestamp": "2024-01-15T10:30:00Z"
      },
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ]
}
```

<Note>
  Store the `consentSetId` returned from this call. You'll need it for the linking step.
</Note>

### Endpoint 2: Link User to Consent Set

```bash theme={null}
PATCH /v2/consent/onboarding/{consentSetId}
```

**API Reference:** [`PATCH /v2/consent/onboarding/{consentSetId}`](/api-reference/consent/link-user-to-consent)

### Request

```bash theme={null}
curl -X PATCH https://prod.com/v2/consent/onboarding/consent_set_123abc \
  -H "x-client-key: YOUR_CLIENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb"
  }'
```

### Response

```json theme={null}
{
  "id": "consent_set_123abc",
  "consentSetId": "consent_set_123abc",
  "userId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "onboardingId": "US_100a99cf-f4d3-4fa1-c123-2e9833440ebb",
  "policy": "us",
  "consents": [
    {
      "id": "consent_456def",
      "type": "eSignAct",
      "status": "granted",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ]
}
```

<Info>
  **Registration Complete with Audit Trail**: Once the user is linked to the consent set, your registration flow is complete with full regulatory compliance. The consent record includes immutable audit trails for all future changes.
</Info>

### Policy Selection

Choose the correct policy based on the user's country of residence:

| User Location | Policy   | Required Consents                                                                       |
| ------------- | -------- | --------------------------------------------------------------------------------------- |
| United States | `us`     | eSignAct, termsAndPrivacy, marketingNotifications, smsNotifications, emailNotifications |
| International | `global` | termsAndPrivacy, marketingNotifications, smsNotifications, emailNotifications           |

<Note>
  **E-Sign Act Disclosure for US Users:** The `eSignAct` consent is required for US users under the federal Electronic Signatures in Global and National Commerce Act. Your client application **must present the E-Sign Act disclosure document** to users and provide an opportunity to review it before they can consent. The disclosure typically explains:

  * What it means to conduct business electronically
  * How to withdraw consent
  * How to obtain paper copies of documents
  * Hardware and software requirements
  * How to contact the company

  This is a legal requirement - users cannot legally consent to something they haven't been given the opportunity to read.
</Note>

<Tip>
  **Mapping Registration to Consent:** The consent choices from Step 2 (`allowMarketing`, `allowSms`) should be used to set the status values in Step 9:

  * `allowMarketing: true` → `marketingNotifications: "granted"`, `emailNotifications: "granted"`
  * `allowMarketing: false` → `marketingNotifications: "denied"`, `emailNotifications: "denied"`
  * `allowSms: true` → `smsNotifications: "granted"`
  * `allowSms: false` → `smsNotifications: "denied"`
  * `termsAndPrivacy` should always be `"granted"` (required to register)
  * `eSignAct` (US only) should always be `"granted"` (required for US users)
</Tip>

### Common Errors

<AccordionGroup>
  <Accordion title="Invalid Policy Type">
    ```json theme={null}
    {
      "message": "Policy must be 'us' or 'global'"
    }
    ```

    **Resolution**: Ensure policy matches user's country (`us` for US, `global` for others)
  </Accordion>

  <Accordion title="Missing Required Consent">
    ```json theme={null}
    {
      "message": "US policy requires eSignAct consent"
    }
    ```

    **Resolution**: Include `eSignAct` consent when using `policy: "us"`
  </Accordion>

  <Accordion title="Invalid Onboarding ID">
    ```json theme={null}
    {
      "message": "Onboarding ID not found or already used"
    }
    ```

    **Resolution**: Verify the `onboardingId` from Step 2 and ensure it hasn't been used before
  </Accordion>

  <Accordion title="User Already Linked">
    ```json theme={null}
    {
      "message": "Consent set already linked to a user"
    }
    ```

    **Resolution**: Cannot link the same consent set to multiple users. Create a new consent set for each user.
  </Accordion>
</AccordionGroup>

### Next Steps After Consent

Once consent is established, you can:

* **View consent status**: [`GET /v2/consent/user/{userId}`](/api-reference/consent/get-user-consent)
* **Access audit trail**: [`GET /v2/consent/user/{userId}/audit`](/api-reference/consent/get-consent-audit)
* **Allow revocation**: [`DELETE /v2/consent/consentSet/{id}/consent/{id}`](/api-reference/consent/revoke-consent)

See the complete [Consent Management Guide](/guides/consent/overview) for details on audit trails, revocation, and compliance best practices.

## Complete Registration Example

Here's a complete end-to-end example implementing all registration steps:

<CodeGroup>
  ```javascript JavaScript/TypeScript theme={null}
  class RegistrationFlow {
    private apiBase = 'https://dev.api.baanx.com';
    private clientKey = 'YOUR_CLIENT_KEY';

    async sendEmailCode(email: string): Promise<string> {
      const response = await fetch(`${this.apiBase}/v1/auth/register/email/send`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ email })
      });

      if (!response.ok) {
        throw new Error(`Email send failed: ${response.statusText}`);
      }

      const data = await response.json();
      return data.contactVerificationId;
    }

    async verifyEmail(
      email: string,
      password: string,
      code: string,
      contactVerificationId: string,
      countryOfResidence: string
    ): Promise<string> {
      const response = await fetch(`${this.apiBase}/v1/auth/register/email/verify`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          email,
          password,
          verificationCode: code,
          contactVerificationId,
          countryOfResidence,
          allowMarketing: true,
          allowSms: true
        })
      });

      if (!response.ok) {
        throw new Error(`Email verification failed: ${response.statusText}`);
      }

      const data = await response.json();
      return data.onboardingId;
    }

    async sendPhoneCode(
      phoneCountryCode: string,
      phoneNumber: string,
      contactVerificationId: string
    ): Promise<void> {
      const response = await fetch(`${this.apiBase}/v1/auth/register/phone/send`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          phoneCountryCode,
          phoneNumber,
          contactVerificationId
        })
      });

      if (!response.ok) {
        throw new Error(`Phone send failed: ${response.statusText}`);
      }
    }

    async verifyPhone(
      phoneCountryCode: string,
      phoneNumber: string,
      code: string,
      onboardingId: string
    ): Promise<void> {
      const response = await fetch(`${this.apiBase}/v1/auth/register/phone/verify`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          phoneCountryCode,
          phoneNumber,
          verificationCode: code,
          onboardingId
        })
      });

      if (!response.ok) {
        throw new Error(`Phone verification failed: ${response.statusText}`);
      }
    }

    async submitPersonalDetails(
      onboardingId: string,
      firstName: string,
      lastName: string,
      dateOfBirth: string,
      countryOfNationality: string,
      ssn?: string
    ): Promise<void> {
      const body: any = {
        onboardingId,
        firstName,
        lastName,
        dateOfBirth,
        countryOfNationality
      };

      if (ssn) {
        body.ssn = ssn;
      }

      const response = await fetch(`${this.apiBase}/v1/auth/register/personal-details`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(body)
      });

      if (!response.ok) {
        throw new Error(`Personal details failed: ${response.statusText}`);
      }
    }

    async submitAddress(
      onboardingId: string,
      addressLine1: string,
      city: string,
      zip: string,
      isSameMailingAddress: boolean,
      addressLine2?: string,
      usState?: string
    ): Promise<string | null> {
      const body: any = {
        onboardingId,
        addressLine1,
        city,
        zip,
        isSameMailingAddress
      };

      if (addressLine2) body.addressLine2 = addressLine2;
      if (usState) body.usState = usState;

      const response = await fetch(`${this.apiBase}/v1/auth/register/address`, {
        method: 'POST',
        headers: {
          'x-client-key': this.clientKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(body)
      });

      if (!response.ok) {
        throw new Error(`Address submission failed: ${response.statusText}`);
      }

      const data = await response.json();
      return data.accessToken;
    }

    async completeRegistration() {
      try {
        const contactVerificationId = await this.sendEmailCode('user@example.com');
        console.log('Email code sent');

        const emailCode = '123456';
        const onboardingId = await this.verifyEmail(
          'user@example.com',
          'SecurePassword123!',
          emailCode,
          contactVerificationId,
          'GB'
        );
        console.log('Email verified, onboardingId:', onboardingId);

        await this.sendPhoneCode('+44', '7400846282', contactVerificationId);
        console.log('Phone code sent');

        const phoneCode = '123456';
        await this.verifyPhone('+44', '7400846282', phoneCode, onboardingId);
        console.log('Phone verified');

        await this.submitPersonalDetails(
          onboardingId,
          'John',
          'Doe',
          '1990-01-01',
          'GB'
        );
        console.log('Personal details submitted');

        const accessToken = await this.submitAddress(
          onboardingId,
          '23 Werrington Bridge Rd',
          'Peterborough',
          'PE6 7PP',
          true,
          'Milking Nook'
        );
        console.log('Registration complete! Access token:', accessToken);

        return accessToken;
      } catch (error) {
        console.error('Registration failed:', error);
        throw error;
      }
    }
  }

  const registration = new RegistrationFlow();
  registration.completeRegistration();
  ```

  ```python Python theme={null}
  import requests
  from typing import Optional

  class RegistrationFlow:
      def __init__(self, api_base: str, client_key: str):
          self.api_base = api_base
          self.client_key = client_key
          self.headers = {
              'x-client-key': client_key,
              'Content-Type': 'application/json'
          }

      def send_email_code(self, email: str) -> str:
          response = requests.post(
              f'{self.api_base}/v1/auth/register/email/send',
              headers=self.headers,
              json={'email': email}
          )
          response.raise_for_status()
          return response.json()['contactVerificationId']

      def verify_email(
          self,
          email: str,
          password: str,
          code: str,
          contact_verification_id: str,
          country_of_residence: str
      ) -> str:
          response = requests.post(
              f'{self.api_base}/v1/auth/register/email/verify',
              headers=self.headers,
              json={
                  'email': email,
                  'password': password,
                  'verificationCode': code,
                  'contactVerificationId': contact_verification_id,
                  'countryOfResidence': country_of_residence,
                  'allowMarketing': True,
                  'allowSms': True
              }
          )
          response.raise_for_status()
          return response.json()['onboardingId']

      def send_phone_code(
          self,
          phone_country_code: str,
          phone_number: str,
          contact_verification_id: str
      ) -> None:
          response = requests.post(
              f'{self.api_base}/v1/auth/register/phone/send',
              headers=self.headers,
              json={
                  'phoneCountryCode': phone_country_code,
                  'phoneNumber': phone_number,
                  'contactVerificationId': contact_verification_id
              }
          )
          response.raise_for_status()

      def verify_phone(
          self,
          phone_country_code: str,
          phone_number: str,
          code: str,
          onboarding_id: str
      ) -> None:
          response = requests.post(
              f'{self.api_base}/v1/auth/register/phone/verify',
              headers=self.headers,
              json={
                  'phoneCountryCode': phone_country_code,
                  'phoneNumber': phone_number,
                  'verificationCode': code,
                  'onboardingId': onboarding_id
              }
          )
          response.raise_for_status()

      def submit_personal_details(
          self,
          onboarding_id: str,
          first_name: str,
          last_name: str,
          date_of_birth: str,
          country_of_nationality: str,
          ssn: Optional[str] = None
      ) -> None:
          body = {
              'onboardingId': onboarding_id,
              'firstName': first_name,
              'lastName': last_name,
              'dateOfBirth': date_of_birth,
              'countryOfNationality': country_of_nationality
          }

          if ssn:
              body['ssn'] = ssn

          response = requests.post(
              f'{self.api_base}/v1/auth/register/personal-details',
              headers=self.headers,
              json=body
          )
          response.raise_for_status()

      def submit_address(
          self,
          onboarding_id: str,
          address_line1: str,
          city: str,
          zip_code: str,
          is_same_mailing_address: bool,
          address_line2: Optional[str] = None,
          us_state: Optional[str] = None
      ) -> Optional[str]:
          body = {
              'onboardingId': onboarding_id,
              'addressLine1': address_line1,
              'city': city,
              'zip': zip_code,
              'isSameMailingAddress': is_same_mailing_address
          }

          if address_line2:
              body['addressLine2'] = address_line2
          if us_state:
              body['usState'] = us_state

          response = requests.post(
              f'{self.api_base}/v1/auth/register/address',
              headers=self.headers,
              json=body
          )
          response.raise_for_status()
          return response.json().get('accessToken')

      def complete_registration(self) -> str:
          try:
              contact_verification_id = self.send_email_code('user@example.com')
              print('Email code sent')

              email_code = '123456'
              onboarding_id = self.verify_email(
                  'user@example.com',
                  'SecurePassword123!',
                  email_code,
                  contact_verification_id,
                  'GB'
              )
              print(f'Email verified, onboardingId: {onboarding_id}')

              self.send_phone_code('+44', '7400846282', contact_verification_id)
              print('Phone code sent')

              phone_code = '123456'
              self.verify_phone('+44', '7400846282', phone_code, onboarding_id)
              print('Phone verified')

              self.submit_personal_details(
                  onboarding_id,
                  'John',
                  'Doe',
                  '1990-01-01',
                  'GB'
              )
              print('Personal details submitted')

              access_token = self.submit_address(
                  onboarding_id,
                  '23 Werrington Bridge Rd',
                  'Peterborough',
                  'PE6 7PP',
                  True,
                  'Milking Nook'
              )
              print(f'Registration complete! Access token: {access_token}')

              return access_token
          except requests.exceptions.HTTPError as error:
              print(f'Registration failed: {error}')
              raise

  registration = RegistrationFlow(
      'https://dev.api.baanx.com',
      'YOUR_CLIENT_KEY'
  )
  access_token = registration.complete_registration()
  ```
</CodeGroup>

## Troubleshooting

### Session Expiration

Onboarding sessions may expire if too much time passes between steps. If you receive an "invalid onboarding ID" error:

1. Check if the session has expired (typically 30-60 minutes)
2. Restart the registration flow from Step 1
3. Complete all steps within the session timeout window

### Verification Code Issues

If users report not receiving codes:

1. **Email**: Check spam/junk folders, verify email address is correct
2. **SMS**: Verify phone number format, ensure number can receive SMS, check for carrier blocks

### Data Validation Failures

Common validation errors and solutions:

* **Invalid email format**: Ensure proper email validation on client side
* **Weak password**: Implement password strength checker matching server requirements
* **Invalid date format**: Use YYYY-MM-DD format consistently
* **Invalid country code**: Use ISO 3166-1 alpha-2 codes (2-letter codes)
* **Phone number format**: Remove spaces, dashes, and formatting characters

## Best Practices

<CardGroup cols={2}>
  <Card title="Client-Side Validation" icon="check">
    Validate all fields on the client side before API submission to reduce errors and improve UX
  </Card>

  <Card title="Progress Tracking" icon="list-check">
    Show users clear progress indicators through the 8-step flow so they know where they are
  </Card>

  <Card title="Error Messages" icon="triangle-exclamation">
    Provide clear, actionable error messages. Don't just show API errors—explain what the user should do
  </Card>

  <Card title="Session Management" icon="clock">
    Store onboardingId securely and implement timeout warnings to prevent session expiration
  </Card>

  <Card title="Code Resend" icon="rotate">
    Implement "resend code" functionality for both email and phone verification with rate limiting
  </Card>

  <Card title="Field Persistence" icon="floppy-disk">
    Save form progress locally so users don't lose data if they navigate away or refresh
  </Card>
</CardGroup>

## Next Steps

After successful registration (including Step 9 consent):

1. **✅ Store Access Token**: Securely store the returned `accessToken` for authenticated API calls (6-hour validity)
2. **✅ Consent Established**: Your consent record with audit trail is now active. Users can manage consent via their profile
3. **Identity Verification**: Direct users to complete identity verification via [`GET /v1/user/verification`](/api-reference/user/verification)
4. **Profile Management**: Allow users to view and update their profile via [Profile Management](/guides/user/profile)

<Warning>
  **Production Checklist**: Ensure Step 9 (Consent Management) is implemented before deploying to production. Missing consent records can result in regulatory compliance issues.
</Warning>

<CardGroup cols={3}>
  <Card title="Consent Management" icon="clipboard-check" href="/guides/consent/overview">
    Learn about consent audit trails, revocation, and compliance
  </Card>

  <Card title="Authentication Guide" icon="lock" href="/guides/user/authentication">
    Learn about login flows and session management
  </Card>

  <Card title="Profile Management" icon="user-gear" href="/guides/user/profile">
    Managing user information after registration
  </Card>
</CardGroup>
