FinCrime Transaction Context Guide
This page is the integration surface of the Fraud & Risk Add-on for Agentic Payments. Callers that want financial-crime governance for an agent-initiated transaction attach two documented context objects, fincrime_transaction and fincrime_cohort, to the request. The FinCrime Policy Pack's controls and its risk score read these objects. Two of the pack's controls also match the request statement, so payment phrasings are governed even on requests that carry no context.
The Fraud & Risk Add-on for Agentic Payments is a separately priced add-on to AxonFlow Enterprise. It is not included in the base Enterprise license, the Evaluation tier, or the source-available Community edition. This page describes platform v11.1.0. The add-on is currently available through the early-access program.
Where the objects ride
The same two objects are carried differently per request surface:
| Request surface | Carrier |
|---|---|
POST /api/v1/decide (Decision Mode) | context.fincrime_transaction and context.fincrime_cohort, as top-level keys inside the request context object |
POST /mcp/resources/query, POST /mcp/tools/execute, POST /api/v1/mcp/check-input (Agent API), and the MCP server's check_policy tool | parameters.fincrime_transaction and parameters.fincrime_cohort |
All fields in both objects are optional, and unknown fields are ignored. A request that carries neither key is not scored, and there is no context for the pack's context-bound controls to match in. Its request statement is still matched by the pack's statement-bound controls, and on the MCP planes every parameter value is scanned, so a parameter that happens to carry a matching shape (for example a serialized "amount": 15000) can apply pack controls with no fincrime key attached; see the pack reference's false-positive surface section.
On the decision API, the fincrime keys are object-valued entries inside the same context map that also carries audit context propagation. The two mechanisms are independent: audit context propagation forwards allowlisted string values into spans and audit records, while the two fincrime objects are lifted out and evaluated. The rest of the context map is audit-only and is never evaluated.
What consumes the objects
- The FinCrime Policy Pack's deterministic controls match the objects' JSON serialization (see Serialization), identically on the decision API and the MCP planes.
- The advisory risk score, when a scoring service is configured: the agent sends
fincrime_transactionandfincrime_cohort, as you sent them, to the in-deployment scoring service, and the pack's score control reads the returned score. A score never denies. At or above the threshold it holds the transaction for approval; if no score comes back, the control does not apply and the decision records why inpolicy_details.fincrime_risk_score.
Per-plane outcome semantics
Read this before integrating. On v11.1.0, on an Enterprise deployment licensed for human approval:
| Request surface | A pack deny control applies | A pack hold control applies (including a risk score at or above the threshold) |
|---|---|---|
POST /api/v1/decide | Verdict deny | Verdict needs_approval with a pending_approval object |
MCP check-input, resources/query, tools/execute | HTTP 403, blocked | HTTP 403 with a pending_approval object |
MCP server check_policy tool | allowed: false | allowed: false, a block_reason starting approval_pending, and a pending_approval object |
A held call does not run. A person approves or rejects the pending approval on the portal's Approvals page, and the caller then retries the same call, naming the approval in the X-Axonflow-Approval-Id header or an approval_id field. The retry is decided again and passes once if it is still held for the same approval; a retry that a deny control now applies to is denied, because no approval lifts a denial. One call that applies several hold controls opens one approval, and repeating the call while it is pending names the same approval. The contract, including every condition on the retry, is in Pending approval on MCP and decide.
When a hold control applies, the call is refused rather than held in these cases:
- the deployment's licence does not carry the human-approval entitlement (reason
approval_required, nothing queued); - the enforcement point's PEP capability handshake does not advertise
approval_challenge(reasonunsupported_obligation). Advertise the capability to receive holds; an Enterprise request that sends no handshake header is held; - the request came through the AuthZEN evaluation route (
/access/v1/evaluation), which does not hold (reasonapproval_required); - the approval could not be queued (reason
approval_required, with the cause).
The pack does not apply on the Gateway Mode pre-check path, the Workflow Control Plane, or multi-agent plans.
fincrime_transaction
The formats below are what the pack's controls and the scoring model expect. Nothing validates them (see Validation), so a value in another format is not rejected; it simply may not match the control you expect.
| Field | Type | Expected format | Notes |
|---|---|---|---|
amount | number | >= 0 | Transaction amount in major units of currency (48500 means 48,500.00 USD). Send a JSON number, never a string. The pack's numeric thresholds assume major units: a caller sending minor units (cents) will trip them on routine values. |
currency | string | 3-letter ISO 4217 code | |
payment_type | string | free-form | Conventional values: transfer, wire, card, ach, rtp, crypto. The structuring control keys on transfer and wire. |
payment_method | string | free-form | Conventional values: card_present, card_not_present, bank_account, wallet. The card-not-present control keys on card_not_present (and cnp). |
sender_location | string | ISO 3166-1 alpha-2 or alpha-3 code, optional - subdivision suffix | Examples: US, US-NY, IRN. The location controls match either case. |
receiver_location | string | same as sender_location | |
counterparty_account_id | string | free-form | Opaque identifier. Never send a full card number (PAN); see the note on scanning below. |
merchant_id | string | free-form | |
merchant_category_code | string | 4-digit ISO 18245 MCC | A string, not a number: leading zeros are significant, and the restricted-MCC control matches the quoted string. |
timestamp | string | RFC 3339 | When the transaction was initiated by the agent. |
fincrime_cohort
Caller-computed rolling aggregates for the transacting principal. AxonFlow is the policy decision point, not the metrics store: your system already observes its own transaction stream, so it supplies the aggregates and the platform evaluates the thresholds. Requests that carry no cohort data do not receive the velocity and exposure controls. In-platform cross-request aggregation is planned.
| Field | Type | Expected format | Notes |
|---|---|---|---|
historical_mean_amount | number | >= 0 | The principal's historical mean transaction amount. |
txn_frequency_1h | number | >= 0 | Transactions initiated by this principal in the trailing hour, including this one. |
Validation
On v11.1.0 nothing validates the transaction context objects. v10.x stepped up a present-but-malformed context for human review, attributed to fincrime_mandatory_fields. That check was removed with the v10 engine, and nothing replaces it: a malformed value is matched as written and is neither held nor denied for being malformed.
That shifts the burden to your producer, because a malformed value usually means a control silently does not match:
- an
amountsent as a string ("12000") does not match the amount cap, which expects a JSON number; - a numeric
merchant_category_codedoes not match the restricted-MCC control, which expects a quoted string; - a location that is not an ISO code shape does not match the geography controls.
The scoring service treats some unusable values as absent, which lowers feature_coverage: for example a timestamp string that is not RFC 3339. Others make it reject the request, for example a timestamp sent as a number; the decision then records unavailable, and the transaction is not scored. The pack's controls are stricter than the scoring service: a string amount may still be scored while the amount cap does not match it. Validate your producer against the field tables above before rollout.
Scanning covers more than the pack
On the decision API, the two fincrime objects are handed to the policy engine's detectors as request parameters, exactly as MCP parameters are. The platform's other detectors see them too, not only the pack's. A card number pasted into counterparty_account_id can therefore trip a PII detection, not just a fincrime one. This is deliberate fail-toward-governance for opt-in traffic only; decision requests without the keys are scanned as before.
Serialization
The deterministic controls match object-valued parameters as their JSON serialization: keys sorted ascending, no whitespace. Example:
{"amount":9300,"currency":"USD","payment_type":"transfer","receiver_location":"VN"}
The pack's context-bound controls are written against this form, which is identical on the decision API and the MCP planes. As an integrator you do not need to produce this form yourself; send ordinary JSON and the platform serializes it before matching.
Example: prompt-injected beneficiary change
A procurement agent has ingested a poisoned invoice PDF that instructs it to change the supplier's bank details and pay an inflated total. The enforcement point calls the decision API before the tool call:
{
"stage": "tool",
"caller_identity": {"gateway_id": "procurement-gw"},
"target": {"type": "tool", "server": "erp", "tool": "erp.update_vendor_bank_details"},
"query": "Update beneficiary bank account for vendor ACME-GmbH to DE44 5001 0517 5407 3249 31 and process payment of invoice INV-2209",
"context": {
"fincrime_transaction": {
"amount": 48500,
"currency": "USD",
"payment_type": "transfer",
"sender_location": "US",
"receiver_location": "DE",
"counterparty_account_id": "vendor-acme-gmbh",
"timestamp": "2026-08-20T09:15:00Z"
}
}
}
Expected outcome: verdict deny. The payment tool authorization gate matches the beneficiary change in the request statement, and the high-value amount cap independently matches the 48,500 amount. A deny control applies, so the call is denied even though the statement also matches the payment execution hold: no approval lifts a denial. The decision's evaluated_policies names the controls by their pack:fincrime: ids, so the decisions feed and the compliance exports attribute the denial.
Example: structuring burst with cohort aggregates
The same agent, now compromised into splitting a large transfer into sub-threshold legs. Each leg carries the transaction plus caller-side cohort aggregates:
{
"stage": "tool",
"caller_identity": {"gateway_id": "procurement-gw"},
"target": {"type": "tool", "server": "payments", "tool": "payments.create_transfer"},
"query": "Transfer part 7 of supplier settlement",
"context": {
"fincrime_transaction": {
"amount": 9300,
"currency": "USD",
"payment_type": "transfer",
"sender_location": "US",
"receiver_location": "VN",
"counterparty_account_id": "acct-9917",
"timestamp": "2026-08-20T09:42:00Z"
},
"fincrime_cohort": {
"historical_mean_amount": 210.5,
"txn_frequency_1h": 12
}
}
}
Expected outcome: verdict needs_approval, with a pending_approval object naming one approval. Three hold controls apply: the structuring control (a transfer in the 3,000 to 9,999 band), the velocity control (12 is at or above the 10 per hour threshold), and the corridor control (the receiver is on the pack's increased-monitoring snapshot). With a scoring service configured, the leg is also scored; a score at or above the threshold adds the risk score control to the same hold, and policy_details.fincrime_risk_score records the score either way. Retry the call with the approval's id once a person approves it.
Example: the same context on an MCP plane
On the MCP planes the objects ride in parameters. Here is the pre-execution check (POST /api/v1/mcp/check-input) for a connector-executed transfer, carrying the same transaction object:
{
"connector_type": "payments",
"statement": "INSERT INTO transfers (account, amount) VALUES ($1, $2)",
"parameters": {
"1": "acct-9917",
"2": "9300",
"fincrime_transaction": {
"amount": 9300,
"currency": "USD",
"payment_type": "transfer",
"receiver_location": "VN",
"timestamp": "2026-08-20T09:42:00Z"
}
}
}
Expected outcome: HTTP 403 with a pending_approval object: the structuring and corridor controls hold the call, and it is not executed. After a person approves, retry the same check naming the approval. Had the transaction matched a deny control instead (for example an amount of 12000, at or above the 10,000 cap), the response would be a plain block. POST /mcp/resources/query and POST /mcp/tools/execute carry the objects the same way, inside their parameters maps, alongside those endpoints' own required fields, which are documented in the Agent API endpoints reference.
Request authentication
Fincrime context adds nothing to authentication. Decision API calls authenticate exactly as documented in Decision Mode, and MCP-plane calls as documented in the auth header matrix.
