Policy, approvals and audit

For control-model reviewers and architects. How the Policy Engine evaluates rules, raises approval requests, routes tasks to approvers, and what evidence each step leaves.

Set the bank's payment controls

A policy expresses the bank's rules for an instruction. It can allow the instruction, refuse it or require sign-off. For example, a bank could allow smaller payments automatically, ask a manager to approve larger ones and refuse amounts above its chosen ceiling. Those thresholds are the bank's choice.

Risk and compliance teams define the control. Administrators configure the rule and eligible approvers. An approver uses their own identity, and the decision is recorded for later review. Approval permits the instruction to continue; operations must still confirm settlement.

Policy evaluation is disabled in the shipped settlement configuration. Verify that it is enabled for the operations you need, then test the rule and separation of duties in your environment. Custody signing controls are separate and also need verification. The rule, versioning and approval details follow below.

What the Policy Engine does

The Policy Engine checks operations against rules your organisation configures. Its use for token settlement is controlled by the environment configuration and is disabled in the shipped default. Confirm that it is enabled for the operation types you require before relying on these controls. When invoked, it has three jobs:

JobWhat it does
DecideChecks the event against your active rules and returns one response.
RouteWhen people are needed, resolves who they are at that moment, creates their approval tasks, orders the tasks into steps and applies a deadline.
ProveRecords each decision: what was checked, which policy and version applied, who approved or refused, when, and why.

Rules are created from templates and set to your own thresholds, currencies and approvers. Your administrators do this themselves, in the Management Panel or through the API. No software change or request to Quant Network is needed.

Design the control from the customer's instruction

Start with the operation your application will submit and the decision your bank wants to make. For a payment threshold, the rule needs both an amount and its currency. For independent sign-off, it needs correctly configured approvers and an application path that keeps the instruction pending until the request resolves.

Four resources serve different purposes: a template is a reusable starting document; a policy is the configured control; a version is an immutable rule document; an approval request is a decision about one instruction. Editing a template does not update an existing policy, and creating a policy does not make a draft enforce anything.

The Policy Engine decides and records. The token engine acts on the decision when evaluation is enabled for the operation. Your backend tracks the resulting transaction state; it does not call an internal evaluation endpoint or move the instruction past approval itself.

A policy action name is not a new payment endpoint. The policy catalogue includes fiat and conversion actions, but that does not make direct fiat payment or FX swap submission available through the external token API. Use the supported operation types and confirm the configured policy mapping.

The four responses

Each event receives one response, recorded with the rule that produced it.

ResponseWhen it happensEffect
ApprovedA rule confirms no sign-off is needed, for example an amount under an automatic threshold.The operation continues. The decision is still recorded.
PendingOne or more rules name approvers.The operation waits. Tasks appear in the approvers' inboxes and the deadline starts.
RejectedA refusal rule matches, for example a hard ceiling.The operation is refused. No task is created and no approver is asked.
Manual reviewNo rule matches, and the policy's defaults.unmatched is REQUIRE_MANUAL_REVIEW (the default).The request is held with one task and no approvers. It stays pending until someone reassigns it, cancels it, or it expires.

Refusal outranks approval. If an event matches an approval rule and a refusal rule, the refusal applies, whatever order the rules were written in.

Two layers of control

Signing happens outside the platform, under the custody provider's own signing policy. The two controls answer different questions at different moments:

ControlQuestion it answersWhen
Policy EngineDoes your business want this operation, and who in your organisation must agree?After the operation is accepted and before settlement dispatch, when policy is enabled for its type
Custody signing policyWill this key sign this transaction?At the moment of signing

The two are operated by different systems under different administrators. Set your approval thresholds consistently across the two, and verify both controls in your configured environment before go-live.

How policy applies to token operations

When policy is enabled for a token operation's type, it is evaluated after acceptance. The shipped judged types are MINT, BURN, TRANSFER and LOCK; RELEASE and CANCEL are excluded. An excluded type, or any type with policy disabled, starts in BATCHED with no policy decision. A judged operation starts in POLICY_PENDING. The outcome is reported on the operation, not on the submission:

Policy outcomeOperation stateFailure code if it ends
ClearedVALIDATEDnone
Held for approvalAWAITING_APPROVALAPPROVAL_REJECTED if an approver refuses
RefusedFAILEDVALIDATION_REJECTED
Policy reports EXPIRED before an approval waitFAILEDPOLICY_EXPIRED
Policy reports CANCELLEDCANCELLEDRead the recorded cancellation reason
No policy responseRemains POLICY_PENDING until operator action todayThe configured POLICY_TIMEOUT deadline is not currently enforced

The Policy Engine covers these actions today: ordinary payments, international transfers, currency conversions, fiat-to-token purchases, token-to-fiat redemptions and token issuance.

  • Use actions and context fields supplied by the selected template and confirmed in the deployment's policy configuration. Do not invent a token-operation-to-action mapping from an example alone.
  • The shipped settlement configuration has policy disabled. With policy enabled, behaviour when no policy is active for an action must be confirmed for your deployment; this is different from disabling policy entirely.

Read a threshold policy as a complete decision

Suppose your control requires one payments-team decision from 100.00 GBP upwards and refuses amounts from 10,000.00 GBP upwards. A 50.00 GBP instruction takes the configured unmatched outcome. A 500.00 GBP instruction creates a payments approval task. A 15,000.00 GBP instruction matches both thresholds, but the refusal wins and no human approval can override that decision.

Rules use hitPolicy: collect: every matching rule contributes. If a 500.00 GBP instruction also matches a compliance rule at the next step, both decisions are required, in order. Naming a group creates one task that any eligible member can decide; it does not require every group member's vote. To require independent decisions, model separate tasks and test their routing.

Use Author and simulate a policy for the supported condition syntax, context fields and activation requests. Use Build your own approval inbox for task reads and decisions. Test thresholds at, below and above their boundaries in a non-production environment where policy is enabled.

Policy and human approval

%%{init: {"theme": "neutral"}}%%
flowchart TB
    Accepted["Accepted token operation"] --> Enabled{"Policy enabled for this type?"}
    Enabled -->|No| Settle["Settlement and custody controls"]
    Enabled -->|Yes| Evaluate["Evaluate active policy rules"]
    Evaluate -->|Cleared| Settle
    Evaluate -->|Approval required| Tasks["Approval request / approver tasks"]
    Evaluate -->|Refused| Failed["FAILED"]
    Tasks -->|Approved| Settle
    Tasks -->|Rejected or expired| Failed
    Settle --> Outcome["Read final transaction state"]

This is a summary of the configured policy path, not every lifecycle transition. Manual review, cancellation and missing responses are detailed below. Approval does not prove settlement or custody authorisation.

Policy versions · Approval inbox · Approve task · Read operation

Policies

A policy is a set of rules for one action, such as FIAT_PAYMENT. Each policy has:

FieldMeaning
ruleTypeThe kind of rule, for example approval.
templateKeyThe template the policy was created from.
actionTaken from content.appliesTo.action.
timeToLiveHow long an approval request from this policy stays open, stored as amount and unit, for example 30D. Units are H, D, W, M and Y.
statusDRAFT, ACTIVE or ARCHIVED.
versionThe current version number.
contentThe rules of the current version.
createdByThe sub of the user who created it.

Versions are immutable

A policy is created in DRAFT at version 1. Changing its rules creates a new, immutable version at the next number. Creating a version does not make it live. The policy keeps serving the version it pointed at until you set the current version explicitly. Authoring a rule change and putting it into effect are therefore separate steps. Apply your bank's separation of authoring and activation responsibilities in the administration workflow; verify which permission checks the deployed endpoints enforce.

Editing a policy's name and description does not create a version.

To roll back, point the current version at an earlier one. No version is overwritten or deleted, so the full history of the rules stays available.

Activation

Activating a policy moves it to ACTIVE. At most one non-archived policy in an organisation may hold a given combination of ruleType, action and templateKey. A second one is refused with 409. Archiving a policy stops it being selected and frees that combination. Versions are kept after archiving.

Rules

content.rules is a list. Every rule reads the same way: when these conditions hold, this happens. Conditions can test the amount (above, below or within a range), the currency or asset, and on conversions the target asset. Outcomes are: proceed automatically, require these approvers in these steps, or refuse.

Not available today: cumulative limits (for example a weekly limit per counterparty), time-of-day windows, and inputs from your own screening systems.

Every rule has an id, a when block and an emit block.

when maps context fields to conditions. All conditions must hold for the rule to match.

FormExampleAllowed for field types
Equality== EURall
Comparison>= 100.00AMOUNT, TIMESTAMP
Membershipin [EUR, USD]STRING, PRINCIPAL
Interval[100.00 .. 1000.00)AMOUNT, TIMESTAMP

There is no negation; != is rejected. A rule that compares amount must also constrain assetId, so every threshold is per currency. Every field a rule names must exist in the context catalogue for the action. The check runs when the content is saved. See the context fields in Author and simulate a policy for the per-action catalogue. The catalogue belongs to the platform release; templates cannot add fields.

emit says what a matching rule produces.

FieldEffect
outcomeAPPROVE (default) routes the request into the approval flow. REJECT creates the request already REJECTED and takes precedence over every other matched rule.
autoApprovaltrue produces an already-approved task with no approver.
stepTasks at a lower step must resolve before a task at a higher step can be acted on.
approverAn approval-group key such as #mlro, or one user id.
reasonCopied onto the request and task.

Every matching rule contributes its own task. defaults.unmatched sets what happens when no rule matches: AUTO_APPROVE resolves the request immediately; REQUIRE_MANUAL_REVIEW (the default) creates a pending request with one task and no approvers.

Send every field your rules reference. A field named in any rule but absent from the request context ends the whole evaluation with an error, even from a rule that would not have matched.

Templates

A policy is created from a template. SYSTEM templates are visible to every organisation. TENANT templates are visible only to the organisation that created them. A template's defaultContent is not copied into a policy, so content must be supplied when a policy is created. Updating or deprecating a template does not change policies already created from it.

Tenant template content is not validated when the template is saved. Invalid rules are rejected later, when a policy is created from the template.

Approvals

An approval request is one decision the organisation owes on one resource. It holds one or more tasks, each owed by an approver or a group.

Request sourceTypeRaised by
POLICYA policy evaluation. sourceRef carries the evaluation id.
MANUALPOST /api/approvals/requests, with no policy behind it.
APIReserved. Never written today.
Request statusMeaning
PENDINGAwaiting decisions.
APPROVEDEvery task approved.
REJECTEDA task was rejected, or a rule rejected it. Remaining pending tasks become SKIPPED.
EXPIREDIts time-to-live elapsed. Pending tasks become EXPIRED.
CANCELLEDWithdrawn. Pending tasks become CANCELLED.

Routing to approvers

  • Groups. An approver expression such as #mlro names an approval group. It is resolved to the group's members when the task is created, and that set is frozen in approverIds. A later change to the group does not change who was asked. Any one member may decide the task; actedBy records who did. One task is one decision, however many members the group has.
  • Named users. Any other value is stored as one user id, lower-cased and unvalidated. A mistyped id matches nobody, and the task waits until it is reassigned or expires.
  • Steps. Tasks at a lower step must all resolve before a higher step can be acted on. Use steps to build sequential sign-off, for example business approval then compliance.
  • Inbox. GET /api/approvals/tasks returns tasks whose approverIds contain the caller's sub. A task with no approvers appears in no inbox and can only be reassigned, cancelled or left to expire.
  • Reassignment. A pending task can be moved to another group or user. The original expression is kept, and the move is recorded on the task.
  • More than one approver. A group task needs one member's decision. To require several people to agree, give each its own task, in the same step or in successive steps. Design separate tasks and approver assignments for independent decisions; do not assume one person cannot decide multiple tasks unless your application and deployed policy enforce it.

Example: approval tiers

A bank sets tiers for outgoing euro payments. The thresholds are illustrative. Every number, currency and approver is your organisation's choice, and thresholds are set per currency.

Amount (EUR)Who decidesOutcome
Up to 10,000NobodyProceeds, still recorded
Over 10,000, up to 50,000Payments managerOne approval releases it
Over 50,000, up to 250,000Payments manager, then the compliance teamTwo steps, strictly in order
Over 250,000NobodyRefused. No approver is asked.

Each band is one rule. The top band is a REJECT rule, so it applies even where an approval band also matches. In the two-step band, the compliance task shows in every team member's inbox, any one member may decide it, and the record shows both the team that was asked and the person who acted. One rejection at any step ends the request as REJECTED and closes the remaining tasks as SKIPPED.

Separation of duties

The approve and reject endpoints refuse a caller whose sub matches the request's initiatedBy, and a caller who is not in the task's approverIds. Reassignment removes the initiator from the new approver set.

  • The task-decision endpoint excludes the request initiator from approving their own request. Verify the initiating identity is propagated correctly and use a different named user for approval.
  • The current cancel/reassign endpoints allow authenticated callers within the tenant; the public contract does not restrict these operations to an initiator or administrator. Restrict these actions through your bank's application authorization policy.

Expiry

A request from a policy expires after the policy's time-to-live. Expiry is applied by a periodic sweep, so a request is observed as EXPIRED at the next sweep after expiresAt, not at that instant. The API Reference states that the sweep runs hourly by default. A request with no expiresAt never expires.

When the policy engine sends an expiry decision, an operation waiting in POLICY_PENDING ends FAILED with POLICY_EXPIRED; an operation already AWAITING_APPROVAL treats expiry as a refusal. A cancellation decision moves it to CANCELLED. Expired requests are recorded and reportable. If no decision message arrives, the settlement service does not currently enforce its response timeout; escalate the waiting operation rather than assuming it will expire.

Fixed behaviours

These apply to every organisation and every rule. They cannot be configured or disabled.

BehaviourWhat it means
Every decision is on the recordEach decision stores what was submitted, which policy and version judged it, the outcome and who acted. Records are append-only. Policies are archived, never deleted. Resolved approval requests stay queryable.
Refusal outranks approvalWhen rules overlap, a refusal wins. A hard ceiling cannot be avoided by also matching an approval tier.
Approvers are fixed at the moment of askingGroups resolve to people when tasks are created, and that snapshot is kept. The record answers "who was in the group at the time?" years later.
Organisations are separatedEvery policy, approval and record belongs to one organisation. Another organisation's rules and decisions cannot be read, and a request for one returns 404.
Policies retain their own contentA policy stores the content supplied on creation. A later template change does not alter that content.

Audit evidence

The platform records evidence at three levels.

EvidenceWhereWhat it shows
Activity logGET /api/audit/events, GET /api/audit/events/{eventId}, CSV exportWho did what, when, with what outcome. Full events carry before and after values for each changed field; values the producer withheld read as masked.
Approval recordEach request and taskinitiatedBy, actedBy, actedAt, comment, resolvedBy, resolvedAt, and reassignment fields. System actions are named, for example system:auto-approval and system:ttl-sweeper.
Policy historyPolicy versionsEvery version is immutable and records createdBy, createdAt and an optional changeSummary.

Use approval request reads for requests that exist and transaction reads for the operation outcome. Do not assume an automatically approved or policy-bypassed transaction has an approval-request record; correlate using the resource identifier supplied by the policy configuration.

Audit events carry an actor (USER, SERVICE or SYSTEM), a category (BUSINESS_ACTION, DATA_CHANGE, API_ACCESS or SECURITY, an open list) and an outcome (SUCCESS, FAILURE or DENIED). The CSV export returns the whole filtered set with no row limit.

Agree retention and archived-event access for your deployment, and export required evidence to your bank-controlled store. See Audit and evidence.

Govern policy changes as integration changes

Record the template, policy, current version and approval-group dependencies used for each enabled workflow. The bank defines the business rules and eligible approvers; the platform evaluates the configured decision path and records requests/tasks. Verify that policy enforcement is enabled for the relevant operation types in your assigned environment.

Test threshold boundaries, rejection and separate initiator/approver identities before selecting a new current version. Review pending requests before changing group membership or retiring a policy; a new rule document does not prove that existing work was re-evaluated. Retain decision evidence alongside transaction outcomes, and use Production readiness for release acceptance.

Worked example

Create a policy that routes EUR payments of 100.00 or more to the #mlro group.

POST /api/policies HTTP/1.1
Host: api.qfp.quantnetwork.net
Authorization: Bearer <access token>
API-Version: 1.0.0
Content-Type: application/json

{
  "name": "Named-approver check",
  "description": "Routes every EUR payment over 100 to a named approver",
  "ruleType": "approval",
  "templateKey": "PAYMENT_TIERS",
  "timeToLive": 30,
  "timeToLiveUnit": "D",
  "content": {
    "appliesTo": { "action": "FIAT_PAYMENT" },
    "hitPolicy": "collect",
    "rules": [
      {
        "id": "needs-approval",
        "when": { "amount": ">= 100.00", "assetId": "== EUR" },
        "emit": {
          "autoApproval": false,
          "step": 0,
          "approver": "#mlro",
          "reason": "Over 100 EUR"
        }
      }
    ],
    "defaults": { "unmatched": "AUTO_APPROVE" }
  }
}

Illustrative example built from the schema.

HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "0b8e2f4a-6c13-4d7e-9a25-3f1c8d6e4b90",
  "name": "Named-approver check",
  "description": "Routes every EUR payment over 100 to a named approver",
  "ruleType": "approval",
  "templateKey": "PAYMENT_TIERS",
  "timeToLive": "30D",
  "action": "FIAT_PAYMENT",
  "status": "DRAFT",
  "version": 1,
  "content": {
    "appliesTo": { "action": "FIAT_PAYMENT" },
    "hitPolicy": "collect",
    "rules": [
      {
        "id": "needs-approval",
        "when": { "amount": ">= 100.00", "assetId": "== EUR" },
        "emit": {
          "autoApproval": false,
          "step": 0,
          "approver": "#mlro",
          "reason": "Over 100 EUR"
        }
      }
    ],
    "defaults": { "unmatched": "AUTO_APPROVE" }
  },
  "createdBy": "8f14e45f-ceea-467a-9a1e-1c8b4b0b5a21",
  "createdAt": "2026-08-18T09:00:00.000Z",
  "updatedAt": "2026-08-18T09:00:00.000Z"
}

The policy is DRAFT. It takes effect only after POST /api/policies/{policyId}/activate.

View in API Reference

Errors and edge cases

Policy and approval errors use the common envelope. Two responses add a machine-readable code: VALIDATION_FAILED on a 400 from request validation (with field messages in details on some responses), and IDEMPOTENCY_CONFLICT on a 409.

SituationResponse
content fails validation against the action's context catalogue400
templateKey names no template visible to your organisation404
Activating a second policy for the same ruleType, action and templateKey409
Setting a current version that does not exist404
Approving or rejecting a task you are not an approver on, or as the initiator403
Acting on a task already decided, or before a lower step resolves409
Cancelling a request that is not PENDING409
An approver expression that resolves to nobody, or only to the caller400 with VALIDATION_FAILED
A resource that exists in another organisation404, the same as one that does not exist

hitPolicy is documentary. Any value is stored and evaluation always collects every matching rule.

Follow a Recipe

Step through the code in JavaScript or Python, inspect illustrative responses, and open the connected endpoint schemas.

API Reference

Open an endpoint for request fields, response examples and the interactive API console.

Related pages

Implementation resources

Tenant templates code recipes. SYSTEM templates are maintained by the platform operator; use the template listing to discover those available to your organisation.

Use Implement a backend client for request handling, durable idempotency and outcome tracking. The developer integration coverage map links every public operation to its API Reference and contract-derived code recipe. Replace example identifiers with values from your assigned environment.


Did this page help you?