Cookbook
Build a generation integration
If your product generates advice with an LLM on behalf of regulated firms, you can attest each generation into that firm's ledger. Integrate once, and every firm you serve gets a complete, tamper-evident record of how their advice was produced.
Who this is for
This guide is for tool builders: paraplanning platforms, suitability-report generators, adviser copilots, and any product that calls an LLM to draft financial advice on behalf of regulated firms. If you are a firm attesting your own in-house generation, you do not need any of this: use your own API key directly as shown in Attestation.
The model
A generation belongs to the firm that gave the advice, not to your product. So every attestation you send is written into that firm's ledger. You attest on the firm's behalf, and the platform attributes the record to the firm and the named adviser, never to you.
Connecting a firm
The connection uses an OAuth-style authorisation grant, the same pattern the platform's own CRM integrations use. Your product is registered once as a connected app; each firm then authorises it individually.
- Register your product in the developer portal. You receive a
client_idandclient_secret. - Send the firm to the platform's authorisation screen, requesting the
generations:writescope. A firm admin approves the connection. - The platform returns an authorisation code to your callback. Exchange it for a per-firm refresh token bound to your app and that firm.
- Exchange the refresh token for short-lived access tokens as needed. Each token is write-only and can only append generations to that firm's ledger.
The firm can revoke your product at any time from their settings, which invalidates the grant immediately without affecting any other credential.
Least privilege
Credentials issued through the grant carry only the generations:write scope. They cannot read ledger records, issue certificates, manage webhooks, rotate keys, or see any other firm's data.
Attesting on a firm's behalf
Initialise the SDK with the firm's access token. The adviser is who the platform records as the actor on the ledger record, so it must be the individual responsible for the advice.
import { Bedrock } from '@bedrockgovernance/attest';
// One client per connected firm, using that firm's access token.
const bedrock = new Bedrock({
accessToken: await tokenStore.getFirmToken(firmId),
});
await bedrock.attest({
clientReference,
correlationId, // your conversation/session id, reused across the conversation
adviser: { name: adviser.name, fcaRef: adviser.fcaRef },
model: { provider: 'anthropic', name: 'claude-opus-4-8' },
instructions,
input,
retrievedContext,
output: { content: draft, finishReason: 'stop' },
});Because you serve many firms from one codebase, wire the attestation in once at your generation boundary and pass the firm context through. If you use the AI SDK, the middleware does this for you. Resolve the firm token and adviser per request:
import { wrapLanguageModel } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { attestMiddleware } from '@bedrockgovernance/attest/ai-sdk';
function modelForRequest({ firmId, clientReference, correlationId, adviser }) {
const bedrock = new Bedrock({ accessToken: tokenStore.getFirmToken(firmId) });
return wrapLanguageModel({
model: anthropic('claude-opus-4-8'),
middleware: attestMiddleware({ bedrock, clientReference, correlationId, adviser }),
});
}Grouping generations
A piece of advice is usually several generations: a draft, a regenerated section, a revision, and often more than one drafting conversation. Pass a stable id you already hold for the conversation, a chat, thread, or session id, as correlationId on every attest() call in it, so those records form one set in the firm's ledger.
You do not need to carry that id through to review. The platform associates a firm's conversations with the advice they informed from the client reference, the responsible adviser, and the timeline, so the review of a piece of advice surfaces every generation behind it. Order within a conversation is optional: set supersedes to name the generation a revision replaces, or leave it and rely on ledger order.
The platform stamps an outputHash on every generation, so each output stays independently tamper-evident once it is hashed into the chain.
Checklist
- Register once as a connected app; request only
generations:write. - Store per-firm refresh tokens, never raw firm API keys.
- Identify the responsible adviser when you have them, so the record names an individual rather than the firm credential.
- Reuse your conversation id as
correlationIdon every generation in a drafting conversation; it does not need to reach review. - Send prompts, context, and output verbatim, so the platform hashes them into the chain.
- Use an idempotency key so retries do not duplicate records.