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:
{
"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:
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:
{
"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:
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:
{
"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:
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:
{
"error": "Validation error",
"details": [
"onboardingId is required and must not be empty"
]
}
Cause: Missing or empty onboardingId in request.
Solution:
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:
{
"error": "Validation error",
"details": [
"tenantId is required"
]
}
Cause: Missing tenantId field in request.
Solution:
const requestBody = {
onboardingId: onboardingId,
tenantId: 'tenant_baanx_prod', // Required field
policyType: 'global',
consents: [...]
};
The tenantId value should be provided by Baanx during onboarding. Contact support if you don’t have your tenant ID.
409 Conflict
Duplicate Onboarding ID
Error:
{
"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:
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:
{
"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:
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:
{
"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:
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:
{
"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:
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:
{
"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:
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:
{
"error": "Invalid client key",
"details": [
"The provided x-client-key is invalid or expired"
]
}
Cause: Missing, incorrect, or expired x-client-key header.
Solution:
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)
});
If your client key is expired or invalid, contact Baanx support to obtain new credentials.
499 Missing Client Key
Error:
{
"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:
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:
{
"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:
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)
);
If 500 errors persist, contact Baanx support with your request details and timestamp for investigation.
Common Integration Issues
Issue: Consent Status Always Incomplete
Symptoms:
- Created consent set with all required consents
- Status returns
incomplete instead of complete
Possible Causes:
-
Using
denied status for required consents:
// ❌ This will result in incomplete status
{ consentType: 'termsAndPrivacy', consentStatus: 'denied' }
Fix: Required consents must be granted:
// ✅ Correct
{ consentType: 'termsAndPrivacy', consentStatus: 'granted' }
-
Revoked consents:
If any required consent has been revoked, status becomes
incomplete.
Check:
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:
-
After creating consent set, store the
consentSetId:
const { consentSetId } = await consentManager.createOnboardingConsent(...);
await storage.set(`consent_${onboardingId}`, consentSetId);
-
After user creation, link the consent set:
const consentSetId = await storage.get(`consent_${onboardingId}`);
await consentManager.linkUserToConsent(consentSetId, userId);
-
Now you can retrieve by
userId:
const status = await consentManager.getUserConsentStatus(userId);
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:
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);
All metadata values must be serializable to JSON. Avoid passing functions, circular references, or undefined values.
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:
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:
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
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
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:
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:
- Check API Status: Verify the Baanx API is operational
- Review Request/Response: Use logging to inspect full request and response
- Contact Support: Email [email protected] with:
- Request timestamp
- Request/response details (redact sensitive data)
x-request-id from response headers
- Steps to reproduce
Next Steps