While our API is designed for direct, seamless integration, placing it behind an API Gateway (or a dedicated proxy layer) unlocks significant architectural and performance benefits. This approach ensures your production environment benefits from caching, centralized security, and compliance enforcement without requiring a dedicated runtime or complex service mesh setup just to manage TPP validation.
The Gateway acts as the control plane, allowing your backend services to remain clean and focused on their core business logic.
The primary architectural advantage is the ability to leverage your Gateway's built-in features to eliminate repeated external calls.
Zero Integration Runtime: Your upstream application code simply makes a call to your internal Gateway endpoint. The Gateway handles the heavy lifting of external communication, protocol negotiation, and response manipulation—freeing your application servers from this non-core logic.
Intelligent Caching: TPP status changes infrequently. The Gateway can cache the successful 200 OK validation responses, keying them off the eIDAS certificate hash. This drastically reduces latency for subsequent calls and saves costs on external requests.
Cache Expiry Logic: By reading our x-tpp-latest header (the time of the last NCA/EBA register update), the Gateway can implement a time-bound cache, ensuring data is refreshed when it's most likely to have changed.
2. 🛡️ Centralized Security and Compliance Enforcement
The API Gateway is the ideal place to enforce the PSD2 compliance rules embedded within our API response headers.
Gateway Action
Source Data
Benefit
Request Validation
cc query parameter
Ensures the TPP is requesting services for jurisdictions that were included in the validation call.
Passporting Enforcement
x-tpp-passports header
Crucial PSD2 compliance check. The Gateway automatically compares requested jurisdictions (cc) against confirmed rights, instantly denying access if the TPP is not licensed for the target jurisdiction.
Audit Logging
All headers (x-tpp-identifier, x-tpp-latest, etc.)
Logs all structured compliance data before the request hits your backend. Provides a single, auditable record for every transaction.
Error Standardization
X-TPP-Reason header
Converts external 422 Unprocessable Entity responses into standardized internal (or external 403 Forbidden) errors, protecting your backend from proprietary error formats.
The most critical Gateway function is the Passporting Enforcement Check. This ensures regulatory compliance by matching the TPP's requested jurisdictions against the confirmed licenses.
This logic runs after a successful 200 OK validation response is received.
This example uses JavaScript, a common scripting language within Gateways (like Apigee, Kong, etc.), to check if every requested jurisdiction (request.queryparam.cc) is present in the confirmed passporting list (tpp.passports).
// Policy: JS-VerifyPassportingRights// Checks that all jurisdictions in the 'cc' parameter are covered by 'x-tpp-passports'.varrequestedJurisdictions = context.getVariable('request.queryparam.cc');varconfirmedPassports = context.getVariable('tpp.passports');// e.g., 'FR=PSP_AI,PSP_PI;IT=PSP_AI,PSP_PI'varmissingJurisdictions = [];if(requestedJurisdictions && confirmedPassports){varreqCodes = requestedJurisdictions.toUpperCase().split(',');// Check for coverage: is the requested CODE followed by '=' (to ensure a match) present // in the confirmed list?reqCodes.forEach(function(code){varsearchString = code.trim() + '=';// Check if any passport entry starts with the required jurisdiction code (e.g., 'FR=')varisPassported = confirmedPassports.toUpperCase().split(';').some(function(passportEntry){returnpassportEntry.startsWith(searchString);});if(!isPassported){missingJurisdictions.push(code);}});}if(missingJurisdictions.length > 0){context.setVariable('tpp.passporting.valid',false);context.setVariable('tpp.passporting.missingCodes',missingJurisdictions.join(','));}else{context.setVariable('tpp.passporting.valid',true);}
// Policy: JS-VerifyPassportingRights// Checks that all jurisdictions in the 'cc' parameter are covered by 'x-tpp-passports'.varrequestedJurisdictions = context.getVariable('request.queryparam.cc');varconfirmedPassports = context.getVariable('tpp.passports');// e.g., 'FR=PSP_AI,PSP_PI;IT=PSP_AI,PSP_PI'varmissingJurisdictions = [];if(requestedJurisdictions && confirmedPassports){varreqCodes = requestedJurisdictions.toUpperCase().split(',');// Check for coverage: is the requested CODE followed by '=' (to ensure a match) present // in the confirmed list?reqCodes.forEach(function(code){varsearchString = code.trim() + '=';// Check if any passport entry starts with the required jurisdiction code (e.g., 'FR=')varisPassported = confirmedPassports.toUpperCase().split(';').some(function(passportEntry){returnpassportEntry.startsWith(searchString);});if(!isPassported){missingJurisdictions.push(code);}});}if(missingJurisdictions.length > 0){context.setVariable('tpp.passporting.valid',false);context.setVariable('tpp.passporting.missingCodes',missingJurisdictions.join(','));}else{context.setVariable('tpp.passporting.valid',true);}
// Policy: JS-VerifyPassportingRights// Checks that all jurisdictions in the 'cc' parameter are covered by 'x-tpp-passports'.varrequestedJurisdictions = context.getVariable('request.queryparam.cc');varconfirmedPassports = context.getVariable('tpp.passports');// e.g., 'FR=PSP_AI,PSP_PI;IT=PSP_AI,PSP_PI'varmissingJurisdictions = [];if(requestedJurisdictions && confirmedPassports){varreqCodes = requestedJurisdictions.toUpperCase().split(',');// Check for coverage: is the requested CODE followed by '=' (to ensure a match) present // in the confirmed list?reqCodes.forEach(function(code){varsearchString = code.trim() + '=';// Check if any passport entry starts with the required jurisdiction code (e.g., 'FR=')varisPassported = confirmedPassports.toUpperCase().split(';').some(function(passportEntry){returnpassportEntry.startsWith(searchString);});if(!isPassported){missingJurisdictions.push(code);}});}if(missingJurisdictions.length > 0){context.setVariable('tpp.passporting.valid',false);context.setVariable('tpp.passporting.missingCodes',missingJurisdictions.join(','));}else{context.setVariable('tpp.passporting.valid',true);}
// Policy: JS-VerifyPassportingRights// Checks that all jurisdictions in the 'cc' parameter are covered by 'x-tpp-passports'.varrequestedJurisdictions = context.getVariable('request.queryparam.cc');varconfirmedPassports = context.getVariable('tpp.passports');// e.g., 'FR=PSP_AI,PSP_PI;IT=PSP_AI,PSP_PI'varmissingJurisdictions = [];if(requestedJurisdictions && confirmedPassports){varreqCodes = requestedJurisdictions.toUpperCase().split(',');// Check for coverage: is the requested CODE followed by '=' (to ensure a match) present // in the confirmed list?reqCodes.forEach(function(code){varsearchString = code.trim() + '=';// Check if any passport entry starts with the required jurisdiction code (e.g., 'FR=')varisPassported = confirmedPassports.toUpperCase().split(';').some(function(passportEntry){returnpassportEntry.startsWith(searchString);});if(!isPassported){missingJurisdictions.push(code);}});}if(missingJurisdictions.length > 0){context.setVariable('tpp.passporting.valid',false);context.setVariable('tpp.passporting.missingCodes',missingJurisdictions.join(','));}else{context.setVariable('tpp.passporting.valid',true);}
Apigee RaiseFault Policy: Denial of Access
If the check fails, the Gateway raises a fault, immediately denying the request before it reaches your core backend service.
<RaiseFaultname="RF-MissingPassporting"><Condition>tpp.passporting.valid != "true"</Condition><Set><StatusCode>403</StatusCode><ReasonPhrase>Forbidden</ReasonPhrase><PayloadcontentType="application/json">{"error": "MISSING_PASSPORTING_RIGHTS",
"message": "TPP is not licensed for all requested jurisdictions: {tpp.passporting.missingCodes}"
}
</Payload></Set></RaiseFault>
<RaiseFaultname="RF-MissingPassporting"><Condition>tpp.passporting.valid != "true"</Condition><Set><StatusCode>403</StatusCode><ReasonPhrase>Forbidden</ReasonPhrase><PayloadcontentType="application/json">{"error": "MISSING_PASSPORTING_RIGHTS",
"message": "TPP is not licensed for all requested jurisdictions: {tpp.passporting.missingCodes}"
}
</Payload></Set></RaiseFault>
<RaiseFaultname="RF-MissingPassporting"><Condition>tpp.passporting.valid != "true"</Condition><Set><StatusCode>403</StatusCode><ReasonPhrase>Forbidden</ReasonPhrase><PayloadcontentType="application/json">{"error": "MISSING_PASSPORTING_RIGHTS",
"message": "TPP is not licensed for all requested jurisdictions: {tpp.passporting.missingCodes}"
}
</Payload></Set></RaiseFault>
<RaiseFaultname="RF-MissingPassporting"><Condition>tpp.passporting.valid != "true"</Condition><Set><StatusCode>403</StatusCode><ReasonPhrase>Forbidden</ReasonPhrase><PayloadcontentType="application/json">{"error": "MISSING_PASSPORTING_RIGHTS",
"message": "TPP is not licensed for all requested jurisdictions: {tpp.passporting.missingCodes}"
}
</Payload></Set></RaiseFault>
This setup ensures that:
The external validation service confirms the certificate is valid and registered.
The API Gateway then performs the compliance check to ensure the scope of the request (cc) is within the TPP's confirmed rights (x-tpp-passports), granting or denying access based on the match.
Zero-Friction Integration. See how easily our single-endpoint validation service fits into your stack. Request a Demo, on how-to achieve compliance in minutes.
Zero-Friction Integration. See how easily our single-endpoint validation service fits into your stack. Request a Demo, on how-to achieve compliance in minutes.