A practitioner's guide to hosting AI for customer financial data in the UK. It takes one real-world shape of workload, classifying bank transactions plus reading receipts and answering chat questions, and works through how AWS Bedrock and Microsoft Azure bill, where each actually processes data, what every route costs at three volumes, how to cut the bill with rules, memory, prompt caching and batch, how to test quality before price, when a dedicated GPU pays off, and the governance checks that should sit around all of it. Workflows and code are included throughout.
This technote is the companion to our blog post Private AI hosting in the UK: Bedrock, Azure or your own GPUs?. Prices are approximate list prices as of November 2026, in US dollars unless stated, with £1 = $1.30 and €1 = $1.15. Some come from third-party price trackers because the providers' own pricing pages do not always render their tables to automated tools. Re-check every figure in the provider's calculator before you commit.
1. The workload and the data rule
The worked example is a UK finance application: a bookkeeping or tax product that imports customers' bank transactions and receipts. Most of the AI spend is classification: one call per transaction, with about 3,600 input tokens (the category list, the customer's rules and a few examples, plus the transaction) and about 250 output tokens. Measure your own figures with an LLM tracing tool such as Langfuse before you trust any estimate, including ours.
| Job | How often it calls the AI | Needs |
|---|---|---|
| Classify bank transactions | Once per transaction | About 3,600 tokens in, 250 out; the bulk of the volume |
| Read scanned PDFs and receipts | Once per page or image | A model that reads images |
| Chat assistant for customers | Once per message | A strong conversational model; latency matters |
| Import fallback for unreadable files | Only when rules fail | Same path as classification |
The data rule that drives everything: customer financial data must not go to public consumer AI services. It must be processed in the UK, or at least the EU, with no training on it and no long retention. Write that rule down, have your data protection lead own it, and make the code enforce it (section 8).
2. Idle cost: what actually bills when nothing is happening
A common instinct is to switch AI services off at night to save money. That only works for services billed by the hour. Here is how each model bills:
| Billing model | Bedrock name | Azure name | Cost when idle | Use it when |
|---|---|---|---|---|
| Pay per token | On-demand | Standard / Global Standard / Data Zone Standard | Nothing | Almost always, at SME volumes |
| Reserved capacity | Provisioned Throughput (via AWS sales) | Provisioned Throughput Units (PTU) | Full hourly rate, used or not | Very high, steady volume or guaranteed latency |
| Batch | Batch inference (about 50% off) | Batch deployments (discounted) | Nothing | Work nobody is waiting on, such as backfills |
| Prompt caching | Cache read at about 0.1x input price (Claude) | Depends on model | Nothing | Any prompt with a long repeated prefix |
| Rented GPU | Your own model server on a cloud or colocated GPU | Full monthly rate | Very high volume, or data that must never leave your control | |
So on any pay-per-token route, the on/off switch decides where data goes, not what you pay. The supporting infrastructure is cheap too: on AWS, IAM roles, configuration and an empty log group cost nothing, and a single CloudWatch alarm is about $0.10 a month.
3. The cost model
Every per-token route reduces to one formula:
cost per call = input_tokens × input_price + output_tokens × output_price
# Claude Haiku on Bedrock EU, ~$0.11 in / $0.55 out per million tokens
= 3,600 × 0.11 / 1,000,000 + 250 × 0.55 / 1,000,000
= $0.000396 + $0.000138
≈ $0.00053 per call → ~$53 per 100,000 calls
| Route | Processed in | Price per 1M tokens (in / out) | 10k / month | 100k / month | 1M / month |
|---|---|---|---|---|---|
| Amazon Nova Micro, Bedrock London | UK | ~$0.049 / $0.196 | ~$2 | ~$23 | ~$225 |
| Amazon Nova Lite, Bedrock London | UK | ~$0.084 / $0.336 | ~$4 | ~$39 | ~$386 |
| Claude Haiku 5.5, Bedrock EU profile | EU | ~$0.11 / $0.55 | ~$5 | ~$53 | ~$534 |
| Claude Haiku 5.5, Microsoft Foundry | Not pinned | ~$0.10 / $0.50 | ~$5 | ~$49 | ~$485 |
| GPT-5 mini, Azure OpenAI UK South | UK (after approval) | ~$0.28 / $2.20 | ~$16 | ~$156+ | ~$1,558+ |
| Civo relaxAI | UK | from £0.10 / £0.60 | ~$7 | ~$66 | ~$663 |
| Qwen3-32B, Nebius | EU or US (fixed only on a dedicated endpoint) | ~$0.10 / $0.30 | ~$4 | ~$44 | ~$435 |
| Mistral Small 3.2, Scaleway | France | €0.15 / €0.35 | ~$7 | ~$72 | ~$722 |
| Rented L40S GPU, London | UK (your model) | flat | $942 | $942 | $942 |
| Your own GPU server | Your premises | none per call | Hardware, power and upkeep only | ||
Two cautions. GPT-5 mini is a reasoning model: hidden reasoning tokens bill as output, so the real figure is above the one shown. And the cheapest route is only cheap if it classifies correctly; a model that misfiles one transaction in ten creates support and correction work that dwarfs the token bill. Section 9 shows how to test.
4. AWS Bedrock in detail
4.1 Where Bedrock processes data
Bedrock offers three ways to call a model, and they decide residency:
- In-region: the request is processed in the region you call. Amazon Nova Micro and Nova Lite are available in-region in Europe (London),
eu-west-2. This is the UK-only route. - Geographic cross-region inference profiles (IDs prefixed
eu.): the request may be processed in any region within that geography. Claude Haiku through the EU profile stays in EU regions but is not pinned to the UK. - Global profiles: processed wherever capacity is. Not suitable for this data rule.
A frequent mistake is assuming that "an EU region" means "the UK". Stockholm (eu-north-1) or Frankfurt is EU locality, not UK locality; describe it accurately in your documentation and your privacy notice. AWS states that Bedrock does not use prompts and completions to train AWS models or share them with model providers; model invocation logging is off unless you enable it.
4.2 Calling Nova in London with a cached prefix
The Bedrock Converse API gives one request shape for Nova and Claude. A cachePoint after the system prompt marks the repeated prefix for prompt caching on models that support it:
import json, boto3
brt = boto3.client("bedrock-runtime", region_name="eu-west-2") # London
SYSTEM_PROMPT = open("classify_system_prompt.txt").read() # categories, rules, examples
def classify(txn: dict, model_id="amazon.nova-micro-v1:0") -> dict:
resp = brt.converse(
modelId=model_id, # in-region model: UK only
system=[{"text": SYSTEM_PROMPT}, {"cachePoint": {"type": "default"}}],
messages=[{"role": "user", "content": [{"text": json.dumps(txn)}]}],
inferenceConfig={"maxTokens": 300, "temperature": 0},
)
usage = resp["usage"] # inputTokens, outputTokens, cacheReadInputTokens, cacheWriteInputTokens
answer = json.loads(resp["output"]["message"]["content"][0]["text"])
return {"answer": answer, "usage": usage}
For Claude through the EU profile, the call is the same with the EU inference profile ID for the model as modelId, made from one of the profile's EU source regions. Check the profile's current source and destination regions in the Bedrock console before you write them into a privacy notice.
4.3 Enforcing the region in IAM
Do not rely on code alone to keep requests in region. Grant the application role only the models and regions the data rule allows. For a UK-only service:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeUkOnlyModels",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:eu-west-2::foundation-model/amazon.nova-micro-v1:0",
"arn:aws:bedrock:eu-west-2::foundation-model/amazon.nova-lite-v1:0"
]
}
]
}
An EU-allowed service adds the inference profile ARN and the foundation-model ARNs in each of the profile's destination regions, which is exactly the list your privacy notice should name.
4.4 Things that cost money for no benefit
- Private network endpoints (AWS PrivateLink interface endpoints) cost roughly $7 to $8 per endpoint per Availability Zone per month. They only help if your application servers run inside AWS. If they run elsewhere, traffic still crosses the internet to reach the VPC, so leave them off and use TLS with IAM authentication.
- Provisioned Throughput is billed every hour whether used or not. At SME volumes on-demand is cheaper.
Do add an AWS Budgets alert, for example at $20 a month, so a retry loop or a runaway backfill pages someone before the invoice does.
5. Microsoft Azure in detail
5.1 Deployment types and residency
Azure's per-token deployments come in three residency scopes: Global Standard (processed in any Azure region), Data Zone Standard (processed within a data zone such as the US or the EU), and regional Standard (processed in the deployment's region). Reserved capacity is PTU, billed hourly.
- Claude on Microsoft Foundry is available as Global Standard or a US data zone. At the time of writing there is no EU data zone for Claude, and Microsoft's documentation notes that data may be processed outside the region you choose. That rules it out under an EU-or-UK rule, whatever its price.
- Azure OpenAI in UK South is the only route on either cloud to OpenAI models processed in the UK. Customers report that it needs Microsoft's approval, can start with zero quota, and that older models are retired on a schedule, so plan for migrations. GPT-5 mini at about $0.28 in and $2.20 out per million tokens comes to roughly three times the per-transaction cost of Claude Haiku, before reasoning tokens.
5.2 Calling Azure OpenAI
Azure OpenAI's v1 API accepts the standard OpenAI client with a different base URL, so an application that already speaks OpenAI's format needs only configuration, plus the deployment name in place of a model name:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://<your-resource>.openai.azure.com/openai/v1/",
api_key=os.environ["AZURE_OPENAI_API_KEY"],
)
resp = client.chat.completions.create(
model="<your-uk-south-deployment>",
messages=[{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": txn_json}],
)
Where it fits: organisations already committed to Microsoft contracts, or customers who specifically require OpenAI models processed in the UK. For this workload and rule, it costs more and offers less certainty of access than Bedrock.
6. UK and EU alternatives
| Option | Processed in | Notes |
|---|---|---|
| Civo relaxAI | UK data centres | UK company; OpenAI-compatible API from £0.10 / £0.60 per million tokens; open models, so test quality |
| Scaleway Generative APIs | France | OpenAI-compatible; Mistral and other open models; free starter allowance; batch discount |
| Nebius Token Factory | EU or US | Shared endpoints choose their region; use a dedicated endpoint to fix it, and enable zero data retention |
| Your own GPU server | Your premises or colocation | Open models through Ollama or vLLM; no per-call fee; you own capacity, patching and uptime |
| Rented UK GPU | UK | About $942 a month for an L40S in London; you run the model server |
Most of these speak OpenAI's request format, so one adapter covers them all. A minimal provider registry that carries each route's residency with it (illustrative; base URLs and model names are placeholders):
from dataclasses import dataclass
from openai import OpenAI
@dataclass(frozen=True)
class Route:
name: str
residency: str # "UK", "EU" or "GLOBAL"
base_url: str
model: str
key_env: str
ROUTES = [
Route("own-gpu", "UK", "http://llm.internal:11434/v1", "<open-model>", "LOCAL_LLM_KEY"),
Route("relaxai", "UK", "<relaxai-base-url>", "<model>", "RELAXAI_API_KEY"),
Route("scaleway", "EU", "<scaleway-base-url>", "<model>", "SCW_SECRET_KEY"),
]
ALLOWED = {"UK": {"UK"}, "EU": {"UK", "EU"}}
def routes_for(rule: str) -> list[Route]:
"""Only routes the customer's data rule permits, in preference order."""
return [r for r in ROUTES if r.residency in ALLOWED[rule]]
The point of the design is that a route without a declared residency cannot be selected, and a misconfigured fallback cannot quietly send UK-only data to an EU or global endpoint.
7. Make fewer, smaller calls
The cheapest AI call is the one you do not make. In order of impact:
- Rules first. Payee names, merchant category codes and simple patterns classify a large share of transactions with no model at all. Log how many each run matches so you can see the share grow.
- Per-customer memory. The same supermarket, landlord or software subscription appears every week or month. Store each customer's confirmed answers and reuse them for repeat payees; call the model only when the payee is new or the amount pattern changes.
- Prompt caching. Each classification resends the same category list, rules and examples. If about 3,000 of the 3,600 input tokens are a stable prefix, reading it from cache at 0.1x the input price cuts Claude Haiku on Bedrock EU from about $53 to about $24 per 100,000 calls. Two conditions: the prefix must be byte-identical between calls (put anything variable after it), and it must exceed the model's minimum cacheable length, which can be several thousand tokens on smaller models. Measure the repeated share first.
- Shorter prompts. Cost scales with length. Prune examples that do not change accuracy on your golden set.
- Batch overnight work. Reclassifying history after a category change is perfect batch work at about half price.
| Step | Calls per 100k transactions (example) | Monthly cost, Haiku EU (example) |
|---|---|---|
| Every transaction to the model | 100,000 | ~$53 |
| Rules match 40% | 60,000 | ~$32 |
| Memory reuses half of the rest | 30,000 | ~$16 |
| Prompt caching on the remainder | 30,000 | ~$7 |
The percentages are illustrative; your logs will give the real ones. The shape is what matters: each layer removes work before the more expensive one runs.
8. Enforce residency, not just document it
- Per-customer data rule stored with the account (UK or EU), read by the router on every call.
- Allow-list of routes with declared residency (section 6); unknown routes rejected.
- Cloud-side enforcement: IAM limited to the permitted model ARNs and regions (section 4.3); on Azure, only regional or EU data-zone deployments in the subscription.
- No training and minimal retention: confirm each provider's terms; enable zero data retention where it is an option; keep your own logs of prompts out of general-purpose log stores, or redact them.
- Kill switch per route: an admin setting that removes a route from the chain instantly, without a deploy.
- Trace every call: route, model, region, input, cached and output tokens, cost and latency, so finance and the data protection lead see the same numbers.
- Budget alerts on every cloud account and every API key.
- Document it accurately in your privacy notice and records of processing: name the regions, not just "Europe".
9. Test quality before price
Build a golden set of about 200 real transactions with known right answers, weighted towards the hard cases: refunds, transfers between a customer's own accounts, split payments and ambiguous payees. Run every route your residency rule allows, including your own GPU model as a baseline, with the same prompt and temperature 0. A minimal harness (illustrative):
import csv, time, statistics
def evaluate(route, golden_csv, classify, price_in, price_out):
rows = list(csv.DictReader(open(golden_csv)))
right, costs, latencies, schema_fail = 0, [], [], 0
for row in rows:
t0 = time.perf_counter()
try:
out = classify(row, route) # returns {"answer": {...}, "usage": {...}}
except ValueError: # unparseable or off-schema answer
schema_fail += 1
continue
latencies.append(time.perf_counter() - t0)
right += out["answer"]["category"] == row["expected_category"]
u = out["usage"]
costs.append(u["inputTokens"] * price_in / 1e6 + u["outputTokens"] * price_out / 1e6)
n = len(rows)
return {
"route": route,
"accuracy": right / n,
"schema_failures": schema_fail,
"cost_per_1k": 1000 * statistics.mean(costs) if costs else None,
"p95_latency_s": statistics.quantiles(latencies, n=20)[18] if len(latencies) >= 20 else None,
}
Pick the cheapest route that meets your accuracy bar overall and on the hard categories, and send low-confidence answers to a stronger model or a person. Ship it in shadow beside the current route first, then switch with the old route kept as a fallback. Keep the golden set: it becomes the regression test you re-run whenever a provider retires a model or you change the prompt.
10. When a dedicated GPU pays off
A rented UK GPU at about $942 a month only matches Claude Haiku on Bedrock EU at roughly 1.8 million classifications a month, and does not match Nova Micro within two million. Below that, pay-per-token is cheaper and costs nothing when idle. The sum changes in three cases:
- You already own the hardware. Then only power and upkeep count, and a capable open model on a workstation-class GPU can serve classification at near-zero marginal cost.
- Data must never leave your control, for contractual or regulatory reasons, regardless of cost.
- The GPU serves several workloads: classification, document reading, embeddings and internal assistants together can justify a dedicated server well below 1.8 million calls.
Operating your own model server means owning capacity planning, model updates, monitoring and failover. Keep a per-token route in the chain as a fallback so a GPU fault degrades service rather than stopping it.
11. Decision checklist
- Data protection lead decides the rule: UK only, or EU allowed.
- Measure real token counts and the repeated-prefix share with tracing.
- Run the golden set across every allowed route; choose by accuracy first, then cost.
- Add rules, per-customer memory and prompt caching before scaling volume.
- Deploy with IAM or subscription-level region enforcement, a per-route kill switch and budget alerts; leave private endpoints and reserved capacity off unless your servers live in the same cloud and your volume justifies them.
- Document the regions in the privacy notice and records of processing.
- Re-check every price in the providers' own calculators, and re-run the golden set when a provider changes or retires a model.
12. How Workstation can help
We help UK organisations run AI on sensitive data: choosing and wiring residency-aware routes on Bedrock, Azure and UK providers, building the evaluation harness and golden sets, adding caching and rules to cut cost, and supplying or hosting GPU servers when owning the model makes sense. See our GPU private cloud guide and VRAM sizer for the hardware side, compare leasing and buying at gpuleasing.co.uk, or talk to us.
Assumptions and sources
- Workload: about 3,600 input and 250 output tokens per classification; measure your own.
- Prices: approximate November 2026 list prices from provider pages and third-party trackers; exchange rates £1 = $1.30, €1 = $1.15. Rented GPU: about $942 a month for an NVIDIA L40S in London.
- AWS: Amazon Bedrock pricing (on-demand, batch, provisioned throughput), Amazon Nova model cards and regional availability (Nova Micro and Nova Lite in-region in London), Bedrock cross-region inference documentation, AWS PrivateLink pricing.
- Anthropic: Claude pricing, including Claude Haiku 5.5 base, cache-read and batch rates.
- Microsoft: Foundry deployment types (Global Standard, Data Zone Standard, PTU, Batch) and Claude in Foundry documentation; InfoQ, "Claude Reaches GA on Microsoft Foundry: European Enterprises Cannot Deploy It" (July 2026); Microsoft Q&A threads on UK South access and model retirement.
- Civo: relaxAI API launch announcement (UK deployments from £0.10 / £0.60 per million tokens). Scaleway Generative APIs pricing. Nebius Token Factory documentation on regions and zero data retention.
- All code is illustrative and must be adapted, reviewed and tested before use.
Acknowledgement of rights: Amazon Web Services, AWS, Amazon Bedrock and Amazon Nova are trademarks of Amazon.com, Inc. or its affiliates. Microsoft, Azure and Microsoft Foundry are trademarks of the Microsoft group of companies. Claude is a trademark of Anthropic, PBC. OpenAI and GPT are trademarks of OpenAI. NVIDIA and L40S are trademarks of NVIDIA Corporation. Other product and company names are the property of their respective owners.