Under the Berlin Group PSD2 framework, Third-Party Providers (TPPs) are expected to integrate with banks using standardized APIs for Account Information (AIS) and Payment Initiation (PIS).
While the specification is detailed, real-world bank sandboxes are often incomplete, unstable, or misleading. As a result, development teams waste months debugging integration issues that only appear after going live.
A complete Berlin Group–compliant PSD2 sandbox is not just an API mock.
It is a regulatory-grade simulation of a real bank.
This article explains:
- What a proper Berlin Group sandbox must implement
- Where most bank sandboxes fail
- How to design it correctly
- Concrete sequence diagrams
- Real Spring Boot examples used in production-grade sandboxes
Why Berlin Group Compliance Changes Everything
Berlin Group defines not just endpoints, but behavior:
- Consent lifecycle rules
- OAuth2 + SCA orchestration
- PSU interaction models
- Error semantics
- State transitions
A sandbox that “returns JSON” but does not enforce these rules is dangerous, because it gives TPPs false confidence.
Architecture of a Complete Berlin Group PSD2 Sandbox
At a high level, a production-grade sandbox contains:
TPP Application
↓
OAuth2 / SCA Gateway
↓
Consent Management Engine
↓
AIS / PIS Domain Services
↓
Scenario-Driven Bank Core Simulator
Each layer must behave like a real bank—not like a demo.
1. OAuth2 & SCA – Berlin Group Style
Berlin Group mandates OAuth2 Authorization Code Flow, tightly coupled with consent and SCA.
Correct Berlin Group OAuth2 Sequence
TPP → Authorization Endpoint
TPP ← Redirect to PSU Login
PSU → Authenticate & SCA
PSU ← Consent Approval UI
TPP ← Authorization Code
TPP → Token Endpoint
TPP ← Access Token (Consent-Bound)
Key Sandbox Requirements
✔ Tokens must expire
✔ Tokens must be bound to consentId
✔ SCA must not always succeed
✔ Redirect-based PSU interaction must be enforced
2. Consent Management (AIS / PIS)
Why Consent Is the Core of Berlin Group
In Berlin Group, everything is consent-driven:
- Accounts
- Balances
- Transactions
- Payments
Most sandboxes create consents but never enforce them.
A real sandbox must block access if consent scope or status is invalid.
Consent Lifecycle
RECEIVED → VALID → REVOKED → EXPIRED
Spring Boot – Consent Validation Example
@Service
public class ConsentValidator {
public void validate(String consentId, ConsentScope scope) {
Consent consent = consentRepository.findById(consentId)
.orElseThrow(() -> new ForbiddenException("Consent not found"));
if (!consent.isValid()) {
throw new ForbiddenException("Consent expired or revoked");
}
if (!consent.getScopes().contains(scope)) {
throw new ForbiddenException("Consent scope missing");
}
}
}
3. AIS – Account Information Services
Mandatory Berlin Group AIS Endpoints
GET /accountsGET /accounts/{accountId}GET /accounts/{accountId}/balancesGET /accounts/{accountId}/transactions
What a Real Sandbox Must Simulate
✔ Multiple accounts per PSU
✔ Multiple currencies
✔ Booking date vs value date
✔ Pagination
✔ Realistic transaction histories
Example – Transactions Endpoint (Spring Boot)
@GetMapping("/accounts/{id}/transactions")
public ResponseEntity<TransactionsResponse> getTransactions(
@PathVariable String id,
@RequestHeader("Consent-ID") String consentId) {
consentValidator.validate(consentId, ConsentScope.TRANSACTIONS);
return ResponseEntity.ok(
transactionService.getTransactionsForAccount(id)
);
}
4. PIS – Payment Initiation Services
Supported Payment Types
A complete sandbox should support:
- SEPA Credit Transfers
- Instant Payments
- Scheduled payments
- Rejected & failed payments
Payment State Machine (Berlin Group)
RCVD → ACTC → ACSC
↓
RJCT
Payment Flow – Sequence Diagram
TPP → Create Payment
TPP ← paymentId (RCVD)
TPP → Authorize Payment
PSU → SCA Challenge
PSU ← SCA Success / Failure
TPP → Get Payment Status
TPP ← ACTC / ACSC / RJCT
Payment Status Endpoint Example
@GetMapping("/payments/{paymentId}/status")
public PaymentStatusResponse getStatus(@PathVariable String paymentId) {
return paymentService.getStatus(paymentId);
}
5. SCA Simulation (Critical for Realism)
Why Most Sandboxes Are Useless Here
Many sandboxes:
❌ Always pass SCA
❌ Never expire challenges
❌ Ignore PSU behavior
A real sandbox must simulate:
- OTP success
- OTP failure
- Timeout
- Cancelled authentication
Example SCA Challenge Response
{
"scaStatus": "started",
"scaMethod": "OTP",
"expiresInSeconds": 120
}
6. Error Handling – Where Most Sandboxes Fail
Berlin Group Error Semantics Matter
TPPs rely on precise HTTP codes and error bodies.
| Scenario | HTTP |
|---|---|
| Consent expired | 403 |
| Invalid IBAN | 400 |
| Unauthorized scope | 403 |
| Rate limit exceeded | 429 |
| Bank unavailable | 503 |
Example Error Response
{
"tppMessages": [
{
"category": "ERROR",
"code": "CONSENT_EXPIRED",
"text": "The consent is no longer valid"
}
]
}
7. Deterministic Test Scenarios (Essential for CI/CD)
A professional sandbox must offer predictable PSU profiles:
PSU_BASIC → AIS only, no SCA exemptions
PSU_PREMIUM → Multiple accounts, large balances
PSU_RISKY → Frequent SCA, payment rejections
This enables:
- Automated regression tests
- Continuous integration
- Safe pre-production validation
8. Observability for TPPs
A TPP-friendly sandbox exposes:
✔ Correlation IDs
✔ Deterministic logs
✔ Traceable consent/payment chains
X-Request-ID: 9a7c-psd2-sbx-001
This alone can cut integration time by 30–40%.
Why Teams Build Their Own Berlin Group Sandbox
Organizations that build internal PSD2 sandboxes report:
- Faster bank onboarding
- Fewer production incidents
- Higher certification success rates
- Stronger regulatory confidence
In a market where bank sandboxes are unreliable, a well-designed Berlin Group sandbox becomes a competitive advantage.
Final Thoughts
A Berlin Group PSD2 sandbox is not a mock API.
It is a bank behavior simulator, and it must fail, reject, expire, and challenge—just like a real bank.
If your sandbox:
- Always succeeds
- Never expires tokens
- Ignores consent scopes
…it is actively hurting your Open Banking project.


