> ## 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.

# Implementation Guide

> Step-by-step guide to integrating consent management into your application

## Integration Overview

This guide walks through implementing the complete consent management workflow in your application. We'll cover collecting consent during onboarding, linking users, checking status, and handling revocations.

<Note>
  All examples use the **US policy** with 5 required consents (including `eSignAct` for E-Sign Act compliance). For Global policy, exclude the `eSignAct` consent.
</Note>

## Prerequisites

Before implementing consent management, ensure you have:

* Baanx API credentials (`x-client-key` and `x-secret-key`)
* User registration flow in your application
* Ability to track temporary identifiers before user creation

## Step 1: Collect Consent During Onboarding

Create a consent set during user registration **before** address submission finalizes the registration.

### Use Onboarding ID from Registration

The `onboardingId` is obtained from the email verification step of the [user registration flow](/guides/user/registration):

```typescript theme={null}
// Step 2 of registration: Email verification returns onboardingId
const { onboardingId } = await fetch('/v1/auth/register/email/verify', {
  method: 'POST',
  headers: { 'x-client-key': 'YOUR_CLIENT_KEY' },
  body: JSON.stringify({
    email: 'user@example.com',
    password: 'SecurePassword123!',
    verificationCode: '123456',
    contactVerificationId: verificationId,
    countryOfResidence: 'US'
  })
}).then(res => res.json());

// Use this onboardingId for consent creation (Step 4 of registration)
// Example onboardingId: "100a99cf-f4d3-4fa1-9be9-2e9828b20ebb"
```

<Warning>
  **Do not generate a new onboardingId** - always use the `onboardingId` returned from email verification (`POST /v1/auth/register/email/verify`). This ID links the consent to the user's registration session.
</Warning>

### Create Consent Set

```typescript theme={null}
const response = await fetch('https://api.baanx.com/v2/consent/onboarding', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-client-key': 'your_client_key',
    'x-secret-key': 'your_secret_key'
  },
  body: JSON.stringify({
    onboardingId: onboardingId,
    tenantId: 'tenant_baanx_prod',
    policyType: 'US',
    consents: [
      {
        consentType: 'eSignAct',
        consentStatus: 'granted'
      },
      {
        consentType: 'termsAndPrivacy',
        consentStatus: 'granted'
      },
      {
        consentType: 'marketingNotifications',
        consentStatus: 'granted'
      },
      {
        consentType: 'smsNotifications',
        consentStatus: 'denied'
      },
      {
        consentType: 'emailNotifications',
        consentStatus: 'granted'
      }
    ],
    metadata: {
      ipAddress: '192.168.1.1',
      userAgent: navigator.userAgent,
      timestamp: new Date().toISOString(),
      clientId: 'web-app-v1.2.0'
    }
  })
});

const { consentSetId } = await response.json();
// Store this consentSetId for linking later
```

### Response

```json theme={null}
{
  "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
  "onboardingId": "onboarding_abc123xyz",
  "tenantId": "tenant_baanx_prod",
  "createdAt": "2024-01-15T10:30:00Z",
  "_links": {
    "self": {
      "href": "https://api.baanx.com/v2/consent/consentSet/550e8400-e29b-41d4-a716-446655440001",
      "method": "GET"
    }
  }
}
```

### Validation Rules

The API validates that all required consents are present based on policy type:

| Policy Type | Required Consents                                            |
| ----------- | ------------------------------------------------------------ |
| `US`        | All 5 types (including `eSignAct` for E-Sign Act compliance) |
| `global`    | 4 types (excludes `eSignAct`)                                |

<Info>
  Consents can have `granted` or `denied` status during creation. Both are valid, but the user's overall consent status will be `incomplete` if required consents are denied.
</Info>

## Step 2: Link User to Consent Set

After address submission finalizes registration and returns a permanent `userId`, link it to the consent set created in Step 1.

### When to Link

Call this endpoint after:

* ✅ Physical address submitted (`POST /v1/auth/register/address`) returns `userId` and `accessToken`
* ✅ OR mailing address submitted (`POST /v1/auth/register/mailing-address`) returns `userId` and `accessToken` (US users only)
* ✅ Registration is complete and you have the `userId`

<Note>
  The address endpoints finalize registration. Non-US users and US users with same mailing address get `userId` from the address endpoint. US users with different mailing addresses get `userId` from the mailing address endpoint.
</Note>

### Link Request

```typescript theme={null}
const response = await fetch(
  `https://api.baanx.com/v2/consent/onboarding/${consentSetId}`,
  {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'x-client-key': 'your_client_key',
      'x-secret-key': 'your_secret_key'
    },
    body: JSON.stringify({
      userId: 'user_123abc456def'
    })
  }
);

const linkedConsent = await response.json();
```

### Response

```json theme={null}
{
  "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
  "userId": "user_123abc456def",
  "completedAt": "2024-01-15T10:35:00Z",
  "consentSet": {
    "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
    "userId": "user_123abc456def",
    "onboardingId": "onboarding_abc123xyz",
    "tenantId": "tenant_baanx_prod",
    "policyType": "US",
    "completedAt": "2024-01-15T10:35:00Z",
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:35:00Z",
    "consents": [
      {
        "consentId": "consent_001",
        "consentType": "eSignAct",
        "consentStatus": "granted",
        "metadata": {
          "timestamp": "2024-01-15T10:30:00Z",
          "ipAddress": "192.168.1.1"
        },
        "createdAt": "2024-01-15T10:30:00Z",
        "updatedAt": "2024-01-15T10:30:00Z"
      }
    ]
  },
  "_links": {
    "self": {
      "href": "https://api.baanx.com/v2/consent/consentSet/550e8400-e29b-41d4-a716-446655440001",
      "method": "GET"
    },
    "audit": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def/audit",
      "method": "GET"
    }
  }
}
```

<Warning>
  A consent set can only be linked to **one user**. Attempting to link again will result in a 409 Conflict error.
</Warning>

## Step 3: Check Consent Status

Retrieve a user's consent status to validate compliance or gate features.

### Short Status Check (Fast)

Get quick status summary:

```typescript theme={null}
const response = await fetch(
  'https://api.baanx.com/v2/consent/user/user_123abc456def',
  {
    headers: {
      'x-client-key': 'your_client_key'
    }
  }
);

const { userId, consentStatus } = await response.json();
```

**Response:**

```json theme={null}
{
  "userId": "user_123abc456def",
  "consentStatus": "complete",
  "_links": {
    "self": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def",
      "method": "GET"
    },
    "full": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def?full=true",
      "method": "GET"
    },
    "audit": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def/audit",
      "method": "GET"
    }
  }
}
```

### Status Values

| Status       | Meaning                                              | Action                             |
| ------------ | ---------------------------------------------------- | ---------------------------------- |
| `complete`   | All required consents granted, no revocations        | Allow access                       |
| `incomplete` | Missing required consents OR has denials/revocations | Restrict access, prompt re-consent |
| `none`       | No consent sets found                                | Initiate consent collection        |

### Full Consent Details

Get all consent sets with detailed records:

```typescript theme={null}
const response = await fetch(
  'https://api.baanx.com/v2/consent/user/user_123abc456def?full=true',
  {
    headers: {
      'x-client-key': 'your_client_key'
    }
  }
);

const { userId, consentStatus, consentSets } = await response.json();
```

**Use Cases:**

* Detailed consent review pages
* User privacy dashboards
* Compliance reporting
* Consent preference management

## Step 4: Access Audit Trail

Retrieve complete consent change history for compliance reporting.

### Paginated Audit Request

```typescript theme={null}
const response = await fetch(
  'https://api.baanx.com/v2/consent/user/user_123abc456def/audit?limit=50&offset=0',
  {
    headers: {
      'x-client-key': 'your_client_key'
    }
  }
);

const { userId, auditRecords, pagination } = await response.json();
```

### Response

```json theme={null}
{
  "userId": "user_123abc456def",
  "auditRecords": [
    {
      "auditId": "audit_001",
      "action": "created",
      "timestamp": "2024-01-15T10:30:00Z",
      "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
      "changes": {
        "before": null,
        "after": {
          "consentType": "termsAndPrivacy",
          "consentStatus": "granted"
        }
      },
      "metadata": {
        "ipAddress": "192.168.1.1",
        "userAgent": "Mozilla/5.0..."
      }
    },
    {
      "auditId": "audit_002",
      "action": "revoked",
      "timestamp": "2024-01-20T14:22:00Z",
      "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
      "changes": {
        "before": {
          "consentType": "marketingNotifications",
          "consentStatus": "granted"
        },
        "after": {
          "consentType": "marketingNotifications",
          "consentStatus": "revoked"
        }
      }
    }
  ],
  "pagination": {
    "total": 2,
    "limit": 50,
    "offset": 0
  },
  "_links": {
    "self": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def/audit?limit=50&offset=0",
      "method": "GET"
    }
  }
}
```

### Pagination Parameters

| Parameter | Description       | Default |
| --------- | ----------------- | ------- |
| `limit`   | Records per page  | 50      |
| `offset`  | Starting position | 0       |

## Step 5: Revoke Consent

Allow users to withdraw specific consents while maintaining audit trail.

### Revocation Request

```typescript theme={null}
const response = await fetch(
  `https://api.baanx.com/v2/consent/consentSet/${consentSetId}/consent/${consentId}`,
  {
    method: 'DELETE',
    headers: {
      'x-client-key': 'your_client_key',
      'x-secret-key': 'your_secret_key'
    }
  }
);

const revokedConsent = await response.json();
```

### Response

```json theme={null}
{
  "consentId": "consent_new_revoked_001",
  "consentSetId": "550e8400-e29b-41d4-a716-446655440001",
  "consentType": "marketingNotifications",
  "consentStatus": "revoked",
  "revocationTimestamp": "2024-01-20T14:22:00Z",
  "_links": {
    "consentSet": {
      "href": "https://api.baanx.com/v2/consent/consentSet/550e8400-e29b-41d4-a716-446655440001",
      "method": "GET"
    },
    "audit": {
      "href": "https://api.baanx.com/v2/consent/user/user_123abc456def/audit",
      "method": "GET"
    }
  }
}
```

<Info>
  Revocation creates a **new consent record** with `revoked` status. The original consent record is preserved for audit purposes.
</Info>

### Post-Revocation Handling

After revocation:

1. User's overall consent status may become `incomplete`
2. Check consent status to determine access restrictions
3. Prompt user to re-grant consent if needed for service access

## Complete Integration Example

Here's a full TypeScript implementation:

```typescript theme={null}
class ConsentManager {
  private baseUrl = 'https://api.baanx.com';
  private clientKey: string;
  private secretKey: string;

  constructor(clientKey: string, secretKey: string) {
    this.clientKey = clientKey;
    this.secretKey = secretKey;
  }

  async createOnboardingConsent(
    onboardingId: string,
    consents: Array<{ type: string; status: 'granted' | 'denied' }>,
    metadata?: Record<string, any>
  ) {
    const response = await fetch(`${this.baseUrl}/v2/consent/onboarding`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-client-key': this.clientKey,
        'x-secret-key': this.secretKey
      },
      body: JSON.stringify({
        onboardingId,
        tenantId: 'tenant_baanx_prod',
        policyType: 'US',
        consents: consents.map(c => ({
          consentType: c.type,
          consentStatus: c.status
        })),
        metadata: {
          ipAddress: metadata?.ipAddress,
          userAgent: metadata?.userAgent,
          timestamp: new Date().toISOString(),
          ...metadata
        }
      })
    });

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

    return response.json();
  }

  async linkUserToConsent(consentSetId: string, userId: string) {
    const response = await fetch(
      `${this.baseUrl}/v2/consent/onboarding/${consentSetId}`,
      {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
          'x-client-key': this.clientKey,
          'x-secret-key': this.secretKey
        },
        body: JSON.stringify({ userId })
      }
    );

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

    return response.json();
  }

  async getUserConsentStatus(userId: string, full: boolean = false) {
    const url = full
      ? `${this.baseUrl}/v2/consent/user/${userId}?full=true`
      : `${this.baseUrl}/v2/consent/user/${userId}`;

    const response = await fetch(url, {
      headers: {
        'x-client-key': this.clientKey
      }
    });

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

    return response.json();
  }

  async getAuditTrail(userId: string, limit: number = 50, offset: number = 0) {
    const response = await fetch(
      `${this.baseUrl}/v2/consent/user/${userId}/audit?limit=${limit}&offset=${offset}`,
      {
        headers: {
          'x-client-key': this.clientKey
        }
      }
    );

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

    return response.json();
  }

  async revokeConsent(consentSetId: string, consentId: string) {
    const response = await fetch(
      `${this.baseUrl}/v2/consent/consentSet/${consentSetId}/consent/${consentId}`,
      {
        method: 'DELETE',
        headers: {
          'x-client-key': this.clientKey,
          'x-secret-key': this.secretKey
        }
      }
    );

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

    return response.json();
  }
}
```

### Usage Example

```typescript theme={null}
const consentManager = new ConsentManager(
  'your_client_key',
  'your_secret_key'
);

// Get onboardingId from registration email verification (Step 2)
const { onboardingId } = await registrationApi.verifyEmail('user@example.com', '123456');

// Create consent during registration (Step 4 - before address submission)
const { consentSetId } = await consentManager.createOnboardingConsent(
  onboardingId,
  [
    { type: 'eSignAct', status: 'granted' },
    { type: 'termsAndPrivacy', status: 'granted' },
    { type: 'marketingNotifications', status: 'granted' },
    { type: 'smsNotifications', status: 'denied' },
    { type: 'emailNotifications', status: 'granted' }
  ],
  {
    ipAddress: '192.168.1.1',
    userAgent: 'web-app-v1.2.0'
  }
);

// Continue registration: submit address (Step 5)
// Address endpoint finalizes registration and returns userId
const { userId, accessToken } = await registrationApi.submitAddress(onboardingId, addressData);

// Link consent to userId (Step 7)
await consentManager.linkUserToConsent(consentSetId, userId);

// Verify consent status
const { consentStatus } = await consentManager.getUserConsentStatus(userId);

if (consentStatus === 'complete') {
  console.log('Registration complete with consent audit trail');
} else {
  console.log('Consent incomplete - user needs to grant required consents');
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use Registration OnboardingId">
    Always use the `onboardingId` returned from email verification (`POST /v1/auth/register/email/verify`). Do not generate a new ID - the onboardingId links the consent to the registration session.

    ```typescript theme={null}
    // ✅ Correct: Use onboardingId from registration
    const { onboardingId } = await verifyEmail(email, code);
    await createConsent(onboardingId, consents);

    // ❌ Wrong: Do not generate new ID
    // const onboardingId = `onboarding_${uuid()}`;
    ```
  </Accordion>

  <Accordion title="Store ConsentSetId Temporarily">
    Store the `consentSetId` in your session or database between consent creation (Step 4) and linking (Step 7). You'll need it to link the consent after address submission returns the `userId`.
  </Accordion>

  <Accordion title="Capture Rich Metadata">
    Include IP address, user agent, timestamp, and client version in metadata for comprehensive audit trails:

    ```typescript theme={null}
    metadata: {
      ipAddress: req.ip,
      userAgent: req.headers['user-agent'],
      timestamp: new Date().toISOString(),
      clientId: 'mobile-app-ios-v2.1.0',
      sessionId: req.session.id
    }
    ```
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Implement retry logic for network failures and provide clear user feedback for validation errors:

    ```typescript theme={null}
    try {
      await consentManager.createOnboardingConsent(...);
    } catch (error) {
      if (error.status === 409) {
        // Duplicate onboardingId - generate new one
      } else if (error.status === 400) {
        // Validation error - check required consents
      } else {
        // Network error - retry with backoff
      }
    }
    ```
  </Accordion>

  <Accordion title="Use Short Status for Gating">
    For access control and feature gating, use the short status check (no `?full=true`) for optimal performance:

    ```typescript theme={null}
    const { consentStatus } = await consentManager.getUserConsentStatus(userId);
    if (consentStatus !== 'complete') {
      return res.status(403).json({ error: 'Incomplete consent' });
    }
    ```
  </Accordion>

  <Accordion title="Monitor Consent Status Changes">
    Periodically check consent status and notify users when action is needed (e.g., revoked required consents).
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Compliance Guide" icon="clipboard-check" href="/guides/consent/compliance">
    Regulatory requirements and audit strategies
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/guides/consent/troubleshooting">
    Common issues and error handling
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/consent/create-onboarding-consent">
    Complete endpoint documentation
  </Card>

  <Card title="Overview" icon="circle-info" href="/guides/consent/overview">
    Back to consent management overview
  </Card>
</CardGroup>
