AI agents in open banking: what banks should enforce at the API layer
AI agents in open banking will reach your account and payment APIs through TPP apps and your own assistants. Separate their identities and keep SCA in place.
- ai-agents
- open-banking
- payments
- security
Your payment initiation API was designed for two kinds of caller: a TPP app driven by a person who has just authenticated with your bank, and your own channels. Agentic commerce adds a third. Visa and Mastercard have announced agent payment programmes on card rails, and Google's AP2 is designed to work with real-time bank transfers as well, so some agent traffic will reach you as AI agents in open banking: an assistant inside a TPP's budgeting app pulling transactions, or an agent submitting a payment under a consent the customer authorised earlier. Your bank will ship its own assistants too.
The platform team's questions are practical. Can you tell whether a person or an agent drove a call? Can an agent that only needs balances submit a payment? If an agent loops, whose traffic gets throttled? Can you show a supervisor what an agent did last Tuesday from one log?
Strong customer authentication and consent stay with your authorization server, banking app and consent service. The gateway enforces what they decided and records who acted on it.
Why existing approaches fail
Many banks run open banking on a general-purpose gateway such as Apigee, Kong, AWS API Gateway, Azure API Management or MuleSoft. Each authenticates the TPP, checks scopes and throttles per app. The weak point is the identity model. When a TPP adds an agent feature to an existing app, it usually reuses that app's registration and credentials, so the agent and the human app arrive as the same caller, sharing a rate bucket, a log identity and method rights. When the agent misbehaves, the only lever you have also cuts off the app.
Newer AI gateway features do not close that gap. Azure API Management's AI gateway and Kong's AI plugins centre on model traffic: token limits, semantic caching, prompt moderation. An agent calling your payment API is inbound business traffic, where what matters is the API operation and the consent behind it. Both can expose REST APIs as MCP tools, which helps discovery but leaves the payment checks where they were. AI gateway, LLM gateway or agent API gateway separates the two directions.
Those checks usually live in custom code: a Lambda authorizer, an Apigee ServiceCallout with JavaScript, a Mule flow, each redeployed for every rule change. And the bank's own assistants often get an internal service account behind the gateway, so their calls never appear next to TPP traffic.
How to govern AI agents in open banking at the API layer
These are decisions you already make for TPPs, applied to agents. In Zerq each maps to configuration your open banking setup already uses.
SCA and consent obligations still apply
Nothing about an agent changes who authenticates the payer. Under the UK Payment Services Regulations 2017, a payment is authorised only if the payer consented to it or to a series it belongs to (regulation 67), and SCA applies when the payer initiates an electronic payment, dynamically linked to amount and payee for remote payments (regulation 100). PSD2 Articles 64 and 97 match. Card schemes bind agents to consumer authentication through tokenized credentials and Google's AP2 uses user-signed mandates, but no regulator has confirmed that any of these satisfies SCA for an agent-initiated payment.
The FCA's Mills Review (July 2026) recommends a framework for how agents are authorised, identified and held accountable, including consent mandates. HM Treasury's July 2026 payments consultation asks whether authentication, consent and liability rules need updating for agentic payments. The Bank of England's Sarah Breeden named consent for multiple agent transactions as a key issue.
SCA happens when the customer authorises a consent in your banking app or authorization server. Zerq does not perform SCA or store consent. On payment submission, the gateway checks that the call matches a consent your systems hold and stops anything outside it; for account access, consent stays with the token your authorization server issued.
Give agent traffic its own client identity
In Zerq, identity is a client plus a profile, sent as X-Client-ID and X-Profile-ID on every request. A policy attaches to the client and covers all its profiles together, so the client is the unit for agent traffic: its own rate bucket, collection assignments and log filter value.
For a TPP with an agent feature, create a second client under Clients, New Client, with a Name and Description that say what it carries, for example northpay-agent. Give its profiles the auth type the TPP's app uses, oidc or mtls, so the agent path is no weaker. The client and certificate-bound profile setup follows our TPP onboarding guide.
Offer this to TPPs rather than requiring it. The RTS lists additional registrations and additional consent checks among possible obstacles, and PSD2 requires PISP payment orders to be treated without discrimination absent an objective reason. The TPP benefits too: its agent can no longer exhaust its human users' bucket. Apply the same consent checks to both clients; the difference is identity and rate shape, not stricter rules.
Your own assistants carry no such constraint. Give each one its own client per environment.
Keep account information agents on GET-only profiles
An agent that reads balances and transactions has no reason to hold a write method. Set Allowed Methods on its profile to GET, and any other method returns 405 before a workflow or backend runs, whatever the agent was talked into. If the agent never initiates payments, assign its client only the accounts collection, so payments paths do not exist for it (the gateway returns 404 for a path outside the client's collections, and a GET-only profile stops a POST earlier with 405). OWASP's Top 10 for Agentic Applications lists tool misuse and recommends giving agents the minimum tools and permissions they need. Method and collection scope enforce that regardless of the prompt. This applies to agents calling your REST APIs; Gateway MCP assistants work differently, as described below.
Put payment initiation behind a workflow
Payment submission needs the most checks, as for any payment API. Open Collections, select the payments collection, open the payment submission proxy and click Edit Workflow. The graph runs http_trigger, validate_node, http_request_node, condition_node, then proxy_node on the allowed path followed by a response_node that returns the upstream status and body ({{ $json['forward'].response.status_code }}, {{ $json['forward'].response.body }}), with proxy_node's error output and every denial ending in their own response_node. The payload shape is simplified; use your standard's field names.
{
"id": "check_shape",
"type": "validate_node",
"data": {
"inputs": { "value": "{{ $json['http_trigger'].request.body }}" },
"config": {
"schema": {
"type": "object",
"required": ["consent_id", "instructed_amount", "creditor_account"],
"additionalProperties": false,
"properties": {
"consent_id": { "type": "string", "pattern": "^[A-Za-z0-9-]{1,128}$" },
"instructed_amount": {
"type": "object",
"required": ["amount", "currency"],
"properties": {
"amount": { "type": "string", "pattern": "^[0-9]{1,13}\\.[0-9]{2}$" },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
}
},
"creditor_account": { "type": "object", "required": ["identification"] }
}
}
}
}
}
The schema rejects malformed agent payloads before any outbound call. The consent_id pattern matters because the next node puts that value in a URL path, so it keeps / and .. out of the consent lookup. Wire invalid to a response_node returning 400 with {"error": "invalid_payment_payload"}.
{
"id": "consent_lookup",
"type": "http_request_node",
"data": {
"config": { "timeout_ms": 2000 },
"inputs": {
"url": "https://consent.bank.internal/v1/consents/{{ $json['http_trigger'].request.body.consent_id }}",
"method": "GET"
}
}
}
Non-2xx responses and timeouts both leave through error. Wire it to a second condition_node with one condition, $json['consent_lookup'].response.status_code === 404, routed to a response_node returning 403 with consent_not_found. Its default output catches timeouts (status code 0) and 5xx, and goes to a response_node returning 503 with consent_service_unavailable and a Retry-After header. So a missing consent and an unreachable consent service both stop the payment, with different status codes.
{
"id": "consent_rules",
"type": "condition_node",
"data": {
"config": {
"conditions": [
{ "condition": "$json['consent_lookup'].response.body.status !== 'Authorised'", "output": "not_authorised" },
{ "condition": "$json['consent_lookup'].response.body.creditor_account.identification !== $json['http_trigger'].request.body.creditor_account.identification", "output": "payee_mismatch" },
{ "condition": "$json['http_trigger'].request.body.instructed_amount.currency !== $json['consent_lookup'].response.body.currency", "output": "currency_mismatch" },
{ "condition": "Number($json['http_trigger'].request.body.instructed_amount.amount) > Number($json['consent_lookup'].response.body.max_amount)", "output": "over_limit" },
{ "condition": "$json['consent_lookup'].response.body.status === 'Authorised' && Number($json['http_trigger'].request.body.instructed_amount.amount) <= Number($json['consent_lookup'].response.body.max_amount)", "output": "within_consent" }
],
"default_output": "deny"
}
}
}
Look at the default. A condition that fails, say because the consent record lacks a field, does not match, and anything matching nothing lands on default_output. So the default denies, and approval is an explicit last condition. The currency check runs before the amount check, so an amount is never compared with a limit set in another currency. Only within_consent connects to the proxy_node (id forward), and its success output goes to the response_node that returns the payment service's status and body to the TPP. These rules do not check which TPP a consent belongs to, so the example relies on your authorization server binding each access token to the consent it was issued for, and on your payment service checking that binding. Each denial gets a structured body an agent can act on:
{
"id": "deny_over_limit",
"type": "response_node",
"data": {
"inputs": {
"status_code": 403,
"headers": { "Content-Type": ["application/json"] },
"body": {
"error": "payment_exceeds_consent",
"message": "Instructed amount is above the limit recorded on the consent.",
"consent_id": "{{ $json['http_trigger'].request.body.consent_id }}"
}
}
}
}
This checks the payment against the consent your systems already hold. It does not ask the customer to confirm again, which would be the kind of additional consent check Article 32(3) of the RTS lists as a possible obstacle. Agents also retry, so make payment submission idempotent in your payment service, keyed on the TPP's idempotency key. The Redis-guarded idempotency workflow can short-circuit sequential retries at the gateway, but it does not replace backend idempotency.
Tune policies for loops and retries
People click; agents loop, and many agent frameworks retry on any error. Go to Policies, click New Policy, then select it in the agent client's Policy dropdown. Size it so normal use never touches it: a 1m rate limit catches a tight loop within a minute, and a 1d quota caps a slow leak the per-minute limit never sees. Over the rate limit, the agent gets 429 with a Retry-After header from its own bucket, while the TPP's app keeps working; over the daily quota it gets 429 with quota_limit_exceeded.
For TPPs, size from capacity and apply one policy to every agent client, as the open banking rate limit post describes.
Gateway MCP as the discovery surface for your own assistants
Internal assistants belong on the same gateway. Through Zerq's Gateway MCP at /mcp, an assistant sees only its client's collections. Its profile must allow POST, because every MCP message is itself a POST to /mcp, so an assistant's read-only scope comes from collection assignment rather than Allowed Methods. Assign accounts and product reference collections, never payments. The payment proxy then does not exist for the assistant: list_endpoints never returns it, and execute_endpoint is limited to the same collections. Each execute_endpoint call is a real gateway request, subject to the same policy and request logging.
The request path
- A caller sends
X-Client-ID,X-Profile-IDand its credential: anoidcaccess token, or a client certificate validated at your ingress formtlsprofiles. An assistant'sexecute_endpointcall at/mcpbecomes the same gateway request. - The client's policy is checked first, keyed on
X-Client-ID; over the limit the gateway returns429before anything else runs, and these rejections are not written to Request Logs. - The gateway resolves client and profile and validates the credential (
401if it is bad), then checks the profile's IP allowlist (403). - The method must be in Allowed Methods (
405otherwise), and the route must match an active collection assigned to the client (404otherwise). - On the payment proxy, the workflow returns a structured
400,403or503denial, or forwards throughproxy_nodeto the payment service and returns its status and body through aresponse_node. - For every request that got past step 2, the gateway writes a request log entry with Request ID, Client ID, Profile ID, Collection, status code and bodies.
Steps 2 to 4 happen before the workflow, so payment logic never runs for a caller that should not reach it.
What the request log shows
One evening's entries. Names added for readability; the log stores IDs.
Request ID 7c1f9e2a-4b6d-4f0e-9a31-2d5b8c7e1a04
Method/Path POST /payments/v1/domestic-payments
Status code 201
Client ID 6650a1c3e2b94f0d8a7c1101 (northpay-app)
Profile ID 6650a1c3e2b94f0d8a7c1102 (northpay-app-mtls)
Collection payments
Request ID b83e0d51-9f2c-4a7b-8e64-1c0f3a9d2b77
Method/Path POST /payments/v1/domestic-payments
Status code 403
Client ID 6650a1c3e2b94f0d8a7c1201 (northpay-agent)
Profile ID 6650a1c3e2b94f0d8a7c1203 (northpay-agent-pis)
Collection payments
Response body {"error":"payment_exceeds_consent","message":"Instructed amount is above the limit recorded on the consent.","consent_id":"cns-40718"}
Request ID e2a47c90-6d1b-4c3f-b5e8-9a0d7f2c6e15
Method/Path POST /payments/v1/domestic-payments
Status code 405
Client ID 6650a1c3e2b94f0d8a7c1201 (northpay-agent)
Profile ID 6650a1c3e2b94f0d8a7c1202 (northpay-agent-ais)
Collection (empty: method refused before collection matching)
Request ID 41d6b8f3-2e7a-4c95-a0b1-8f3e6d9c2a58
Method/Path GET /accounts/v1/accounts/acc-2231/balances
Status code 200
Client ID 6650a1c3e2b94f0d8a7c1301 (contact-centre-copilot)
Profile ID 6650a1c3e2b94f0d8a7c1302 (copilot-mcp-readonly)
Collection accounts
In Logs, filter by Client ID for one agent's traffic, by Collection and Status code 4xx for refused payments, or search Payload for payment_exceeds_consent. Changes to these clients, profiles and policies land in Audit Logs, visible only to the Auditor role (the shipped config also maps admin into OIDC_AUDITOR_ROLES; remove it for strict separation). Request logs show what agents did; audit logs show who changed what they may do. See observability and security.
A checklist before agents touch payment APIs
- Agent traffic runs on its own client, never shared with a human-facing app.
- Account information agents use
GET-only profiles, and a testPOSTreturns405. - No Gateway MCP assistant has the payments collection, and
list_collectionsconfirms it. - The payment workflow validates shape, looks up consent with a timeout, fails closed and denies by default.
- Every PISP client, agent or app, passes the same consent checks, with no added customer step.
- Nothing in the agent path can mark a consent authorised.
- Each agent client has a loop-catching policy, and the agent honours
Retry-After. - Your payment service rejects a reused idempotency key, so retried submissions cannot execute twice.
- You have rehearsed switching off one agent by setting its profile or client inactive.
What this looks like in practice
Illustrative scenario, not a specific customer deployment.
Consider a retail bank whose largest budgeting TPP launches an assistant on its app's credentials. One evening a parsing bug sends the assistant into a retry loop on the transactions endpoint, the shared policy trips, and the app's human users get 429s. Operations see a spike under the TPP's client but not that an agent caused it. Meanwhile, a contact-centre copilot pilot uses an internal service account that reaches the payment service directly, outside any gateway log.
Afterwards, the TPP accepts a second client for its assistant: same mTLS setup, a GET-only profile for account access, its own policy. When the loop recurs in testing, the agent client hits 429 within a minute and the app is untouched. The copilot moves to Gateway MCP with accounts and products only. In the consent workflow's first week, logs show payment_exceeds_consent denials from one agent client rounding amounts up, which the TPP fixes from the error body alone.
Agents get identities, not exceptions
AI agents in open banking do not need a new gateway or a new consent model at the API layer. They need their own client identities, method and collection scope that matches their job, a payment workflow that checks submissions against held consent and denies by default, policies shaped for loops, and the same request log as everything else. SCA and consent stay with your authorization server and consent service; the gateway checks agent payment submissions against the consent the customer authorised and logs every call that clears the rate limit.
Zerq is an enterprise API gateway built for regulated industries — one platform for API management, AI agent access, compliance audit, and developer portal, running entirely in your own infrastructure. See how it works or request a demo to walk through your specific requirements.