CBA side illustration
CBA Business Banking Supplier and Invoice Register
Holds the approved supplier list and the invoice book for a business banking customer, and refuses any payment instruction naming a counterparty, an account or an amount that does not match what is on file.
This service is an illustration built on the CBA side of the boundary. It is not a Raidiam product. It exists to show what a resource server can demand of an agent, and to show that a refusal can always be explained.
What an agent has to discover
Resourcehttps://rs-supplier.demo.cba.raidiam.io
Authorization serverhttps://commbiz.demo.cba.raidiam.io
Trust anchornot configured
Detail typessupplier_invoice
Scopesopenid, suppliers.read, invoices.settle
Sender constrainingDPoP verified when presented, required for bound tokens
Mutabilityaccepts instructions
Authorization detail types
supplier_invoice
Authority to read the approved supplier list and the invoice book, and to settle an approved invoice. Settlement still needs a separate payment_initiation token addressed to the payments resource, because this resource never forwards the token it was called with.
Required members: type purpose
Optional members: invoice supplier instructedAmount constraints
{
"type": "supplier_invoice",
"purpose": "Settle approved supplier invoices for the August accounts payable run",
"invoice": "INV-1041",
"supplier": "SUP-2201",
"instructedAmount": {
"amount": "8200.00",
"currency": "AUD"
}
}
Token claims this resource decides on
sub required The business banking customer whose supplier list and invoice book are in scope.
root_grant optional The root delegation this call belongs to, recorded on every decision.
act optional The RFC 8693 actor chain, so a refused instruction names the sub agent that made it.
cnf.jkt optional The thumbprint of the key the access token is bound to.
authorization_details[supplier_invoice] required The authority to read the register and to settle from it.
Tools
Guardrails, published in advance
Every call carries a token from the named authorization server
Calls are accepted only with an access token issued by https://commbiz.demo.cba.raidiam.io and addressed to this resource as its audience. A token minted for a different resource is refused even when it is otherwise valid.
Refusal reason missing_access_token, invalid_token · decided at authorization · policy id rs.authenticated_caller
What clears it: Read this metadata document, then request a token from the authorization server it names, with this resource as the audience.
Authority is the RFC 9396 detail type, not a scope
Each tool names one authorization_details type. The token must carry that type, or an umbrella type that narrows to it. Holding a scope, or holding authority for a neighbouring resource, does not admit the call.
Refusal reason insufficient_authority · decided at authorization · policy id rs.authority_gate
What clears it: Obtain a token carrying the detail type the tool names. Delegation only ever narrows, so the delegating envelope must already contain it.
A revoked delegation stops working before its tokens expire
Revocation arrives as a Shared Signals event and is applied to the delegation, not to a single token. Every token issued under a revoked delegation is refused from that moment, whatever its expiry says.
Refusal reason delegation_revoked · decided at authorization · policy id rs.revocation_honoured
What clears it: The customer must grant a fresh delegation. There is no way to appeal a revocation at the resource.
Sender constrained tokens are bound to the key that holds them
A DPoP proof is verified whenever one is presented, and is required whenever the access token names a key in its cnf.jkt claim. Each proof is accepted once, so a captured proof cannot be replayed.
Refusal reason dpop_proof_required, invalid_dpop_proof, dpop_key_mismatch, dpop_proof_replayed, access_token_not_dpop_bound · decided at authorization · policy id rs.dpop_binding
What clears it: Request the access token with a DPoP proof so the authorization server binds it to your key, then send a fresh proof with every call.
A malformed request is refused with the reason it was malformed
Arguments are validated before any business rule runs, and the refusal names the argument at fault rather than returning a bare failure.
Refusal reason tool_error, invalid_amount, unknown_tool · decided at execution · policy id rs.request_validity
What clears it: Correct the named argument. The tool schemas are published in this document.
Money moves only to a counterparty on the approved supplier list
A supplier joins the approved list through a human onboarding process that confirms its bank details by callback to a number already on file. An agent may read the list. An agent may not add to it, and no payment instruction naming a counterparty outside it is passed on, whatever authority the agent holds and however plausible the invoice looks.
Refusal reason supplier_not_approved · decided at execution · policy id sup.approved_list
What clears it: None available to an agent. Onboarding a supplier is a human process.
The counterparty paid is the counterparty that issued the invoice
When an instruction names the supplier it believes it is paying, that name is checked against the invoice record. An agent that has been fed a substituted counterparty is refused on the mismatch rather than on the amount.
Refusal reason counterparty_mismatch · decided at execution · policy id sup.counterparty_matches_invoice
What clears it: Pay the supplier that issued the invoice, or name the invoice the other supplier issued.
The destination account is the one confirmed by callback
Matching the supplier name is not sufficient. The BSB and account number on the instruction are checked against the details recorded against that supplier. Substituting the account while keeping the name correct is the harder version of the attack, and it is refused here.
Refusal reason payee_account_mismatch · decided at execution · policy id sup.payee_account_on_file
What clears it: None available to an agent. Bank details change only through onboarding, with a callback to a number already on file.
Every payment settles a real invoice
An instruction that names no invoice in the book is refused. Accounts payable settles invoices, so a payment with nothing behind it has nothing to settle.
Refusal reason unknown_invoice · decided at execution · policy id sup.invoice_must_exist
What clears it: Read the invoice book and name an invoice from it.
Only an invoice the business has approved is settled
An invoice awaiting approval or in dispute is not settled by an agent, even when the supplier is approved and the amount is within every limit.
Refusal reason invoice_not_approved_for_payment · decided at execution · policy id sup.invoice_approved_for_payment
What clears it: The business approves the invoice first. That is a human step.
The amount paid is the amount owed
An accounts payable agent settles an invoice, it does not decide what the invoice is worth. An instruction for an amount other than the invoice total is refused.
Refusal reason invoice_amount_mismatch · decided at execution · policy id sup.amount_matches_invoice
What clears it: Instruct the invoice total, or raise the difference with the supplier as a credit note.
An invoice is settled once
Sub agents run in parallel and can be handed overlapping work. An invoice already settled is refused a second time here as well as at the payments resource, so a duplicate is caught before it reaches the payment rail.
Refusal reason invoice_already_settled · decided at execution · policy id sup.settle_once
What clears it: None. The invoice is paid.
Settlement carries its own audience scoped token
Settling an invoice means calling the payments resource, and this server never forwards the token it was called with. The caller supplies a separate payment token addressed to the payments resource, and the payments resource applies its own envelope and single payment limit on top of everything decided here.
Refusal reason payment_token_required · decided at execution · policy id sup.separate_payment_token
What clears it: Obtain a payment_initiation token addressed to the payments resource and pass it as payment_token.
Observability
Every admission decision, allowed and refused, is recorded with the policy that decided it and the values it turned on. Read them at /decisions.