Skip to content
QSWEQB
hardDesignCase StudyMidSeniorStaffLead

Design the account-information API a third party will call on behalf of your customers. Where does consent live, and what does it constrain?

Consent is a first-class server-side resource with an account set, a permission set and an expiry, revocable from either side, and every data read is authorised against it rather than against a token's scope; authentication stays at the bank, and adoption is won on stable error semantics.

6 min readUpdated 2026-07-26

What the interviewer is scoring

  • Whether consent is a queryable resource with its own lifecycle rather than a scope encoded in a token
  • Does the candidate authorise each data access against the consent's account set and permission set, not just against a valid token
  • That revocation is designed from both the customer's channel and the third party's, with the effect on in-flight access stated
  • Whether authentication is kept at the bank, with credentials never reaching the third party
  • Does the candidate treat error semantics, pagination and data availability as the things that decide adoption

Answer

The design decision everything else hangs off is whether consent is a scope value on an access token or an object on your server with an identifier, a state and a history. Make it the object.

A consent record holds who granted it, which third party holds it, the specific accounts it covers, the specific permission set within those accounts, when it was granted and by what authentication, when it expires, and its current state with the transitions that got it there. It is addressable, so the customer can see it in their banking app and the third party can query it. It is the thing revocation acts on. And critically, it is what every subsequent data read is authorised against.

That last point is where implementations quietly go wrong. A valid access token proves the third party was authorised at some point for something. It does not prove that this request, for this account, for this data type, is inside what the customer agreed. If the check is "token is valid and carries the accounts scope", then a token minted for two accounts will happily read a third, and a consent that has since been revoked keeps working until the token expires. The authorisation check on every request has to resolve the consent behind the token, confirm it is still active, and confirm that the requested account and the requested data class are within its sets. Fail closed on anything else.

{
  "consentId": "cns_01HV…",
  "tppId": "psp_884",
  "status": "authorised",
  "permissions": ["ReadAccountsDetail", "ReadBalances", "ReadTransactionsDebits"],
  "accounts": ["acc_11", "acc_12"],
  "transactionHistoryFrom": "2025-07-01",
  "grantedAt": "2026-07-14T10:02:11Z",
  "authenticatedWith": "sca-app-biometric",
  "expiresAt": "2027-07-14T10:02:11Z",
  "reconfirmedAt": null,
  "statusHistory": [
    { "status": "awaiting-authorisation", "at": "2026-07-14T10:01:48Z" },
    { "status": "authorised", "at": "2026-07-14T10:02:11Z", "by": "customer" }
  ]
}

Note transactionHistoryFrom. Consent is bounded in the past as well as the future, and a third party granted balances and ninety days of history must not be able to walk back five years by paginating.

Delegated authorisation, and the credential that never moves

The customer authenticates at the bank, in the bank's own interface, and the third party never sees the credential. This is not a stylistic preference; the entire regime exists to replace screen-scraping with shared credentials, and any design where the third party collects a password has recreated the problem.

Mechanically that is a redirect or a decoupled flow. The third party creates the consent request, the customer is sent to the bank to authenticate and to see exactly what they are being asked to share, the bank records the decision against the consent, and an authorisation code comes back to the third party to exchange for tokens. Two details are worth naming because they are where real implementations differ from textbook OAuth. The authorisation request should be lodged with the bank ahead of the redirect rather than passed through the browser as query parameters, so the request cannot be tampered with in transit and the consent the customer approves is provably the one the third party asked for. And client authentication should be strong and bound to the client's own key material rather than a shared secret, with tokens bound to the sender so a stolen token is not usable on its own. The financial-grade API profiles exist precisely to pin these choices down, and citing them beats inventing your own hardening.

The consent screen itself is part of the security design. It must state the accounts, the data categories, the duration and the third party's registered identity, sourced from the regulatory register rather than from the request. A customer who cannot tell from the screen what they are agreeing to has not given informed consent, whatever the token says.

Strong customer authentication, and where re-authentication bites

Strong customer authentication means the customer proves themselves with elements from at least two independent categories — something they know, something they possess, something they are — with the elements independent enough that compromising one does not compromise the other. For account information the bank applies it when consent is established. It does not apply on every data read, because unattended access is the point of the API.

The friction question is what happens over a long-lived consent. Regimes have required the customer to reconfirm access periodically so that a consent granted once does not become permanent by neglect, and the notable direction of travel has been to move that reconfirmation to the third party's own interface rather than forcing the customer back through a bank authentication, because in practice a mandatory bank re-authentication silently killed a large share of active connections — the customer never came back, the service degraded, and neither party could tell whether that was a security outcome or an attrition one. Whichever variant applies to you, the consent record has to carry the reconfirmation state and expiry explicitly, and the API has to signal an approaching expiry in a way the third party can act on before access dies.

Revocation has to work from both ends and mean the same thing. From the bank's channel, because a customer who cannot see and switch off their connections in their own app has no control. From the third party, because a customer who cancels a service expects the access to stop. Either path moves the consent to a terminal state, and the effect on tokens must be immediate rather than at next expiry — which means the token introspection or the per-request consent lookup is on the hot path, and caching it is a decision with a stated staleness window rather than an accident.

What actually makes a third party integrate

The technical standard is table stakes; adoption is decided by operational qualities that rarely appear in a design answer.

Error semantics come first. A third party integrating against ten banks writes one code path per distinguishable failure, so the difference between a consent that expired, a consent revoked, an account closed, a temporary upstream outage and a rate limit must be visible in the response and stable over time. Collapsing all of them into a generic failure means every one of those situations shows the customer "something went wrong", and the third party's support cost lands on your reputation.

Then honesty about data. Say how far back transactions go, whether pending and booked items are distinguished, when balances are as-of rather than live, and what happens overnight while your core banking runs its end-of-day. A field that is present but frequently null is worse than a documented absence, because the integrator built logic on it. Pagination must be cursor-based and stable under concurrent posting, since offset paging over a table that is being appended to will drop or duplicate rows and the third party will discover it as a reconciliation difference weeks later.

Then the plumbing that signals you intend to be a platform: a sandbox whose data shapes match production, published rate limits with headers that let a client back off before being throttled, availability and performance figures published rather than claimed, an idempotency contract on anything that mutates, and versioning where additive changes never break an existing client and breaking changes get a version and a deprecation window. Third parties choose which banks to prioritise, and they choose on the basis of which integrations cost them least to keep working.

The consent object, not the token, is the authorisation boundary: resolve it and re-check its account set, permission set and history window on every single read, and fail closed the moment it is not active. A token that outlives a revoked consent is the defect this whole architecture exists to prevent.

Likely follow-ups

  • The customer revokes consent while a batch job of yours is halfway through a data pull. What does the third party see?
  • How do you distinguish a customer-initiated refresh from unattended background polling, and why does the difference matter?
  • What do you return when one of five consented accounts has since been closed?
  • How would you version this API so a change that adds a field cannot break an existing integration?

Related questions

Further reading

open-bankingconsentoauth2strong-customer-authenticationapi-design