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

# Troubleshooting

> Common issues, error codes, and solutions for consent management

## Common Errors

This guide covers common errors you may encounter when working with the Consent Management API and how to resolve them.

## 400 Bad Request

### Missing Required Consents

**Error:**

```json theme={null}
{
  "error": "Validation error",
  "details": [
    "Missing required consent: termsAndPrivacy for policy type: global"
  ]
}
```

**Cause:** Not all required consents for the policy type are present in the request.

**Required Consents by Policy:**

* **US Policy**: `eSignAct` (E-Sign Act compliance), `termsAndPrivacy`, `marketingNotifications`, `smsNotifications`, `emailNotifications`
* **Global Policy**: `termsAndPrivacy`, `marketingNotifications`, `smsNotifications`, `emailNotifications` (excludes `eSignAct`)

**Solution:**

```typescript theme={null}
const requiredConsents = {
  global: [
    'eSignAct',
    'termsAndPrivacy',
    'marketingNotifications',
    'smsNotifications',
    'emailNotifications'
  ],
  US: [
    'termsAndPrivacy',
    'marketingNotifications',
    'smsNotifications',
    'emailNotifications'
  ]
};

function validateConsents(policyType: 'global' | 'US', consents: ConsentItem[]) {
  const required = requiredConsents[policyType];
  const providedTypes = consents.map(c => c.consentType);

  const missing = required.filter(type => !providedTypes.includes(type));

  if (missing.length > 0) {
    throw new Error(`Missing required consents: ${missing.join(', ')}`);
  }

  return true;
}

validateConsents('global', consents);
```

***

### Invalid Consent Type

**Error:**

```json theme={null}
{
  "error": "Validation error",
  "details": [
    "Invalid consentType: 'pushNotifications'. Must be one of: eSignAct, termsAndPrivacy, marketingNotifications, smsNotifications, emailNotifications"
  ]
}
```

**Cause:** Using a consent type that's not supported by the API.

**Valid Consent Types:**

* `eSignAct`
* `termsAndPrivacy`
* `marketingNotifications`
* `smsNotifications`
* `emailNotifications`

**Solution:**

```typescript theme={null}
const VALID_CONSENT_TYPES = [
  'eSignAct',
  'termsAndPrivacy',
  'marketingNotifications',
  'smsNotifications',
  'emailNotifications'
] as const;

type ConsentType = typeof VALID_CONSENT_TYPES[number];

function isValidConsentType(type: string): type is ConsentType {
  return VALID_CONSENT_TYPES.includes(type as ConsentType);
}

if (!isValidConsentType(consentType)) {
  throw new Error(`Invalid consent type: ${consentType}`);
}
```

***

### Invalid Consent Status

**Error:**

```json theme={null}
{
  "error": "Validation error",
  "details": [
    "Invalid consentStatus: 'pending'. Must be one of: granted, denied"
  ]
}
```

**Cause:** Using an invalid status value during consent creation.

**Valid Status Values:**

* For **creation**: `granted`, `denied`
* For **revocation**: `revoked` (set automatically)

**Solution:**

```typescript theme={null}
type ConsentStatus = 'granted' | 'denied';

function validateConsentStatus(status: string): status is ConsentStatus {
  return status === 'granted' || status === 'denied';
}

const consent = {
  consentType: 'marketingNotifications',
  consentStatus: validateConsentStatus(status) ? status : 'denied'
};
```

***

### Empty Onboarding ID

**Error:**

```json theme={null}
{
  "error": "Validation error",
  "details": [
    "onboardingId is required and must not be empty"
  ]
}
```

**Cause:** Missing or empty `onboardingId` in request.

**Solution:**

```typescript theme={null}
import { v4 as uuidv4 } from 'uuid';

function generateOnboardingId(): string {
  const timestamp = Date.now();
  const uuid = uuidv4();
  return `onboarding_${timestamp}_${uuid}`;
}

const onboardingId = generateOnboardingId();
```

***

### Missing Tenant ID

**Error:**

```json theme={null}
{
  "error": "Validation error",
  "details": [
    "tenantId is required"
  ]
}
```

**Cause:** Missing `tenantId` field in request.

**Solution:**

```typescript theme={null}
const requestBody = {
  onboardingId: onboardingId,
  tenantId: 'tenant_baanx_prod', // Required field
  policyType: 'global',
  consents: [...]
};
```

<Note>
  The `tenantId` value should be provided by Baanx during onboarding. Contact support if you don't have your tenant ID.
</Note>

## 409 Conflict

### Duplicate Onboarding ID

**Error:**

```json theme={null}
{
  "error": "Conflict",
  "details": [
    "Consent set with onboardingId 'onboarding_abc123' already exists"
  ]
}
```

**Cause:** Attempting to create a consent set with an `onboardingId` that already exists.

**Solution:**

Generate a new unique `onboardingId`:

```typescript theme={null}
async function createConsentWithRetry(
  consents: ConsentItem[],
  maxRetries: number = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const onboardingId = generateOnboardingId();

    try {
      return await consentManager.createOnboardingConsent(
        onboardingId,
        consents,
        metadata
      );
    } catch (error) {
      if (error.status === 409 && attempt < maxRetries - 1) {
        console.log(`Duplicate onboardingId, retrying... (${attempt + 1}/${maxRetries})`);
        continue;
      }
      throw error;
    }
  }
}
```

***

### User Already Linked

**Error:**

```json theme={null}
{
  "error": "Conflict",
  "details": [
    "This consent set is already linked to userId 'user_123abc456def'"
  ]
}
```

**Cause:** Attempting to link a `userId` to a consent set that's already been linked.

**Solution:**

Check if consent set is already linked before attempting to link:

```typescript theme={null}
async function linkUserIfNeeded(consentSetId: string, userId: string) {
  try {
    const consentSet = await consentManager.getConsentSetById(consentSetId);

    if (consentSet.userId) {
      if (consentSet.userId === userId) {
        console.log('Consent set already linked to this user');
        return consentSet;
      } else {
        throw new Error(`Consent set is linked to different user: ${consentSet.userId}`);
      }
    }

    return await consentManager.linkUserToConsent(consentSetId, userId);
  } catch (error) {
    if (error.status === 409) {
      console.log('Consent set already linked, fetching current state...');
      return await consentManager.getConsentSetById(consentSetId);
    }
    throw error;
  }
}
```

## 404 Not Found

### Consent Set Not Found

**Error:**

```json theme={null}
{
  "error": "Not found",
  "details": [
    "Consent set with ID '550e8400-e29b-41d4-a716-446655440001' not found"
  ]
}
```

**Cause:** Attempting to access a consent set that doesn't exist or was deleted.

**Possible Reasons:**

* ❌ Incorrect `consentSetId`
* ❌ Consent set created in different environment (sandbox vs production)
* ❌ Using wrong tenant context

**Solution:**

```typescript theme={null}
async function getConsentSetSafely(consentSetId: string) {
  try {
    return await consentManager.getConsentSetById(consentSetId);
  } catch (error) {
    if (error.status === 404) {
      console.error(`Consent set not found: ${consentSetId}`);

      return null;
    }
    throw error;
  }
}

const consentSet = await getConsentSetSafely(consentSetId);
if (!consentSet) {
  console.log('Need to create new consent set');
}
```

***

### User Not Found

**Error:**

```json theme={null}
{
  "error": "Not found",
  "details": [
    "No consent sets found for userId 'user_123abc456def'"
  ]
}
```

**Cause:** Attempting to retrieve consents for a user who has no consent sets.

**Solution:**

Handle missing consent gracefully:

```typescript theme={null}
async function getUserConsentOrDefault(userId: string) {
  try {
    const { consentStatus } = await consentManager.getUserConsentStatus(userId);
    return consentStatus;
  } catch (error) {
    if (error.status === 404) {
      console.log(`No consents found for user ${userId}`);
      return 'none';
    }
    throw error;
  }
}

const status = await getUserConsentOrDefault(userId);

if (status === 'none') {
  console.log('User needs to complete onboarding consent');
}
```

***

### Consent Not Found for Revocation

**Error:**

```json theme={null}
{
  "error": "Not found",
  "details": [
    "Consent with ID 'consent_123' not found in consent set"
  ]
}
```

**Cause:** Attempting to revoke a consent that doesn't exist or has already been revoked.

**Solution:**

```typescript theme={null}
async function revokeConsentSafely(
  consentSetId: string,
  consentId: string
) {
  try {
    return await consentManager.revokeConsent(consentSetId, consentId);
  } catch (error) {
    if (error.status === 404) {
      const consentSet = await consentManager.getConsentSetById(consentSetId);
      const consent = consentSet.consents.find(c => c.consentId === consentId);

      if (!consent) {
        console.log('Consent does not exist');
      } else if (consent.consentStatus === 'revoked') {
        console.log('Consent already revoked');
        return consent;
      }
    }
    throw error;
  }
}
```

## 498 Invalid Client Key

**Error:**

```json theme={null}
{
  "error": "Invalid client key",
  "details": [
    "The provided x-client-key is invalid or expired"
  ]
}
```

**Cause:** Missing, incorrect, or expired `x-client-key` header.

**Solution:**

```typescript theme={null}
if (!clientKey || clientKey.trim() === '') {
  throw new Error('Client key is required');
}

const headers = {
  'x-client-key': clientKey,
  'x-secret-key': secretKey
};

await fetch('https://api.baanx.com/v2/consent/onboarding', {
  method: 'POST',
  headers,
  body: JSON.stringify(requestBody)
});
```

<Warning>
  If your client key is expired or invalid, contact Baanx support to obtain new credentials.
</Warning>

## 499 Missing Client Key

**Error:**

```json theme={null}
{
  "error": "Missing client key",
  "details": [
    "x-client-key header is required for all requests"
  ]
}
```

**Cause:** `x-client-key` header not included in request.

**Solution:**

Ensure all requests include the required header:

```typescript theme={null}
const headers: Record<string, string> = {
  'Content-Type': 'application/json',
  'x-client-key': process.env.BAANX_CLIENT_KEY!
};

if (endpoint.requiresAuth) {
  headers['x-secret-key'] = process.env.BAANX_SECRET_KEY!;
}

await fetch(url, { method, headers, body });
```

## 500 Internal Server Error

**Error:**

```json theme={null}
{
  "error": "Internal server error",
  "details": [
    "An unexpected error occurred while creating the consent set"
  ]
}
```

**Cause:** Unexpected server-side error.

**Solution:**

Implement retry logic with exponential backoff:

```typescript theme={null}
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 500 && attempt < maxRetries - 1) {
        const delay = baseDelay * Math.pow(2, attempt);
        console.log(`Server error, retrying in ${delay}ms... (${attempt + 1}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

const result = await retryWithBackoff(() =>
  consentManager.createOnboardingConsent(onboardingId, consents, metadata)
);
```

<Note>
  If 500 errors persist, contact Baanx support with your request details and timestamp for investigation.
</Note>

## Common Integration Issues

### Issue: Consent Status Always Incomplete

**Symptoms:**

* Created consent set with all required consents
* Status returns `incomplete` instead of `complete`

**Possible Causes:**

1. **Using `denied` status for required consents:**

   ```typescript theme={null}
   // ❌ This will result in incomplete status
   { consentType: 'termsAndPrivacy', consentStatus: 'denied' }
   ```

   **Fix:** Required consents must be `granted`:

   ```typescript theme={null}
   // ✅ Correct
   { consentType: 'termsAndPrivacy', consentStatus: 'granted' }
   ```

2. **Revoked consents:**
   If any required consent has been revoked, status becomes `incomplete`.

   **Check:**

   ```typescript theme={null}
   const { consentSets } = await consentManager.getUserConsentStatus(userId, true);
   const revokedConsents = consentSets[0].consents.filter(
     c => c.consentStatus === 'revoked'
   );

   console.log('Revoked consents:', revokedConsents);
   ```

***

### Issue: Can't Find Consent Set After Creation

**Symptoms:**

* Created consent set successfully
* Can't retrieve it using `GET /v2/consent/user/{userId}`

**Cause:** Consent set hasn't been linked to a `userId` yet.

**Solution:**

1. After creating consent set, store the `consentSetId`:
   ```typescript theme={null}
   const { consentSetId } = await consentManager.createOnboardingConsent(...);
   await storage.set(`consent_${onboardingId}`, consentSetId);
   ```

2. After user creation, link the consent set:
   ```typescript theme={null}
   const consentSetId = await storage.get(`consent_${onboardingId}`);
   await consentManager.linkUserToConsent(consentSetId, userId);
   ```

3. Now you can retrieve by `userId`:
   ```typescript theme={null}
   const status = await consentManager.getUserConsentStatus(userId);
   ```

***

### Issue: Metadata Not Appearing in Audit Trail

**Symptoms:**

* Provided metadata in consent creation
* Metadata missing or incomplete in audit records

**Cause:** Metadata field was not properly structured or contained invalid values.

**Solution:**

Ensure metadata is a valid JSON object:

```typescript theme={null}
const metadata = {
  ipAddress: req.ip || 'unknown',
  userAgent: req.headers['user-agent'] || 'unknown',
  timestamp: new Date().toISOString(),
  clientId: 'web-app-v1.2.0'
};

if (req.session?.id) {
  metadata.sessionId = req.session.id;
}

await consentManager.createOnboardingConsent(onboardingId, consents, metadata);
```

<Info>
  All metadata values must be serializable to JSON. Avoid passing functions, circular references, or undefined values.
</Info>

***

### Issue: HATEOAS Links Not Working

**Symptoms:**

* Received `_links` object in response
* Following links results in 404 errors

**Cause:** Links are environment-specific and may point to incorrect environment.

**Solution:**

Parse and use the `href` from `_links` directly:

```typescript theme={null}
const response = await consentManager.getUserConsentStatus(userId);

if (response._links.audit) {
  const auditUrl = response._links.audit.href;
  const auditResponse = await fetch(auditUrl, {
    headers: { 'x-client-key': clientKey }
  });

  const auditData = await auditResponse.json();
}
```

Don't construct URLs manually - use the provided links.

***

### Issue: Rate Limiting

**Symptoms:**

* Requests failing intermittently
* 429 Too Many Requests errors

**Solution:**

Implement rate limiting and request queuing:

```typescript theme={null}
class RateLimitedConsentManager {
  private queue: Array<() => Promise<any>> = [];
  private processing = false;
  private requestsPerSecond = 10;

  async enqueue<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });

      this.processQueue();
    });
  }

  private async processQueue() {
    if (this.processing || this.queue.length === 0) return;

    this.processing = true;

    while (this.queue.length > 0) {
      const fn = this.queue.shift()!;
      await fn();
      await new Promise(resolve => setTimeout(resolve, 1000 / this.requestsPerSecond));
    }

    this.processing = false;
  }
}

const rateLimitedManager = new RateLimitedConsentManager();

await rateLimitedManager.enqueue(() =>
  consentManager.createOnboardingConsent(...)
);
```

## Debugging Tips

### 1. Enable Request Logging

```typescript theme={null}
async function loggedFetch(url: string, options: RequestInit) {
  console.log(`[REQUEST] ${options.method} ${url}`);
  console.log('[HEADERS]', options.headers);
  console.log('[BODY]', options.body);

  const startTime = Date.now();
  const response = await fetch(url, options);
  const duration = Date.now() - startTime;

  console.log(`[RESPONSE] ${response.status} (${duration}ms)`);

  const responseBody = await response.json();
  console.log('[RESPONSE BODY]', JSON.stringify(responseBody, null, 2));

  return { response, body: responseBody };
}
```

### 2. Validate Before Sending

```typescript theme={null}
function validateConsentRequest(request: any) {
  const errors: string[] = [];

  if (!request.onboardingId) errors.push('onboardingId is required');
  if (!request.tenantId) errors.push('tenantId is required');
  if (!request.policyType) errors.push('policyType is required');
  if (!request.consents || request.consents.length === 0) {
    errors.push('consents array is required');
  }

  const requiredConsents = request.policyType === 'US'
    ? 5
    : 4;

  if (request.consents.length < requiredConsents) {
    errors.push(`Missing required consents for ${request.policyType} policy`);
  }

  if (errors.length > 0) {
    throw new Error(`Validation failed: ${errors.join(', ')}`);
  }
}

validateConsentRequest(requestBody);
```

### 3. Test in Sandbox First

Always test consent flows in sandbox environment before production:

```typescript theme={null}
const environment = process.env.NODE_ENV === 'production' ? 'prod' : 'sandbox';

const baseUrl = environment === 'prod'
  ? 'https://api.baanx.com'
  : 'https://sandbox-api.baanx.com';

console.log(`[ENVIRONMENT] Using ${environment} environment`);
```

## Getting Help

If you continue experiencing issues:

1. **Check API Status**: Verify the Baanx API is operational
2. **Review Request/Response**: Use logging to inspect full request and response
3. **Contact Support**: Email [support@baanx.com](mailto:support@baanx.com) with:
   * Request timestamp
   * Request/response details (redact sensitive data)
   * `x-request-id` from response headers
   * Steps to reproduce

## Next Steps

<CardGroup cols={2}>
  <Card title="Implementation Guide" icon="code" href="/guides/consent/implementation">
    Return to implementation guide
  </Card>

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

  <Card title="Compliance Guide" icon="clipboard-check" href="/guides/consent/compliance">
    Regulatory requirements
  </Card>

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