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 support@baanx.com with:
Request timestamp
Request/response details (redact sensitive data)
x-request-id from response headers
Steps to reproduce
Next Steps
Implementation Guide Return to implementation guide
API Reference Complete endpoint documentation
Compliance Guide Regulatory requirements
Overview Back to consent management overview