Skip to content
decosa
PreviewHostedSelf-hostSelf-host first for real data

Check a bank-detail change before you pay

The warning signs with evidence, the call-back to the number already on file, and a signed record with a second approver.

On production8.1 smedian on production (2026-09-29); slower when the service is busy
List price~$0.053 per 100 emailsmeasured, at list price

Built on: Signed record

Loading the tool…

Use it your way

Use it from your codeThe hosted API with your key, and prompts to paste into a coding agent
Self-host · your GPUs · recommended

Run it yourself, on request

  • The same open models and app, on 1x RTX PRO 6000 (96 GB) for Qwen3.8-27B; the header and vendor-file checks run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Hosted · by Decosa

Get an API key

  • Call the vendor bank-change check API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Synthetic, public or test data only: real confidential data belongs on your own hardware.

Build with it

Paste one of these into Claude Code, Codex or another coding agent. The first wires your project to the hosted API with your DECOSA_API_KEY. The second pulls our containers and runs the same stack on your own GPU, with no Decosa charges.

Base URL
https://api.decosa.ai
Auth
Authorization: Bearer $DECOSA_API_KEY (or a demo session token)
Tool id
bank-change-check

Use the hosted API

# The Decosa bank-detail change check: use the hosted API

You are wiring the Decosa bank-detail change check into this project. It returns:
- the email's sender, Reply-To, Return-Path and authentication results against your own vendor file;
- look-alike domains, numbers and addresses not on file, a bank in another country, an account holder who isn't the vendor;
- pressure, secrecy, "don't call" and redirected payments, quoted from the email;
- the call-back plan to the number already on file, and a signed record the call-back and a second approver complete.

Every model call has a signed receipt. Use only what is listed below; if you need something else, stop and ask me.

- Base URL: `https://api.decosa.ai`
- Health check: `GET https://api.decosa.ai/healthz`.
- **Hosted use is for made-up files only.** Real files belong on a self-hosted box (see the self-host prompt) or confidential access. Say so wherever this is wired in.
- It never says an email is safe and never releases, blocks or schedules a payment. Every bank-detail change is held for a call-back to the number on file.

## Auth: API key (or a demo session)
1. Preferred: an API key (`dk_…`) from "Get an API key" on the tool page. Keep it in `DECOSA_API_KEY`, never in code. Send `Authorization: Bearer $DECOSA_API_KEY`.
2. Without a key: `POST https://api.decosa.ai/demo/session` with `{"vertical": "bank-change-check"}` returns `{"token", "expires_at", "budget"}`. Demo sessions are limited per IP per hour; over a limit you get HTTP 429 with `Retry-After`. A token runs one request at a time (409 otherwise).

## Endpoints
- `POST /bank-change/check` (token). Body: `{eml: "<the whole message>", vendors_csv: "<your vendor file as CSV>", mode?: both|rules}` (`rules` needs no GPU), or `{"sample_id": "..."}` for a sample from `GET /bank-change/samples`. Add `"stream": true` (or `Accept: text/event-stream`) for events; the last one is `result`, which carries `usage` (tokens and cost at list price).
- `GET /bank-change/info`: what is checked, sources, limits, not checked.
- `POST /record/verify` (no auth): re-check the signed record.

- `POST /bank-change/signoff` (token): `{record, callback: {caller, number_dialled, spoke_with?, outcome}, approver?: {name}}`. Refused when the number dialled isn't the number on file or the approver is the caller.

Show the result's own words to the user: the verdict first, then each item with its quote and where it is. Never add a value, a recommendation or a "safe" of your own.

Run it yourself (containers)

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

# The Decosa bank-detail change check: run it yourself (containers)

You are setting up the Decosa bank-detail change check on this machine, so the files never leave it. It returns:
- the email's sender, Reply-To, Return-Path and authentication results against your own vendor file;
- look-alike domains, numbers and addresses not on file, a bank in another country, an account holder who isn't the vendor;
- pressure, secrecy, "don't call" and redirected payments, quoted from the email;
- the call-back plan to the number already on file, and a signed record the call-back and a second approver complete.

Nothing is sent to Decosa's hosted API.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with "not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Ask me before any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/bank-change-check.zip (4 KB, 8 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py bank-change-check` (the api image carries the same bundle under /app/rehearsal/bank-change-check/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py bank-change-check --bundle bank-change-check.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the bank-detail change is detected", "the look-alike domain is flagged in code", "the Hong Kong account is flagged against the vendor's country"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin (docs.docker.com/engine/install), and the NVIDIA container toolkit; check `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Keep the `llm` service (Qwen3.8-27B on vLLM) and the `api` service; on `api` set `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`; keep its data on a named volume; bind every port to 127.0.0.1.
3. Pull and start: `docker compose pull && docker compose up -d`. Wait for the `llm` health check (the first start downloads about 20 GB of weights).
4. Check: `curl -fsS http://127.0.0.1:<PORT>/bank-change/info` answers; `GET /attest/signing-key` shows this box's public key.
5. Smoke test: get a token with `POST /demo/session {"vertical":"bank-change-check"}`, then `POST /bank-change/check {"sample_id": "lookalike-urgent"}`. Expect:
   - `.change_request.present` is `true`;
   - `.cues[].cue` includes `lookalike_domain` and `bank_country_mismatch`;
   - `.callback.number_on_file` is `"+1-312-555-0142"` and `+1-312-555-0199` is under `do_not_use`;
   - the verdict never calls the email safe; every receipt has `"status": "attested"` and the record verifies.

Finish with a summary and these reminders:
- AP email holds vendor bank details: keep it on this machine; the model route stays local (`direct`), and no domain look-ups are made.
- It never says an email is safe and never releases, blocks or schedules a payment. Every bank-detail change is held for a call-back to the number on file.
Run it on your own hardwareWhat it needs, and the prompt that sets it up

Run it on your own GPU

Same app, same pinned models, your hardware. Nothing goes to our servers and there are no Decosa charges.

  • CPU only, 64 GB RAMDoesn't fit

    Qwen3.8-27B (NVIDIA NVFP4) needs a GPU.

  • GeForce RTX 4090standard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with A community 4-bit build of Qwen3.8-27B (AWQ or GGUF). This build is NVIDIA NVFP4, which needs a Blackwell GPU. (Memory is an estimate.)

  • GeForce RTX 5090standard tierRuns

    The standard tier fits with changes: Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

  • 2x GeForce RTX 5090standard tierRuns

    The standard tier fits with changes: Split the language model across the GPUs with tensor parallelism (vLLM --tensor-parallel-size).

  • L40Sstandard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • H100 80 GB (SXM)standard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • RTX PRO 6000 Blackwell 96 GBstandard tierRuns

    The standard tier fits (57.6 of 96 GB).

  • 2x RTX PRO 6000 Blackwell 96 GBbest tierRuns

    The standard tier fits (57.6 of 192 GB). The best tier fits too.

  • Apple M3 Ultra (Mac Studio), 96 GBstandard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B MLX 4-bit. MLX build for Apple Silicon.

  • Apple M5 Max, 64 GBstandard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B MLX 4-bit. MLX build for Apple Silicon.

Memory per component comes from measured footprints, the tool's stack.json, or an estimate from its parameter count, and each is labelled that way below. Only an RTX PRO 6000 and an M3 Ultra Mac Studio have actually been run.

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

  1. 1

    Check the GPU, Docker and the NVIDIA Container Toolkit

    The driver must see the GPU, and Docker must be able to pass it into a container.

    nvidia-smi
    docker compose version
    docker run --rm --gpus all ubuntu nvidia-smi
  2. 2

    Fetch the compose file

    One file describes the API and the language model as services.

    mkdir -p ~/decosa && cd ~/decosa
    curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml
  3. 3

    Pull and start

    The first start downloads pinned model weights, tens of gigabytes.

    docker compose pull
    docker compose up -d
  4. 4

    Check health

    Wait until the API reports ok with the language model loaded. Then point your app at the local base URL.

    curl -fsS http://localhost:<PORT>/healthz
    # {"ok": true, "llm": true, ...}
    curl -fsS -X POST http://localhost:<PORT>/demo/session \
      -H 'Content-Type: application/json' -d '{"vertical":"bank-change-check"}'

Set up with a coding agent, rehearse on mock data, then go private

  1. Set up with a coding agent. Paste the self-host prompt into a coding agent on the machine that will run the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works.
  2. Rehearse on mock data. The agent runs the tool on a bundle of synthetic inputs and checks each answer against the bundle's expected.json. Every check must print PASS.
  3. Go private. Only then do you run your own data against the local API, yourself, on that machine. Never give the agent real data during setup: a coding agent that runs in the cloud sees everything in its context, so keep real data out of the chat and out of the files it reads.
Rehearsal command
docker compose exec api python scripts/rehearse.py bank-change-check

Download the mock-data bundle (4 KB, 8 checks)expected.json

A synthetic email from a look-alike of a made-up supplier's domain, with a free-mail Reply-To, a failed DMARC check, urgency, secrecy, 'don't call' and a Hong Kong account in another company's name, checked against a made-up vendor file. The change must be detected, the code signs flagged, the call-back must name the number on file (not the one in the email), and the signed record must verify.

What the rehearsal checks
  • the bank-detail change is detected
  • the look-alike domain is flagged in code
  • the Hong Kong account is flagged against the vendor's country
  • the call-back names the number on file
  • the number in the email is listed as one not to use
  • the signed record verifies
  • a record with its warning-sign count changed no longer verifies
  • the model call has a signed receipt

Licence: Synthetic: the company, vendors, people, domains (.example) and accounts are invented. Part of decosa-api.

Prompt for your coding agent

# The Decosa bank-detail change check: run it yourself (containers)

You are setting up the Decosa bank-detail change check on this machine, so the files never leave it. It returns:
- the email's sender, Reply-To, Return-Path and authentication results against your own vendor file;
- look-alike domains, numbers and addresses not on file, a bank in another country, an account holder who isn't the vendor;
- pressure, secrecy, "don't call" and redirected payments, quoted from the email;
- the call-back plan to the number already on file, and a signed record the call-back and a second approver complete.

Nothing is sent to Decosa's hosted API.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with "not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Ask me before any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/bank-change-check.zip (4 KB, 8 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py bank-change-check` (the api image carries the same bundle under /app/rehearsal/bank-change-check/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py bank-change-check --bundle bank-change-check.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the bank-detail change is detected", "the look-alike domain is flagged in code", "the Hong Kong account is flagged against the vendor's country"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin (docs.docker.com/engine/install), and the NVIDIA container toolkit; check `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Keep the `llm` service (Qwen3.8-27B on vLLM) and the `api` service; on `api` set `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`; keep its data on a named volume; bind every port to 127.0.0.1.
3. Pull and start: `docker compose pull && docker compose up -d`. Wait for the `llm` health check (the first start downloads about 20 GB of weights).
4. Check: `curl -fsS http://127.0.0.1:<PORT>/bank-change/info` answers; `GET /attest/signing-key` shows this box's public key.
5. Smoke test: get a token with `POST /demo/session {"vertical":"bank-change-check"}`, then `POST /bank-change/check {"sample_id": "lookalike-urgent"}`. Expect:
   - `.change_request.present` is `true`;
   - `.cues[].cue` includes `lookalike_domain` and `bank_country_mismatch`;
   - `.callback.number_on_file` is `"+1-312-555-0142"` and `+1-312-555-0199` is under `do_not_use`;
   - the verdict never calls the email safe; every receipt has `"status": "attested"` and the record verifies.

Finish with a summary and these reminders:
- AP email holds vendor bank details: keep it on this machine; the model route stays local (`direct`), and no domain look-ups are made.
- It never says an email is safe and never releases, blocks or schedules a payment. Every bank-detail change is held for a call-back to the number on file.

Help me customise for my hardware

Pick your GPU or Mac, or enter its memory. You get the tier that fits, the model swaps it needs, measured speed where we have it, and a setup prompt with those choices written in.

Hardware

GeForce RTX 5090: 32 GB GDDR7, 1,792 GB/s, FP8 and NVFP4. NVIDIA product page

RunsVendor bank-change check on GeForce RTX 5090: use the Standard · the hosted demo, one 96 GB card tier

The standard tier fits with changes: Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

Standard · the hosted demo, one 96 GB card: what changesuses estimates

  • Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
Memory per component
  • Reads the email's text and quotes the pressur...: Qwen3.8-27B (NVIDIA NVFP4). ~57.6 GB (at least ~28 GB), weights 21.4 GB (from stack.json). Qwen3.8-27B NVFP4: Weights 19.9 GiB (21.4 GB), measured (field stack.json). The compose file gives the server 0.60 of a 96 GB card (57.6 GB) so the rest is FP8 KV cache for several sessions. The 28 GB minimum is an estimate: weights plus a short-context KV cache, which is why several stacks list a 32 GB RTX 5090 as 'estimate'. (stack.json lists 57 GB for this component.)

Expected speed

Not measured.

Not measured on this hardware. The only measured setups are an RTX PRO 6000 Blackwell and a Mac Studio M3 Ultra.

Setup prompt for this hardware

The self-host prompt for Vendor bank-change check, with a hardware plan for GeForce RTX 5090 added after Step 0. Loading the full prompt; until then it points your agent at the prompt's URL.

# Set up Vendor bank-change check on my hardware

Fetch https://decosa.ai/prompts/bank-change-check-selfhost.md and follow it (including Step 0: rehearse on mock data first), with the hardware plan below applied.

## Hardware plan for this machine (from https://decosa.ai/self-host/hardware?use=bank-change-check)

Target machine: GeForce RTX 5090 (32 GB of GPU memory; CUDA, FP8 and NVFP4).
Quality tier: Standard · the hosted demo, one 96 GB card (standard). Fit check: runs with changes, about 28 GB of 32 GB used; some memory numbers are estimates, not measurements.

First, check the machine: run `nvidia-smi` (or `rocm-smi`, or `sysctl hw.memsize` on a Mac) and confirm the GPUs and free memory match the line above. If they do not, stop and tell me before pulling anything.

Use these components (the setup below describes the standard tier; change it to match):
- Reads the email's text and quotes the pressur...: Qwen3.8-27B (NVIDIA NVFP4) (nvidia/Qwen3.8-27B-NVFP4), 57.6 GB. Change: Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

GPU placement (set each service's device and its vLLM --gpu-memory-utilization to about the share shown):
- GPU 0: Qwen3.8-27B (NVIDIA NVFP4) ~28 GB (88%); about 4 GB left

During the rehearsal, watch GPU memory. If a model fails to load or runs out of memory, lower its --max-model-len and --max-num-seqs first, then its memory share, and tell me what you changed.

The stack's own component list and compose layout: https://decosa.ai/prompts/bank-change-check-assemble.md

The proof

How we tested itEval results and end-to-end checks, hosted and self-hosted, with dates

Verified end to end

Hosted: verified 29 Sep 2026 · measured 29 Sep 2026: · p50 8.1 s · p95 19 s (19 runs) · ~<$0.001 per run · 1 receipt

Loading the nightly status…

Self-host: not yet verified

Measured cost to run: about $0.053 per 100 emails (hosted, 29 Sep 2026). Self-hosting is free: the code is open and the models are open-weight. You pay only for your own hardware and power.

Known limits (4)
  • Hosted verification ran on our pre-release server through the production gateway, before these routes reached the production API.
  • Measured on 64 synthetic emails written by one author; real business email compromise is messier.
  • A calm email from a real vendor's hacked mailbox that keeps the same bank country passes every check in the email; only the call-back catches it.
  • No domain age, ownership or account-validation look-ups (nothing leaves the box by design).

Eval results, nightly checks and cost per runVerify a run

How it's builtThe steps, the models and what each one checks
Self-host · your GPUs · recommended

Run it yourself, on request

  • The same open models and app, on 1x RTX PRO 6000 (96 GB) for Qwen3.8-27B; the header and vendor-file checks run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Hosted · by Decosa

Get an API key

  • Call the vendor bank-change check API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Synthetic, public or test data only: real confidential data belongs on your own hardware.
The open stack

An email asks to change a vendor's bank details: the warning signs with their evidence, the call-back to the number already on file, and a signed record with a second approver.

For AP clerks, controllers and bookkeepers. It reads the email (headers included) against your own vendor file and lists the warning signs: a look-alike domain, replies routed elsewhere, failed sender checks, a sender who isn't on file, a new phone number, a bank in another country, an account holder who isn't the vendor, pressure, secrecy, "don't call". It always says to hold the change and call the number on file, and records the call-back and a second approver in a signed record. It never says an email is safe and never releases or blocks a payment.

Deployment
Self-host first
Regulatory
Nacha Operating Rules, 2024 risk-management amendments (nacha.org rule pages read 29 Sep 2026): non-consumer Originators must have risk-based processes and procedures reasonably intended to identify ACH entries initiated due to fraud, including entries authorized under False Pretenses ("the inducement of a payment by a Person misrepresenting ... that Person's identity"), reviewed at least annually. Phase 1 from 20 Mar 2026 (ODFIs and originators with 6 million or more ACH entries in 2023); Phase 2 from 19 Jun 2026 (all other non-consumer originators). The signed call-back record documents one such step; it is not a compliance determination, and this is not legal advice.
Architecture
Text description

The email and the vendor file go in. Code checks the sender, Reply-To, Return-Path, authentication results, look-alike domains, numbers not on file and the new bank against the vendor file; the open model quotes pressure, secrecy and 'don't call'. Out come the warning signs with evidence, the call-back plan and a signed record completed by the call-back and a second approver.

Architecture

At a glance

What it checks
Sender, Reply-To and Return-Path against your vendor file; SPF, DKIM and DMARC results; look-alike domains; phone numbers and addresses not on file; the new bank's country (IBAN, SWIFT) and account holder against the vendor; pressure, secrecy, "don't call" and redirected payments, quoted.
What it never does
It never says an email is safe, never edits your vendor file, and never releases, blocks or schedules a payment. The call-back must go to the number on file; the record refuses any other number. A confirmed change needs the vendor's last 4 digits of the new account: if they differ from the email, it is signed as "details differ" and stays on hold. The second approver's name must differ from the caller's; that is a name check only (it catches the same name written another way, such as initials, word order or an email address, not two people who sign for each other).
Data retention
Nothing kept on the server: the email and vendor file live in memory for the request; logs carry counts only. You keep the signed records.
What leaves the box (hosted demo)
The email text goes to Qwen3.8-27B through the Decosa API, whose receipts hold hashes, not text. No domain look-ups are made. Self-hosted, nothing leaves. The hosted demo is for made-up emails.
Model calls per email
One (none in rules-only mode).
Cost per email
A fraction of a cent per email on average at the gateway list price (held-out test). Each run shows its own measured cost.
Also used in
Vendor onboarding (HR and procurement): a new vendor's first bank details go through the same check and call-back.
Quality tiers

Pick the tier for the quality you need

Same app at every tier. What changes is the models, the hardware they need, and whether receipts are signed. Scores are measured with the source named, or marked not measured.

  • Lite

    one 48 GB card

    The same pipeline on a smaller mixture-of-experts model. Faster and cheaper; accuracy on this task unknown.

    Models
    • Gemma 4 26B A4B (instruction-tuned)
    Hardware
    1x L40S or RTX 6000 Ada 48 GB (not measured)
    Quality evidence
    • accuracy on this tasknot measured yet
    Latency
    not measured yet
    Verification
    Proof: partialSelf-host onlyDirect route: calls are attested by the box's key; no gateway receipts.
  • In the hosted demo

    Standard

    the hosted demo, one 96 GB card

    Qwen3.8-27B quotes the content signs; the header, domain and vendor-file checks and the call-back plan are code. One model call per email, receipted.

    Models
    • Qwen3.8-27B (NVIDIA NVFP4)
    Hardware
    1x RTX PRO 6000 Blackwell 96 GB
    Quality evidence
    • fraud flagged / genuine changes flagged, held-out test (19 emails, gateway, run once)9 of 9 / 0 of 8decosa-api docs/evals/bank-change-check.md, measured on our server 2026-09-29, gateway route
    • content signs found, open model vs keyword rules (all 64 emails)43 of 47 vs 23 of 47decosa-api docs/evals/bank-change-check.md, dev and test together
    Latency
    measured: seconds per email on the shared gateway under load (test set), longer at the slow end
    Verification
    Proof: strongHosted: gateway-signed receipt per model call. Self-hosted: attested by the box's key.
  • Best

    DeepSeek-V4-Flash on two more cards

    A larger model for long, multi-claimant files.

    Models
    • DeepSeek-V4-Flash (NVIDIA NVFP4)
    Hardware
    2x RTX PRO 6000 96 GB
    Quality evidence
    • accuracy on this tasknot measured yet
    Latency
    not measured yet
    Verification
    Proof: partialSelf-host onlyNot a hosted model: calls are attested by the box's key only.
  • Needs more compute

    Wanted: the best setup

    two large judges from different families

    DeepSeek-V4-Flash and GLM-5.3-Flash each read the file, and a finding stands when they agree; disagreements go to the reviewer. Claim files stay on your own hardware, never on community providers. Not served yet.

    Models
    • DeepSeek-V4-Flash (NVIDIA NVFP4)
    • GLM-5.3-Flash
    Hardware
    Your own hardware: 2x 96 GB cards for DeepSeek-V4-Flash plus 2x 96 GB for GLM-5.3-Flash, or one Mac Studio with 512 GB holding both 4-bit builds (156 + 165 GB, sizes from our Mac; not run together yet). Estimate.
    Quality evidence
    • accuracy on this tasknot measured yet
    Latency
    not measured yet
    Verification
    No proof yetSelf-host onlyOn your own hardware its calls are attested by the box's key only: not a hosted model there, so no gateway receipts. Never sent to community providers.
    Not served yet. It needs more than one 96 GB card, so it runs on your own bigger box.
Components

Every model in the stack

Models in this stack. Each row has a button that shows its licence, engine, verification and evidence.
ModelDetails
After the session
Reads the email's text and quotes the pressure, secrecy, 'don't call' and redirected-payment signs, and reads the new account's bank and holder; the header, domain and vendor-file checks are codeQwen3.8-27B (NVIDIA NVFP4)nvidia/Qwen3.8-27B-NVFP4 on Hugging Face (opens in a new tab)
27.8B · 57 GBProof: strongIn the hosted demo
Lite tier: the same quoted reading on a 48 GB cardGemma 4 26B A4B (instruction-tuned)google/gemma-4-26B-A4B-it on Hugging Face (opens in a new tab)
25.2B (3.8B active)No proof yetSelf-host only
Best tier: a larger model for long, forwarded email threadsDeepSeek-V4-Flash (NVIDIA NVFP4)nvidia/DeepSeek-V4-Flash-NVFP4 on Hugging Face (opens in a new tab)
284B (13B active) · 192 GBNo proof yetSelf-host only
Other
Second judge, from another familyGLM-5.3-Flashzai-org/GLM-5.3-Flash on Hugging Face (opens in a new tab)
321B (18B active) · about 170 GB (estimate)No proof yetSelf-host only

Around the models

Tools, services and hardware

Tools

Services

  • decosa-api:8445
    ${DECOSA_REGISTRY}/decosa-api:0.1.0

    Email parsing, the header and vendor-file checks, the call-back plan, signing and the HTTP API (/bank-change/*). No GPU. Binds 127.0.0.1 by default.

  • decosa-llm:8000
    ${DECOSA_REGISTRY}/decosa-llm:0.1.0

    vLLM OpenAI endpoint for Qwen3.8-27B. Internal to the compose network.

Hardware

  • 1x RTX PRO 6000 Blackwell 96 GB Fits

    Measured: the hosted demo's Qwen3.8-27B runs on one of these cards on our server.

  • 1x L40S / RTX 6000 Ada 48 GB

    Not measured. FP8 Qwen3.8-27B with a shorter context, or Gemma 4 26B A4B (lite).

  • CPU only Fits

    The date rules, the sums, the header checks, signing and verification need no GPU; reading the text needs the model.

Latency per lane

  • one email, busy shared gateway8.1 s

    Measureddecosa-api docs/evals/bank-change-check.md, measured on our server 2026-09-29, gateway route, test set p50 (p95 18.9 s)

  • one email, keyword rules only (no model)20 ms

    Measureddecosa-api docs/evals/bank-change-check.md, measured on our server 2026-09-29, gateway route, rules mode

Assemble it

Run this exact stack on your machine

Paste into Claude Code / Codex to assemble this stack locally. The prompt checks your GPU, pulls the pinned models, writes the compose file and runs a smoke test.

bank-change-check/assemble-prompt.md160 lines
# Assemble the Decosa bank-detail change check on this machine

You are setting up the Decosa bank-detail change check on this Linux machine for an accounts-payable team or a bookkeeping firm. It returns:
- the email's sender, Reply-To, Return-Path and authentication results against your own vendor file;
- look-alike domains, numbers and addresses not on file, a bank in another country, an account holder who isn't the vendor;
- pressure, secrecy, "don't call" and redirected payments, quoted from the email;
- the call-back plan to the number already on file, and a signed record the call-back and a second approver complete.

Everything is sealed in a signed, hash-chained record anyone can re-check.

Work step by step, show me each command before running anything that needs sudo, and stop if a check fails.

**Before anything else, remind me:**
- AP email holds vendor bank details: keep it on this machine; the model route stays local (`direct`), and no domain look-ups are made.
- It never says an email is safe and never releases, blocks or schedules a payment. Every bank-detail change is held for a call-back to the number on file.

Repeat both points in your final summary.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/bank-change-check.zip (4 KB, 8 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py bank-change-check` (the api image carries the same bundle under /app/rehearsal/bank-change-check/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py bank-change-check --bundle bank-change-check.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the bank-detail change is detected", "the look-alike domain is flagged in code", "the Hong Kong account is flagged against the vendor's country"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## What you are building

| service | image | model | port |
|---|---|---|---|
| `llm` | `${DECOSA_REGISTRY}/decosa-llm:0.1.0` (vLLM 0.29.0, `vllm/vllm-openai@sha256:c2914767605584b6d8f45686b82de173ecc99e781897aa3d0a66dacd72c51ae1`) | `nvidia/Qwen3.8-27B-NVFP4` @ `482ca0f3832238542f8f5295dde86b5f22711d80`, Apache-2.0 | internal 8000 |
| `api` | `${DECOSA_REGISTRY}/decosa-api:0.1.0` (no GPU) | none | `127.0.0.1:8445` |

## 1. Check the GPU, driver and Docker

1. Run `nvidia-smi`. I need one NVIDIA GPU with at least 48 GB and driver 580 or newer.
   - Blackwell (RTX PRO 6000, B200): use the defaults below (NVFP4). This is the measured setup.
   - Hopper (H100/H200) or 48 GB Ada/L40S: set `LLM_MODEL=Qwen/Qwen3.8-27B-FP8` and `LLM_REVISION=main`. On a 48 GB card, also set `LLM_MAX_LEN=32768`. Not measured.
   - Under 48 GB: stop and tell me it will not fit. (The `rules` mode of this tool needs no GPU at all: the `api` service alone runs it.)
2. Check `docker --version`, `docker compose version` and `docker run --rm --gpus all ubuntu nvidia-smi`. If Docker or the NVIDIA Container Toolkit is missing, install them from the official Docker and NVIDIA repositories, then run `sudo nvidia-ctk runtime configure --runtime=docker` and restart Docker.
3. Confirm about 60 GB of free disk.

## 2. Get the images

The images are **on request** while self-host is in early access: ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry (set it as `DECOSA_REGISTRY`), pull access and the compose file. Try `docker pull ${DECOSA_REGISTRY}/decosa-{llm,api}:0.1.0`.
If a pull fails, build from source once the `decosa-api` source is published: in the clone, `docker build -f docker/api/Dockerfile -t ${DECOSA_REGISTRY}/decosa-api:0.1.0 .`, and `docker compose build llm` from its compose file. If neither works, stop and tell me.

## 3. Write the compose file

Create `~/decosa-bankchange/.env`:

```bash
DECOSA_TAG=0.1.0
DECOSA_GPU=0
LLM_MODEL=nvidia/Qwen3.8-27B-NVFP4
LLM_REVISION=482ca0f3832238542f8f5295dde86b5f22711d80
LLM_MAX_LEN=65536
LLM_GPU_UTIL=0.85
DECOSA_SIGNER_NAME="<who signs these records, e.g. Example Agency>"
```

Create `~/decosa-bankchange/docker-compose.yml` with exactly these services:

```yaml
name: decosa-bankchange
x-health: &health
  interval: 15s
  timeout: 5s
  retries: 5
services:
  llm:
    image: ${DECOSA_REGISTRY}/decosa-llm:${DECOSA_TAG}
    deploy: { resources: { reservations: { devices: [ { driver: nvidia, device_ids: ["${DECOSA_GPU:-0}"], capabilities: [gpu] } ] } } }
    ipc: host
    restart: unless-stopped
    volumes: [hf-cache:/root/.cache/huggingface]
    command: ["${LLM_MODEL}", "--revision", "${LLM_REVISION}", "--served-model-name", "qwen3.8-27b",
              "--language-model-only", "--max-model-len", "${LLM_MAX_LEN}", "--gpu-memory-utilization", "${LLM_GPU_UTIL}",
              "--max-num-seqs", "16", "--kv-cache-dtype", "fp8_e4m3", "--speculative-config", '{"method":"mtp","num_speculative_tokens":3}',
              "--seed", "0", "--enable-force-include-usage", "--disable-uvicorn-access-log", "--host", "0.0.0.0", "--port", "8000"]
    healthcheck: { <<: *health, test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=4)"], start_period: 900s }
  api:
    image: ${DECOSA_REGISTRY}/decosa-api:${DECOSA_TAG}
    restart: unless-stopped
    depends_on: { llm: { condition: service_healthy } }
    environment:
      DECOSA_LLM_ROUTE: direct                  # local model only; receipts are signed by this box's key ("attested")
      DECOSA_LLM_URL: http://llm:8000/v1
      DECOSA_LLM_MODEL: qwen3.8-27b
      DECOSA_LOCAL_SIGNING: "on"                # Ed25519 key created at /data/attest/ed25519.pem on first start
      DECOSA_SIGNER_NAME: ${DECOSA_SIGNER_NAME}
      DECOSA_SESSIONS_PER_IP_HOUR: "1000"
      DECOSA_BUDGET_LLM_TOKENS: "200000"
      DECOSA_SESSION_TTL_S: "28800"
      DECOSA_CORS_ORIGIN_REGEX: '^https?://(localhost|127\.0\.0\.1)(:\d+)?$$'
      DECOSA_BANKCHANGE_MAX_CONCURRENT: "6"
    ports: ["127.0.0.1:8445:8445"]
    volumes: [decosa-data:/data]
    healthcheck: { <<: *health, test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8445/healthz', timeout=4)"], start_period: 20s }
volumes: { hf-cache: {}, decosa-data: {} }
```

Use the named volume `decosa-data` exactly as written; a host bind mount owned by root makes the API fail on `/data/keys.sqlite`.
Run `docker compose up -d`, then poll `docker compose ps` until both services are healthy (5-10 minutes the first time). `curl -s localhost:8445/healthz` should show `"llm": true`.

## 4. Smoke test

```bash
API=localhost:8445
TOKEN=$(curl -s $API/demo/session -H 'content-type: application/json' -d '{"vertical":"bank-change-check"}' | jq -r .token)
curl -s $API/bank-change/check -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"sample_id":"lookalike-urgent"}' > /tmp/out.json
jq '{verdict, change: .change_request.present, signs: [.cues[].cue], callback: .callback.number_on_file}' /tmp/out.json
jq '{record}' /tmp/out.json | curl -s $API/record/verify -H 'content-type: application/json' -d @- | jq '{ok, summary}'
```

The sample `lookalike-urgent` is synthetic. Pass if:
- `.change_request.present` is `true`;
- `.cues[].cue` includes `lookalike_domain` and `bank_country_mismatch`;
- `.callback.number_on_file` is `"+1-312-555-0142"` and `+1-312-555-0199` is under `do_not_use`;
- the verdict never calls the email safe; every receipt has `"status": "attested"` and the record verifies.

Then check that the call-back record refuses the number in the email:

```bash
jq '{record, callback: {caller: "AP clerk", number_dialled: "+1-312-555-0199", spoke_with: "x", outcome: "vendor_confirmed"}, approver: {name: "Controller"}}' /tmp/out.json \
  | curl -s $API/bank-change/signoff -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d @- | jq .error
```

Pass if it says the number dialled is not the number on file.

## 5. Point the app at the local API

- Base URL: `http://localhost:8445`. For a web app, set `NEXT_PUBLIC_DECOSA_API=http://localhost:8445`; add other origins to `DECOSA_CORS_ORIGINS`.
- `POST /bank-change/check` takes `{eml: "<the whole message>", vendors_csv: "<your vendor file as CSV>", mode?: both|rules}` (`rules` needs no GPU). It returns JSON, or streams Server-Sent Events with `Accept: text/event-stream`.
- `GET /bank-change/info` lists what is checked, the rules with their sources and dates, the limits and what is not checked.
- The server stores nothing. Keep each signed record (JSON) with your file. Anyone can re-check it with `POST /record/verify` against the key at `GET /attest/signing-key`. No call leaves the box.
- Keep the API on 127.0.0.1. For other users on the LAN, put a TLS reverse proxy with authentication in front and set `DECOSA_TRUSTED_PROXIES`.

## 6. Hosted routes (off, and leave them off)

`DECOSA_LLM_ROUTE=gateway` would send prompts, which contain your files, to a hosted gateway. Never use it for real files.

Finish with a summary: what is running, the health output, the smoke-test results, and the reminders above.
Technical detailsModels, where it runs, labels

In short

Last reviewed

What it is
A check for vendor bank change fraud: it reads the email that asks to change a vendor's bank details against your own vendor file, lists every warning sign with its evidence, and makes the call-back to the number on file part of a signed record with a second approver.
Who it's for
AP clerks, controllers and bookkeepers who pay vendors by ACH or wire.
Where it runs
Hosted demo on made-up emails; self-host or confidential access for your real AP inbox
Key numbers

On a held-out set of 19 synthetic emails (9 frauds, 8 genuine bank changes, 2 with no change) it flagged 9 of 9 frauds and 0 of 8 genuine changes; the set is small and written by one author, so the confidence interval is wide.

  • 9 of 9 Fraud flagged (score 3 or more) (test split, n = 9)
  • 0 of 8 Genuine changes flagged (test split, n = 8)
  • 19 of 19 Bank-change requests detected (test split, n = 19)
  • 8.1 s Median end-to-end run, hosted (QA sweep 2026-09-29)
All results, datasets and caveats
Models
Qwen3.8-27B (quotes the pressure and secrecy signs; the header, domain and bank checks are code)
Where
Hosted demo on made-up emails; self-host or confidential access for your real AP inbox
Checks
Header and vendor-file checks in code; every model sign quoted from the email; receipt per model call; signed check and call-back records
Output
Signed record or verdict · Structured data
Data
Confidential business data
Hardware
1× 96 GB GPU
Licence
Permissive (Apache-2.0, MIT)
Runs in
Self-host
Built from
Signed record

Questions people ask

How does it catch vendor bank change fraud from a real vendor's hacked mailbox?

Often it can't from the email alone: a hacked mailbox passes the sender checks. It still lists what the content shows (a bank abroad, an account holder who isn't the vendor, "don't call"), and it always sends you to the number already on file. The call-back is the control.

Will it tell me an email is safe?

No. Every bank-detail change gets the same answer: hold it and call the number on file. A genuine change with no warning signs still needs the call-back and a second approver.

Does it release or block payments?

No. It never edits your vendor file or touches a payment. The signed record documents the check, the call-back and the approver; the change happens in your own AP system.

What does Nacha's fraud monitoring rule have to do with it?

Since 19 Jun 2026 every non-consumer ACH originator needs risk-based processes to catch payments made under false pretenses, such as vendor impersonation. The signed call-back record is written evidence of one such step. It is not a compliance determination.

Ask a question or leave feedbackWe read every message and publish useful answers
Questions & feedback

Ask about Vendor bank-change check

We read every message. Questions, comments and our answers show here once we have reviewed and approved them.

Loading questions…

This is a

Plain text. Please leave out personal, patient or client data.

Shown with your message if we publish it. Leave blank to post as “A visitor”.

Nothing appears here until we have read and approved it.