DOCS
Dashboard Get API Key

Introduction

Screening is an input. Resolution is the product.

Traditional compliance tooling generates alerts. Veris resolves them. The distinction is fundamental: screening vendors deliver a list of problems. Veris delivers finished compliance work, including disposition decisions, evidence packages, SAR narratives, BSA/CTR XML filings, and immutable audit trails. Every alert that enters the system exits as a completed work product ready for CCO review and regulatory submission.

Zero-Touch Resolution

Veris operates an autonomous compliance pipeline that transforms raw blockchain activity into regulatory-ready output. No analyst intervention is required for the majority of alerts.

The pipeline executes in three sequential stages:

StageAgentLatencyOutput
TriageClassification engine2-3 secondsDisposition category, confidence score, reasoning chain
InvestigationEvidence synthesis engine15-25 secondsCounterparty analysis, transaction timeline, risk assessment, recommendation
SAR GenerationNarrative and filing engine10-20 secondsFinCEN-compliant narrative, BSA XML, typology classification

Total resolution time from alert ingestion to finished work: 30-50 seconds.

What "Finished Work" Means

A resolved alert produces five concrete deliverables:

  1. Disposition. A classification decision (clear, suspicious, sar_required) with confidence scoring and a full reasoning chain explaining the decision rationale.
  2. Evidence Package. Structured JSON containing counterparty analysis, transaction flows, risk factor enumeration, and source attribution for every claim.
  3. SAR Narrative. Publication-ready prose suitable for direct submission to FinCEN, written to the standards expected by examiners.
  4. BSA XML. Schema-validated XML conforming to the FinCEN BSA E-Filing specification, ready for upload without manual editing.
  5. Audit Trail. Hash-chained, immutable log entries recording every agent decision, data source consulted, and intermediate output produced during resolution.

Alert in, finished compliance work out.

API Products

Veris exposes three product surfaces through a unified REST API.

1. Zero-Touch Resolution

The core pipeline. Submit a transaction hash or alert identifier, receive a complete resolution with disposition, evidence, and filing artifacts.

EndpointMethodPurpose
/v1/resolvePOSTSubmit a transaction or alert for autonomous resolution
/v1/resolutions/{id}GETRetrieve a completed resolution and its artifacts
/v1/evidence/{id}GETDownload the evidence package for a resolution
/v1/sar/{id}/narrativeGETRetrieve the SAR narrative text
/v1/sar/{id}.xmlGETDownload the BSA-compliant XML filing

2. Continuous Monitoring

Always-on surveillance across stablecoin reserves, travel rule compliance, and regulatory change detection. These agents operate on periodic schedules independent of individual alerts.

EndpointMethodPurpose
/v1/monitor/reservesGETCurrent reserve health and attestation status
/v1/monitor/travel-ruleGETTravel rule compliance state across jurisdictions
/v1/monitor/regulatoryGETRegulatory change feed with impact analysis

3. Intelligence

Programmatic access to Veris intelligence data, including entity resolution, address attribution, and risk scoring.

EndpointMethodPurpose
/v1/intelligence/entity/{address}GETEntity profile and attribution data
/v1/intelligence/risk/{address}GETComposite risk score with factor breakdown
/v1/intelligence/graph/{address}GETTransaction graph and counterparty network

Getting Started

How Veris works with your compliance team. Four inputs from you, continuous autonomous output from the engine.

What Your Team Provides

Four inputs. All delivered through the dashboard or API.

1. Stablecoin Selection

Veris indexes USDC, USDT, EURC, PYUSD, and RLUSD across ten blockchains: Ethereum, Base, Polygon, Arbitrum, Avalanche, BSC, Optimism, Celo, Solana, and Tron. During onboarding, select the tokens and networks relevant to your business. Veris begins monitoring within minutes.

2. Entity Data

Upload KYC and CDD records through the API or dashboard. Map blockchain addresses to legal entities with jurisdiction and verification status. Veris does not perform KYC. Your team handles customer onboarding and feeds the results into Veris via POST /v1/veris/entities.

Example: "Address 0xabc belongs to John's OTC Desk LLC, incorporated in Singapore, KYC verified." Veris uses this mapping for counterparty analysis, travel rule compliance, and investigation context.

3. Compliance Policy

Configure rules for your jurisdiction:

SettingDefaultPurpose
Alert thresholdRisk score 80+Minimum score to generate an alert
Travel rule limit (US)$3,000 USDThreshold for originator/beneficiary capture
Travel rule limit (EU)EUR 1,000PSD2/MiCA threshold
High-risk jurisdiction multiplier2.0xRisk score multiplier for flagged jurisdictions
Transaction monitoring lookback90 daysHistorical window for counterparty analysis

Each setting has a regulatory default. Override any value through the Settings page or PATCH /v1/settings/rules/{rule_key}. Changes apply immediately across all six agents.

4. Analyst Decisions

When a compliance analyst marks an alert as "False Positive" or "Escalate to Case," that decision flows back into the ML pipeline as a training label. The analyst does not need to know this. Every disposition improves the next triage cycle automatically.

Thirteen ML models consume analyst feedback: the meta-learner, false positive classifier, behavioral similarity model, community detection, BNN uncertainty estimator, and eight others. The system becomes more accurate with every resolved alert.

What Veris Generates Automatically

Every output below runs without customer action. Veris reads the blockchain, applies ML models, and delivers finished compliance work.

Core Pipeline

Runs continuously on every transaction.

Transaction Monitoring. The Go indexer reads every block from ten chains directly. Your team does not send transaction data. The system processes 7M+ stablecoin transfers per day with sub-second latency.

Sanctions Screening. Every transfer is checked against OFAC SDN and 70,000+ labeled addresses. Sanctions matches produce immediate alerts. Travel rule thresholds are evaluated per jurisdiction.

Risk Scoring. Every active address receives a composite risk score every 15 minutes. The scoring engine evaluates 16 factors including counterparty risk, behavioral drift, stablecoin health, and cross-chain evasion patterns.

AI Agents (Zero-Touch Resolution)

Three agents form the resolution pipeline. They handle the volume. Your analysts handle the judgment.

Triage Agent. Classifies every alert in 2 to 3 seconds. Deterministic pre-filter rules auto-resolve high-confidence false positives. Sanctions matches are escalated without delay. Ambiguous alerts go to the LLM for deeper analysis.

Investigation Agent. When an alert escalates to a case, the agent gathers 90 days of on-chain history, runs counterparty analysis, checks sanctions exposure, and produces a structured evidence package. Time to completion: 15 to 25 seconds.

SAR Generation Agent. When the investigation recommends filing, this agent drafts a FinCEN narrative and generates BSA XML. A quality gate validates every field. The analyst reviews the draft and approves filing.

Supporting Agents

Three agents run on periodic schedules, included in the platform fee.

AgentScheduleOutput
Travel RulePer transaction (above threshold)Originator/beneficiary PII capture
Reserve MonitorDaily 07:00 UTCProof-of-reserves attestation, GENIUS Act report
Regulatory ChangeDaily 08:00 UTCRule change detection, parameter update webhook

Continuous ML

Background processes that run without intervention.

ProcessFrequencyPurpose
Anomaly detectionEvery 15 minSeven-detector ensemble, entity-aware thresholds
Typology detectionEvery 30 minStructuring, mixer usage, flash loan patterns
Entity clusteringEvery 60 secAddress-to-entity grouping via on-chain heuristics
Behavioral baselinesEvery 5 minPer-address behavioral profiles for self-drift detection
Cross-chain evasionDailyBridge correlation, chain-hopping detection
Community detectionDailyLeiden algorithm, GLOSH outlier scoring
Depeg monitoringEvery 10 minReserve ratios, burn acceleration, volume anomalies
Sanctions updateDaily 03:00 UTCOFAC SDN automatic refresh
Model self-improvementContinuousAnalyst verdicts feed 13 ML training pipelines

The Analyst Workflow

What your compliance team does day to day. Five steps.

Step 1: Log in. Open the dashboard. Alerts are already triaged, scored, and prioritized. High-confidence false positives are auto-resolved. The queue shows only alerts that need human review.

Step 2: Review alerts. Each alert shows the risk score, entity details, counterparty analysis, and the AI triage reasoning. Two options: dismiss as false positive or escalate to a case.

Step 3: Read the investigation. Escalated alerts become cases. The AI investigation agent has already gathered evidence, built a timeline, and produced a recommendation.

Step 4: Approve the SAR. If filing is warranted, review the AI-drafted narrative and BSA XML. Edit if needed. Click "File." Veris submits to FinCEN via secure SFTP.

Step 5: Go home. Veris continues working overnight. Sanctions lists update at 03:00 UTC. Reserves are attested at 07:00 UTC. Regulatory feeds are scanned at 08:00 UTC. Anomaly detection runs every 15 minutes. Nothing is missed.

The Autonomous Workforce

The CCO reviews. The agents produce.

Veris deploys six specialized AI agents organized into two operational tiers. Each agent performs a discrete compliance function, produces structured output with explicit reasoning, and hands off to the next agent in the pipeline through evidence chaining. No agent operates as a black box. Every decision includes the rationale that produced it.

Tier 1: Resolution Team

Tier 1 agents execute in real time on a per-alert basis. They form the sequential pipeline that transforms a raw alert into a finished resolution. Each agent receives the output of the previous agent as input context, creating a continuous chain of evidence and reasoning.

Triage Agent

Classifies incoming alerts within 2-3 seconds. Determines whether an alert is a false positive, requires investigation, or warrants immediate SAR filing.

PropertyValue
TriggerAlert ingestion (real-time)
Latency2-3 seconds
ModelGemini Flash
OutputClassification, confidence score, reasoning chain, auto-resolution eligibility

The Triage Agent evaluates 39 feature dimensions including transaction velocity, counterparty risk exposure, mixer proximity, and behavioral deviation from established baselines. Its reasoning field provides a plain-language explanation of which factors drove the classification decision.

Alerts classified as false_positive with confidence above 0.95 are eligible for auto-resolution, closing the alert without human review. All other classifications advance to the Investigation Agent.

Investigation Agent

Conducts deep-context analysis over a 90-day transaction window. Synthesizes counterparty intelligence, on-chain evidence, sanctions screening results, and related alert history into a structured evidence package.

PropertyValue
TriggerTriage classification of needs_investigation or immediate_sar
Latency15-25 seconds
ModelClaude
OutputSummary, evidence items, transaction timeline, counterparty analysis, risk assessment, recommendation

The Investigation Agent queries multiple data sources: the transaction graph (90-day lookback), entity attribution database (70,000+ labeled addresses), sanctions lists, and the vector store of previously resolved cases. Its recommendation field contains one of three values: clear, escalate, or file_sar. Every claim in the evidence package includes source attribution.

SAR Generation Agent

Produces FinCEN-compliant SAR narratives and BSA XML filings. Operates only when the Investigation Agent recommends file_sar or when manually triggered by a compliance officer.

PropertyValue
TriggerInvestigation recommendation of file_sar or manual request
Latency10-20 seconds
ModelClaude
OutputSAR narrative (prose), BSA XML (schema-validated), typology codes with justifications, filing metadata

The SAR narrative is written to examiner standards. Typology codes reference FinCEN guidance and are accompanied by justification text explaining why each code applies to the specific case. The XML output passes XSD validation before delivery.

Tier 2: Always-on Monitoring

Tier 2 agents operate on periodic schedules independent of individual alerts. They provide continuous surveillance across compliance dimensions that require ongoing attention rather than per-transaction analysis.

Travel Rule Agent

Monitors compliance with FATF Travel Rule requirements across jurisdictions. Tracks originator and beneficiary information completeness for transactions exceeding applicable thresholds.

PropertyValue
ScheduleContinuous (event-driven on qualifying transactions)
ScopeCross-jurisdictional threshold monitoring
OutputCompliance status per transaction, missing data fields, jurisdiction-specific requirements

Reserve Monitor Agent

Tracks stablecoin reserve health, attestation timing, and depeg risk indicators. Provides early warning when reserve conditions deviate from expected parameters.

PropertyValue
ScheduleEvery 5 minutes
ScopeAll monitored stablecoin issuers
OutputReserve ratio, attestation freshness, depeg probability score, anomaly flags

Regulatory Change Agent

Monitors regulatory developments across relevant jurisdictions and assesses impact on current compliance configurations. Flags changes that require policy updates or procedural modifications.

PropertyValue
ScheduleDaily
ScopeUS (FinCEN, OCC, SEC), EU (MiCA), FATF guidance
OutputChange summaries, impact assessments, recommended policy adjustments

Agent Summary

AgentTierTriggerLatencyPrimary Output
Triage1Per alert2-3sClassification + reasoning
Investigation1Post-triage15-25sEvidence package + recommendation
SAR Generation1Post-investigation10-20sNarrative + BSA XML
Travel Rule2Event-drivenContinuousCompliance status
Reserve Monitor2Every 5 minPeriodicReserve health + depeg risk
Regulatory Change2DailyPeriodicImpact assessment

Agent Coordination

Tier 1 agents coordinate through a pipeline handoff model. Each agent writes its output to a structured artifact, and the next agent in the sequence receives that artifact as part of its input context. This design provides three properties:

Evidence chaining. The Investigation Agent sees the Triage Agent's reasoning. The SAR Agent sees both the triage classification and the investigation evidence. No context is lost between stages.

Auditability. Each handoff is recorded as an immutable audit log entry with a SHA-256 hash linking it to the previous entry. The complete chain from alert ingestion to final disposition is reconstructable at any time.

Parallelism where possible. While the core resolution pipeline is sequential (triage must complete before investigation begins), Tier 2 agents operate independently and concurrently. Reserve monitoring does not block triage. Regulatory change detection does not delay SAR generation.

Capacity

The workforce scales horizontally. Each Tier 1 agent runs on dedicated Celery worker pools with configurable concurrency. Under standard load, the system processes hundreds of concurrent resolutions without queue backpressure. Tier 2 agents run on separate worker pools to ensure monitoring tasks never compete with resolution workload for compute resources.

The Orchestrator: POST /v1/resolve

One call. Three agents. Complete resolution.

POST /v1/resolve is the primary entry point to Veris. It accepts a transaction hash or alert identifier, orchestrates the full resolution pipeline (triage, investigation, SAR generation), and delivers finished compliance work to your system via webhook callback.

Endpoint

POST https://api.useveris.finance/v1/resolve

Authentication

All requests require a Bearer token in the Authorization header.

Authorization: Bearer vrs_live_a1b2c3d4e5f6...

Headers

HeaderRequiredDescription
AuthorizationYesAPI key with resolve:write scope
Content-TypeYesapplication/json
Idempotency-KeyRecommendedClient-generated UUID. Prevents duplicate resolution for the same request. Valid for 24 hours.

Request

Submit either a transaction hash or an existing alert identifier. Both trigger the full resolution pipeline.

By Transaction Hash

{
  "tx_hash": "0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
  "callback_url": "https://your-system.com/webhooks/veris",
  "priority": "standard"
}

By Alert ID

{
  "alert_id": "alt_9f3b2c8d-4e7a-11ef-a1b2-0242ac130003",
  "callback_url": "https://your-system.com/webhooks/veris",
  "priority": "standard"
}

Request Parameters

FieldTypeRequiredDescription
tx_hashstringConditionalTransaction hash to resolve. Required if alert_id is not provided.
alert_idstringConditionalExisting alert UUID. Required if tx_hash is not provided.
callback_urlstringYesHTTPS endpoint that receives the completed resolution via webhook POST.
prioritystringNostandard (default) or expedited. Expedited resolutions are placed at the front of the processing queue.

Response

Synchronous Response (HTTP 202 Accepted)

The endpoint returns immediately with a resolution identifier. The full resolution is delivered asynchronously to your callback_url.

{
  "resolution_id": "res_7f3b2c9d",
  "status": "processing",
  "estimated_completion_ms": 35000,
  "poll_url": "https://api.useveris.finance/v1/resolutions/res_7f3b2c9d"
}

Webhook Callback Payload (Complete Resolution)

When all three agents have completed their work, Veris delivers the full resolution to your callback_url via HTTP POST. The payload contains the complete output of every agent, including reasoning fields that explain each decision.

{
  "resolution_id": "res_7f3b2c9d",
  "status": "completed",
  "disposition": "sar_required",
  "resolution_time_ms": 32400,
  "triage": {
    "classification": "needs_investigation",
    "confidence": 0.94,
    "reasoning": "OFAC proximity detected at 2-hop distance. Structuring pattern across 7 transactions below $3,000 threshold within 48-hour window. Counterparty address 0x4a2f... appears in mixer cluster with 89% attribution confidence.",
    "risk_factors": [
      "ofac_proximity_2hop",
      "structuring_pattern",
      "mixer_exposure"
    ],
    "auto_resolution_eligible": false,
    "agent_version": "triage-v2.4.1",
    "completed_at": "2026-03-24T14:22:03.412Z"
  },
  "investigation": {
    "counterparties_analyzed": 14,
    "history_days": 90,
    "evidence_package_url": "https://api.useveris.finance/v1/evidence/res_7f3b2c9d",
    "risk_assessment": "High risk. Mixer exposure 34% of total volume. Cross-chain layering detected via Arbitrum bridge with 3 intermediate wallets. Source of funds traces to unhosted wallet cluster active since 2024-09 with no KYC-verified on-ramp.",
    "recommendation": "file_sar",
    "reasoning": "Investigation examined 14 counterparties across 90-day lookback. 4 counterparties have direct mixer interaction. Transaction graph reveals a fan-out pattern consistent with layering typology (FinCEN Advisory 2023-A002). Arbitrum bridge usage introduces jurisdictional complexity requiring disclosure.",
    "sources_consulted": [
      "transaction_graph_90d",
      "entity_attribution_db",
      "sanctions_screening",
      "related_alerts",
      "vector_store_precedents"
    ],
    "agent_version": "investigation-v3.1.0",
    "completed_at": "2026-03-24T14:22:21.534Z"
  },
  "sar": {
    "narrative_url": "https://api.useveris.finance/v1/sar/res_7f3b2c9d/narrative",
    "xml_url": "https://api.useveris.finance/v1/sar/res_7f3b2c9d.xml",
    "typology_codes": ["004", "030"],
    "typology_justifications": {
      "004": "Structuring: 7 transactions between $2,400 and $2,950 within 48 hours, consistent with deliberate avoidance of $3,000 reporting threshold.",
      "030": "Layering through virtual currency: Funds moved through 3 intermediate wallets and 1 cross-chain bridge before consolidation at destination address."
    },
    "amount_involved_usd": 184750.00,
    "filing_type": "initial",
    "agent_version": "sar-v2.2.0",
    "completed_at": "2026-03-24T14:22:33.012Z"
  },
  "audit_hash": "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}

Pipeline Sequence

Client Veris API Triage Agent Investigation SAR Agent | | | | | | POST /v1/resolve | | | | |------------------->| | | | | 202 Accepted | | | | |<-------------------| | | | | | | | | | | Stage 1: Classify | | | | |------------------->| | | | | | 2.3s | | | | needs_investigation (0.94) | | | |<-------------------| | | | | | | | | | Stage 2: Investigate (triage ctx) | | | |--------------------------------------->| | | | | | 18.1s | | | evidence_package + file_sar | | | |<---------------------------------------| | | | | | | | | Stage 3: Generate SAR (full ctx) | | | |---------------------------------------------------------->| | | | | | 11.6s | | narrative + BSA XML + typology codes | | |<----------------------------------------------------------| | | | | | | | Total: 32.0s | | | | POST callback_url | | | | |<-------------------| | | |

SDK Examples

Python

import veris

client = veris.Client(api_key="vrs_live_a1b2c3d4e5f6...")

# Synchronous resolution (blocks until complete)
resolution = client.resolve(
    tx_hash="0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
    priority="standard"
)

print(resolution.disposition)          # "sar_required"
print(resolution.triage.reasoning)     # Full reasoning chain
print(resolution.investigation.recommendation)  # "file_sar"
print(resolution.sar.narrative_url)    # Download URL for SAR narrative

# Async resolution with webhook
resolution_ref = client.resolve(
    tx_hash="0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
    callback_url="https://your-system.com/webhooks/veris",
    wait=False
)

print(resolution_ref.resolution_id)    # "res_7f3b2c9d"
print(resolution_ref.status)           # "processing"

Node.js

const Veris = require("@veris/sdk");

const client = new Veris.Client({ apiKey: "vrs_live_a1b2c3d4e5f6..." });

// Synchronous resolution
const resolution = await client.resolve({
  txHash: "0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
  priority: "standard",
});

console.log(resolution.disposition);             // "sar_required"
console.log(resolution.triage.reasoning);        // Full reasoning chain
console.log(resolution.investigation.recommendation); // "file_sar"
console.log(resolution.sar.narrativeUrl);        // Download URL

// Async resolution with webhook
const ref = await client.resolve({
  txHash: "0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
  callbackUrl: "https://your-system.com/webhooks/veris",
  wait: false,
});

console.log(ref.resolutionId); // "res_7f3b2c9d"

curl

curl -X POST https://api.useveris.finance/v1/resolve \
  -H "Authorization: Bearer vrs_live_a1b2c3d4e5f6..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "tx_hash": "0x8a3f7b2e1d9c4a6f5e8b3d7c2a1f9e4d6b8c3a5f7e2d1c9b4a6f8e3d7c2a9d",
    "callback_url": "https://your-system.com/webhooks/veris",
    "priority": "standard"
  }'

Webhook Callback Specification

Veris delivers the completed resolution to your callback_url as an HTTP POST with the following properties:

PropertyValue
MethodPOST
Content-Typeapplication/json
Signature HeaderX-Veris-Signature (HMAC-SHA256 of the request body using your webhook secret)
Timeout30 seconds
Retry Policy3 attempts with exponential backoff (10s, 60s, 300s)
Expected ResponseHTTP 2xx. Any non-2xx response triggers a retry.

Verifying Webhook Signatures

import hmac
import hashlib

def verify_webhook(payload_bytes, signature_header, webhook_secret):
    expected = hmac.new(
        webhook_secret.encode(),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header)

Error Handling

HTTP StatusMeaningAction
202Accepted. Resolution is processing.Await webhook or poll poll_url.
400Invalid request. Missing or malformed fields.Fix the request and retry.
401Authentication failure. Invalid or expired API key.Verify your API key.
409Duplicate. An active resolution already exists for this transaction.Use the existing resolution_id from the response body.
422Unprocessable. Transaction hash not found on any monitored chain.Verify the transaction hash and chain.
429Rate limit exceeded.Retry after the duration specified in Retry-After header.
500Internal error.Retry with exponential backoff. Contact support if persistent.

Idempotency

Include an Idempotency-Key header (UUID v4 recommended) to prevent duplicate resolutions. If a request with the same idempotency key is received within 24 hours, Veris returns the existing resolution instead of creating a new one. The response includes a 304 Not Modified status when returning a cached result.

This is critical for webhook-based architectures where network failures may cause your system to retry the initial resolve call. Without an idempotency key, each retry creates a new resolution.

Rate Limits

PlanRequests per minuteConcurrent resolutions
Standard6010
Professional30050
EnterpriseCustomCustom

Rate limit status is returned in response headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1711288800

Resolution Artifacts

Every completed resolution produces a set of structured, immutable artifacts.

Each artifact captures the output of a specific agent in the resolution pipeline, including the reasoning that drove every decision. Artifacts are immutable after creation and are retained according to regulatory requirements.

Artifact Overview

ArtifactProduced ByFormatEndpoint
Triage ResultTriage AgentJSONIncluded in resolution response
Evidence PackageInvestigation AgentJSONGET /v1/evidence/{resolution_id}
SAR NarrativeSAR AgentText (Markdown)GET /v1/sar/{resolution_id}/narrative
BSA XML FilingSAR AgentXMLGET /v1/sar/{resolution_id}.xml
Audit TrailSystemJSONGET /v1/resolutions/{resolution_id}/audit

Triage Artifact

The Triage Agent produces a classification result within 2-3 seconds of alert ingestion. The artifact includes the decision, the confidence level, and the complete reasoning chain that explains which factors influenced the classification.

Schema

{
  "agent": "triage",
  "version": "triage-v2.4.1",
  "completed_at": "2026-03-24T14:22:03.412Z",
  "classification": "needs_investigation",
  "confidence": 0.94,
  "reasoning_chain": [
    {
      "step": 1,
      "factor": "ofac_proximity",
      "observation": "Counterparty 0x4a2f...8b1c appears at 2-hop distance from OFAC-listed address 0x7d3e...1f9a.",
      "weight": 0.35,
      "direction": "escalate"
    },
    {
      "step": 2,
      "factor": "structuring_detection",
      "observation": "7 transactions between $2,400 and $2,950 within 48-hour window. Mean: $2,714. Standard deviation: $187. Pattern consistent with threshold avoidance.",
      "weight": 0.30,
      "direction": "escalate"
    },
    {
      "step": 3,
      "factor": "mixer_exposure",
      "observation": "34% of inbound volume traces to known mixer cluster (attribution confidence: 89%).",
      "weight": 0.25,
      "direction": "escalate"
    },
    {
      "step": 4,
      "factor": "account_age",
      "observation": "Address active for 187 days. Established activity pattern prior to flagged transactions.",
      "weight": 0.10,
      "direction": "neutral"
    }
  ],
  "risk_factors": [
    "ofac_proximity_2hop",
    "structuring_pattern",
    "mixer_exposure",
    "cross_chain_activity"
  ],
  "auto_resolution_eligible": false,
  "feature_vector_summary": {
    "dimensions_evaluated": 39,
    "top_anomalous_features": [
      {"feature": "velocity_24h", "z_score": 3.42},
      {"feature": "mixer_ratio", "z_score": 4.11},
      {"feature": "unique_counterparties_7d", "z_score": 2.87}
    ]
  }
}

Field Reference

FieldTypeDescription
classificationstringOne of false_positive, needs_investigation, immediate_sar
confidencefloat0.0 to 1.0. Model confidence in the classification decision.
reasoning_chainarrayOrdered list of reasoning steps. Each step identifies a factor, the observation, its weight in the decision, and whether it pushed toward escalation or de-escalation.
risk_factorsarrayEnumerated risk factor codes detected during triage.
auto_resolution_eligiblebooleanTrue when classification is false_positive and confidence exceeds 0.95.
feature_vector_summaryobjectSummary of the 39-dimension feature evaluation, highlighting the most anomalous features by z-score.

Evidence Package (Investigation Artifact)

The Investigation Agent produces a comprehensive evidence package after analyzing 90 days of transaction history, counterparty relationships, sanctions screening results, and related case precedents.

Endpoint

GET https://api.useveris.finance/v1/evidence/{resolution_id}

Schema

{
  "agent": "investigation",
  "version": "investigation-v3.1.0",
  "completed_at": "2026-03-24T14:22:21.534Z",
  "resolution_id": "res_7f3b2c9d",
  "summary": "Investigation identified high-risk activity involving mixer exposure, cross-chain layering, and structuring patterns. 14 counterparties analyzed across a 90-day window. 4 counterparties have direct mixer interaction.",
  "reasoning": "The combination of structuring below reporting thresholds, mixer usage exceeding 30% of volume, and cross-chain movement through a bridge with no legitimate business rationale constitutes a pattern consistent with money laundering typologies 004 and 030.",
  "evidence_items": [
    {
      "id": "ev_001",
      "type": "transaction_pattern",
      "description": "Structuring pattern detected across 7 transactions",
      "significance": "high",
      "data": {
        "transaction_count": 7,
        "amount_range_usd": [2400, 2950],
        "time_window_hours": 48,
        "mean_amount_usd": 2714.29,
        "threshold_proximity": 0.98
      },
      "reasoning": "Transaction amounts cluster tightly below the $3,000 reporting threshold. The narrow standard deviation ($187) and compressed time window are inconsistent with organic spending behavior."
    },
    {
      "id": "ev_002",
      "type": "mixer_exposure",
      "description": "Inbound funds from known mixer cluster",
      "significance": "high",
      "data": {
        "mixer_volume_pct": 34.2,
        "attribution_confidence": 0.89,
        "mixer_cluster_id": "mc_tornado_eth_2024",
        "traced_amount_usd": 63184.50
      },
      "reasoning": "34.2% of total inbound volume traces to a mixer cluster with 89% attribution confidence."
    },
    {
      "id": "ev_003",
      "type": "cross_chain_activity",
      "description": "Arbitrum bridge usage with intermediate wallets",
      "significance": "medium",
      "data": {
        "bridge": "Arbitrum L1-L2",
        "intermediate_wallets": 3,
        "total_bridged_usd": 47200.00,
        "destination_chain": "Arbitrum One"
      },
      "reasoning": "Funds were bridged through 3 intermediate wallets before consolidation."
    }
  ],
  "timeline": [
    {"timestamp": "2026-01-15T08:12:00Z", "event": "First transaction from subject address", "amount_usd": 2800.00, "tx_hash": "0xabc1..."},
    {"timestamp": "2026-01-15T14:45:00Z", "event": "Second transaction, same counterparty", "amount_usd": 2650.00, "tx_hash": "0xabc2..."},
    {"timestamp": "2026-02-20T11:30:00Z", "event": "Bridge to Arbitrum via intermediate wallet", "amount_usd": 47200.00, "tx_hash": "0xdef1..."},
    {"timestamp": "2026-03-24T14:20:00Z", "event": "Alert triggered by velocity anomaly detector", "amount_usd": null, "tx_hash": null}
  ],
  "counterparty_analysis": {
    "total_analyzed": 14,
    "high_risk": 4,
    "medium_risk": 3,
    "low_risk": 7,
    "notable_counterparties": [
      {
        "address": "0x4a2f...8b1c",
        "risk_level": "high",
        "labels": ["mixer_user", "ofac_2hop"],
        "interaction_count": 5,
        "total_volume_usd": 63184.50
      }
    ]
  },
  "recommendation": "file_sar",
  "recommendation_reasoning": "Three independent risk indicators converge on the same subject address. Each indicator alone would warrant enhanced review. Together, they establish a pattern that meets the SAR filing threshold under 31 CFR 1020.320.",
  "sources_consulted": [
    "transaction_graph_90d",
    "entity_attribution_db",
    "sanctions_screening",
    "related_alerts",
    "vector_store_precedents"
  ]
}

Evidence Item Types

TypeDescription
transaction_patternStatistical anomaly in transaction behavior (structuring, velocity, timing)
mixer_exposureFunds traced to mixing services or privacy protocols
cross_chain_activityBridge usage, multi-chain movement patterns
sanctions_proximityRelationship to sanctioned addresses (direct or n-hop)
entity_attributionKnown entity labels applied to addresses in the transaction graph
behavioral_deviationDeparture from established baseline activity for the address
dormant_reactivationPreviously inactive address resuming activity

SAR Artifact

The SAR Agent produces two outputs: a human-readable narrative and a machine-readable BSA XML filing. Both are generated from the same underlying evidence and reasoning.

Narrative Endpoint

GET https://api.useveris.finance/v1/sar/{resolution_id}/narrative

Returns the SAR narrative as plain text formatted for direct inclusion in a FinCEN filing.

XML Endpoint

GET https://api.useveris.finance/v1/sar/{resolution_id}.xml

Returns schema-validated BSA XML conforming to the FinCEN E-Filing specification.

SAR Metadata Schema

{
  "agent": "sar",
  "version": "sar-v2.2.0",
  "completed_at": "2026-03-24T14:22:33.012Z",
  "resolution_id": "res_7f3b2c9d",
  "narrative_url": "https://api.useveris.finance/v1/sar/res_7f3b2c9d/narrative",
  "xml_url": "https://api.useveris.finance/v1/sar/res_7f3b2c9d.xml",
  "typology_codes": ["004", "030"],
  "typology_justifications": {
    "004": "Structuring: 7 transactions between $2,400 and $2,950 within a 48-hour period. Amounts cluster within 2% of the $3,000 reporting threshold.",
    "030": "Layering through virtual currency: Funds moved from Ethereum mainnet through 3 intermediate wallets to an Arbitrum bridge, then consolidated at a single destination."
  },
  "amount_involved_usd": 184750.00,
  "date_range": {
    "start": "2026-01-15",
    "end": "2026-03-24"
  },
  "filing_type": "initial",
  "subject_addresses": [
    "0x8a3f...2c9d",
    "0x4a2f...8b1c"
  ],
  "xsd_validation": {
    "valid": true,
    "schema_version": "2.0",
    "validated_at": "2026-03-24T14:22:32.988Z"
  }
}

Audit Trail

Every resolution produces an immutable, hash-chained audit trail. Each entry records a discrete event in the resolution lifecycle and is linked to the previous entry by SHA-256 hash, creating a tamper-evident log.

Endpoint

GET https://api.useveris.finance/v1/resolutions/{resolution_id}/audit

Schema

{
  "resolution_id": "res_7f3b2c9d",
  "chain_valid": true,
  "entries": [
    {
      "sequence": 1,
      "timestamp": "2026-03-24T14:22:01.002Z",
      "event": "resolution_initiated",
      "actor": "api",
      "detail": "Resolution created from tx_hash 0x8a3f...2c9d",
      "hash": "sha256:0000...0000",
      "previous_hash": null
    },
    {
      "sequence": 2,
      "timestamp": "2026-03-24T14:22:01.015Z",
      "event": "triage_dispatched",
      "actor": "orchestrator",
      "detail": "Triage Agent invoked with 39-dimension feature vector",
      "hash": "sha256:a1b2...f0a1b2",
      "previous_hash": "sha256:0000...0000"
    },
    {
      "sequence": 3,
      "timestamp": "2026-03-24T14:22:03.412Z",
      "event": "triage_completed",
      "actor": "triage-v2.4.1",
      "detail": "Classification: needs_investigation (confidence: 0.94)",
      "hash": "sha256:b2c3...a1b2c3",
      "previous_hash": "sha256:a1b2...f0a1b2"
    }
  ]
}

Audit Entry Fields

FieldTypeDescription
sequenceintegerMonotonically increasing event counter within the resolution
timestampstringISO 8601 timestamp with millisecond precision
eventstringEvent type identifier
actorstringSystem component or agent version that produced the event
detailstringHuman-readable description of the event
hashstringSHA-256 hash of the current entry (includes all fields plus previous_hash)
previous_hashstringSHA-256 hash of the preceding entry. Null for the first entry.

Chain Verification

The chain_valid field in the audit response indicates whether the system has verified the integrity of the hash chain. Clients can independently verify by computing the SHA-256 hash of each entry and confirming it matches the hash field, and that each entry's previous_hash matches the preceding entry's hash.

Download Endpoints

ArtifactMethodEndpointContent-Type
Complete ResolutionGET/v1/resolutions/{resolution_id}application/json
Evidence PackageGET/v1/evidence/{resolution_id}application/json
SAR NarrativeGET/v1/sar/{resolution_id}/narrativetext/plain
BSA XMLGET/v1/sar/{resolution_id}.xmlapplication/xml
Audit TrailGET/v1/resolutions/{resolution_id}/auditapplication/json
Evidence ItemGET/v1/evidence/{resolution_id}/items/{item_id}application/json

All download endpoints require authentication via Bearer token with resolutions:read scope.

curl Examples

# Download evidence package
curl -s https://api.useveris.finance/v1/evidence/res_7f3b2c9d \
  -H "Authorization: Bearer vrs_live_a1b2c3d4e5f6..." \
  -o evidence.json

# Download SAR narrative
curl -s https://api.useveris.finance/v1/sar/res_7f3b2c9d/narrative \
  -H "Authorization: Bearer vrs_live_a1b2c3d4e5f6..." \
  -o sar_narrative.txt

# Download BSA XML
curl -s https://api.useveris.finance/v1/sar/res_7f3b2c9d.xml \
  -H "Authorization: Bearer vrs_live_a1b2c3d4e5f6..." \
  -o sar_filing.xml

# Download audit trail
curl -s https://api.useveris.finance/v1/resolutions/res_7f3b2c9d/audit \
  -H "Authorization: Bearer vrs_live_a1b2c3d4e5f6..." \
  -o audit_trail.json

Retention Policy

ArtifactRetention PeriodBasis
Resolution (complete)7 yearsBSA record retention (31 CFR 1010.430)
Evidence Package7 yearsBSA record retention
SAR Narrative7 yearsFinCEN SAR retention requirements
BSA XML7 yearsFinCEN SAR retention requirements
Audit Trail7 yearsSOC 2 and BSA compliance
Triage Artifact7 yearsPart of resolution record

All artifacts are encrypted at rest using AES-256-GCM. Access to artifacts is logged in the audit trail. Deletion before the retention period requires compliance officer authorization and produces an audit entry documenting the reason and approver.

Triage Agent

Classifies every alert within 2 to 3 seconds.

The Triage Agent classifies every alert generated by the Veris platform within 2 to 3 seconds. It determines whether an alert is a false positive, requires human investigation, or warrants immediate SAR filing. Every alert passes through this agent before reaching an analyst queue.

Classification Pipeline

The agent operates in two stages: a deterministic pre-filter and an AI classification layer.

Stage 1: Deterministic Pre-Filter

Before invoking the language model, the agent applies rule-based checks that produce instant classifications with zero ambiguity.

ConditionResultRationale
OFAC SDN list match (exact or fuzzy above 95%)immediate_sarFederal law requires immediate filing. No model discretion permitted.
Known mixer or tumbler entityimmediate_sarMixer interaction is a standalone typology under FinCEN Advisory FIN-2019-A003.
Sanctioned jurisdiction (DPRK, Iran, Syria, Cuba, Crimea)immediate_sarOFAC comprehensive sanctions programs require blocking and reporting.

When the pre-filter triggers, the agent bypasses the LLM entirely and returns a deterministic result with confidence: 1.0 and a citation to the matching rule.

Stage 2: AI Classification

Alerts that pass the pre-filter enter the AI classification layer. The model receives a structured context payload and returns one of three classifications.

Input Context:

Classifications:

ClassificationDefinitionTypical Analyst Action
false_positiveAlert does not represent suspicious activity. Noise from legitimate operations.Auto-close with audit trail.
needs_investigationAlert contains indicators that require human review and deeper analysis.Route to analyst queue for case creation.
immediate_sarAlert contains clear indicators of illicit activity requiring regulatory filing.Escalate to SAR preparation pipeline.

Output Schema

Every triage result conforms to the following structure. Pydantic validation enforces type correctness and value constraints on every response before it is persisted.

{
  "classification": "needs_investigation",
  "severity": "high",
  "confidence": 0.92,
  "reasoning_chain": [
    "Address 0xabc...def received 847,000 USDC from a mixer-adjacent address within 3 blocks.",
    "Recipient address was dormant for 186 days before this transfer.",
    "Amount exceeds the 99th percentile for this entity's historical volume."
  ],
  "risk_factors": [
    {
      "factor": "mixer_proximity",
      "weight": 0.35,
      "detail": "1-hop distance from Tornado Cash deposit address"
    },
    {
      "factor": "dormancy_reactivation",
      "weight": 0.25,
      "detail": "186-day dormancy period followed by high-value inflow"
    },
    {
      "factor": "volume_anomaly",
      "weight": 0.30,
      "detail": "Transfer amount 12.4x above entity 30-day average"
    }
  ],
  "recommended_action": "escalate_to_case"
}

Field Reference

FieldTypeConstraints
classificationstringOne of: false_positive, needs_investigation, immediate_sar
severitystringOne of: low, medium, high, critical
confidencefloatRange: 0.0 to 1.0. Values below 0.6 trigger mandatory human review.
reasoning_chainarray[string]Ordered list of reasoning steps. Minimum 1 entry.
risk_factorsarray[object]Each entry includes factor, weight (0.0 to 1.0), and detail.
recommended_actionstringOne of: auto_close, escalate_to_case, escalate_to_sar

Model Configuration

ParameterValue
ModelGemini Flash
Output formatUnstructured text with JSON extraction
Max tokens2048
Temperature0.1

The agent does not use response_mime_type="application/json". Instead, the model returns text that may include markdown fences, and a dedicated _extract_json() parser handles extraction. This avoids internal JSON parsing failures observed with forced structured output modes.

Anti-Hallucination Controls

  1. Pydantic validation: Every output field is validated against strict type and range constraints before persistence.
  2. Confidence floor: Classifications with confidence below 0.6 are flagged for mandatory human review regardless of the model's recommendation.
  3. Deterministic override: Rule-based pre-filter results always supersede model outputs when both fire on the same alert.
  4. Audit trail: Every triage result is stored in the ai_audit_log hypertable with full input context for post-hoc review.

Latency Profile

MetricValue
P501.8 seconds
P952.9 seconds
P993.4 seconds
Deterministic pre-filter< 50 milliseconds

Investigation Agent

Deep compliance analysis in 15 to 25 seconds.

The Investigation Agent performs deep compliance analysis on escalated alerts, producing structured evidence packages in 15 to 25 seconds. It executes parallel data gathering across multiple subsystems, applies PII redaction before LLM submission, and augments its analysis with historical investigation context retrieved via vector search.

Architecture

The agent uses a 2-phase data gathering architecture built on ThreadPoolExecutor(max_workers=8). Phase A executes independent queries in parallel. Phase B executes dependent queries that require Phase A results as input.

Phase A: Independent Queries (25-second timeout)

All 8 queries execute concurrently. If any query exceeds the 25-second timeout, the agent proceeds with partial results and notes the data gap in its output.

QuerySourceData Retrieved
Transfer historyTimescaleDB90-day transfer events for the subject address
Entity profileTimescaleDBLabels, risk scores, registration metadata
Sanctions checkOFAC + EU + UK listsReal-time screening against consolidated watchlists
Behavioral baselineRedis + TimescaleDBVelocity metrics, dormancy state, peer group statistics
Related alertsTimescaleDBAlerts sharing addresses, entities, or transaction patterns
On-chain graphTimescaleDB2-hop counterparty network with edge weights
Contract analysisEVM stateToken approvals, contract interactions, proxy patterns
Past investigationsQdrantVector similarity search over prior investigation reports

Phase B: Dependent Queries (10-second timeout)

Phase B queries use Phase A results to perform targeted lookups.

QueryDepends OnData Retrieved
Counterparty risk profilesOn-chain graphRisk scores for top 10 counterparties by volume
Cross-chain correlationTransfer historyMatching patterns across Ethereum, Base, Polygon, Arbitrum
Typology matchingBehavioral baseline + transfer historyBest-fit typology codes from the FinCEN registry

RAG: Historical Investigation Context

The agent queries the Qdrant vector store using Gemini text-embedding-004 embeddings to retrieve relevant past investigations. This provides the model with institutional memory: how similar patterns were analyzed, what conclusions were reached, and what regulatory actions followed.

Retrieved documents are ranked by cosine similarity. The top 5 results above a 0.72 similarity threshold are included in the prompt context.

PII Redaction

Before submitting any data to the language model, the agent applies PII redaction to all text fields. The following categories are redacted:

PII CategoryRedaction Method
Personal namesReplaced with [PERSON_1], [PERSON_2], etc.
Email addressesReplaced with [EMAIL_REDACTED]
Phone numbersReplaced with [PHONE_REDACTED]
Physical addressesReplaced with [ADDRESS_REDACTED]
Government IDs (SSN, passport)Replaced with [GOV_ID_REDACTED]

Blockchain addresses, transaction hashes, and token amounts are not redacted. They are required for accurate analysis and do not constitute PII under applicable regulations.

A mapping table is retained server-side so that the final output can be re-hydrated with original values before delivery to the requesting analyst.

Output Schema

{
  "summary": "Address 0xabc...def received 2.1M USDC through a layering pattern involving 14 intermediate wallets over 72 hours. The originating funds trace to a Tornado Cash withdrawal. Behavioral analysis indicates coordinated activity with 3 previously flagged entities.",
  "evidence_items": [
    {
      "type": "transaction",
      "tx_hash": "0x789...012",
      "relevance": "Initial Tornado Cash withdrawal funding the layering chain",
      "amount_usd": 500000.00,
      "timestamp": "2026-03-18T14:22:31Z"
    },
    {
      "type": "entity_link",
      "entity_id": "ent_abc123",
      "relevance": "Shared counterparty with 2 prior SAR filings",
      "risk_score": 0.91
    }
  ],
  "timeline": [
    {"timestamp": "2026-03-18T14:22:31Z", "event": "Tornado Cash withdrawal of 500,000 USDC to 0x111...aaa"},
    {"timestamp": "2026-03-18T15:04:12Z", "event": "Split into 5 transfers of 100,000 USDC across intermediate wallets"},
    {"timestamp": "2026-03-20T09:31:45Z", "event": "Consolidation into 0xabc...def with final balance of 2.1M USDC"}
  ],
  "counterparty_analysis": {
    "total_counterparties": 14,
    "high_risk_counterparties": 3,
    "sanctioned_counterparties": 0,
    "network_density": 0.23
  },
  "recommendation": "file_sar",
  "typology_codes": ["BSA-2019-TC-03", "BSA-2019-TC-07"],
  "confidence": 0.94
}

Field Reference

FieldTypeDescription
summarystringAnalyst-ready narrative summarizing findings. Maximum 500 words.
evidence_itemsarray[object]Discrete pieces of evidence with type, relevance, and supporting data.
timelinearray[object]Chronologically ordered events relevant to the investigation.
counterparty_analysisobjectAggregate statistics on the counterparty network.
recommendationstringOne of: close_no_action, monitor, file_sar.
typology_codesarray[string]Applicable FinCEN typology codes.
confidencefloatRange: 0.0 to 1.0.

Model Configuration

ParameterValue
ModelClaude (Anthropic)
Max tokens4096
Temperature0.2
Context window utilizationUp to 80K tokens for complex cases

Case Integration

When the Investigation Agent completes its analysis, the result is written to the ai_investigation_result JSONB column on the associated case record. If a case does not yet exist for the alert, the agent creates one automatically and links all related alerts.

If the recommendation is file_sar, the case status transitions to pending_sar, making it eligible for SAR Generation Agent processing.

Latency Profile

MetricValue
P5016 seconds
P9523 seconds
P9928 seconds
Phase A (parallel gather)3 to 8 seconds
Phase B (dependent gather)1 to 4 seconds
LLM inference10 to 18 seconds

SAR Generation Agent

Regulatory filing narratives and structured XML in 10 to 20 seconds.

The SAR Generation Agent produces regulatory filing narratives and structured XML documents in 10 to 20 seconds. It supports three jurisdictions (US, EU, UK), three languages (EN, DE, FR), and includes a hallucination gate that verifies every factual claim against source data before output delivery.

Jurisdiction Support

JurisdictionRegulatory BodyFiling FormatNarrative Standard
United StatesFinCENBSA XML (FinCEN SAR)Part V narrative, 5W structure
European UnionAMLA (MiCA framework)goAML XMLSTR narrative per national FIU requirements
United KingdomFCA / NCASAR Online (NCA format)DAML or consent SAR narrative

Narrative Structure: FinCEN 5W Framework

For US filings, the narrative follows the FinCEN-mandated 5W structure. Each section is generated independently and assembled into the final Part V narrative.

SectionContent
WhoSubject identification. Entity type, known aliases, wallet addresses, risk profile summary.
WhatSuspicious activity description. Transaction patterns, typology classification, deviation from baseline behavior.
WhereGeographic and chain context. Blockchain networks involved, jurisdiction of known counterparties, IP geolocation data (when available).
WhenTemporal scope. Date range of suspicious activity, key timestamps, sequence of events.
WhyAnalyst reasoning. Why the activity is suspicious, what legitimate explanations were considered and excluded, regulatory basis for filing.

Language Support

LanguageJurisdictionsNotes
EnglishUS, UK, EUDefault for all jurisdictions
GermanEURequired by BaFin for German-supervised entities
FrenchEURequired by ACPR for French-supervised entities

The language is selected per filing. The agent generates the narrative in the target language natively rather than translating from English, ensuring idiomatic regulatory terminology.

Hallucination Gate

Every SAR narrative passes through a fact verification layer before delivery. The gate checks every verifiable claim in the generated text against the source data that was provided to the model.

Verified Fields

Field TypeVerification Method
Blockchain addressesExact string match against case transfer records
Transaction hashesExact string match against case transfer records
USD amountsNumeric match within 0.01 tolerance against source amounts
TimestampsISO 8601 match against source event timestamps
Entity namesExact match against entity profile records
Typology codesValidated against the FinCEN typology registry

Verification Process

  1. The agent extracts all factual claims from the generated narrative using structured parsing.
  2. Each claim is compared against the investigation evidence payload.
  3. Claims that fail verification are flagged with the specific discrepancy.
  4. If any claim fails, the narrative is regenerated with explicit correction instructions.
  5. The retry loop executes up to 3 times. If verification still fails after 3 attempts, the output is delivered with a verification_warnings array detailing unresolved discrepancies for human review.

Typology Code Selection

The agent selects typology codes from the FinCEN advisory registry based on the investigation evidence. Each selected code includes a justification explaining why the observed activity matches that typology.

{
  "typology_codes": ["BSA-2019-TC-03", "BSA-2019-TC-07"],
  "typology_justifications": [
    {
      "code": "BSA-2019-TC-03",
      "name": "Layering through multiple wallets",
      "justification": "Funds moved through 14 intermediate wallets within 72 hours with no economic purpose. Each hop involved a different chain to obscure the trail."
    },
    {
      "code": "BSA-2019-TC-07",
      "name": "Use of mixing services",
      "justification": "Originating funds trace to a Tornado Cash withdrawal. 500,000 USDC deposited to the mixer 4 hours before the first layering transfer."
    }
  ]
}

Output Schema

{
  "narrative": "Part V narrative text (typically 800-2000 words)...",
  "narrative_language": "en",
  "jurisdiction": "us",
  "typology_codes": ["BSA-2019-TC-03", "BSA-2019-TC-07"],
  "typology_justifications": [],
  "amount_usd": 2147500.00,
  "date_range": {
    "start": "2026-03-18T00:00:00Z",
    "end": "2026-03-20T23:59:59Z"
  },
  "filing_type": "initial",
  "subject_count": 3,
  "verification_status": "passed",
  "verification_warnings": [],
  "xml_document": "base64-encoded BSA XML document"
}

Field Reference

FieldTypeDescription
narrativestringComplete filing narrative in the specified language.
narrative_languagestringISO 639-1 code: en, de, or fr.
jurisdictionstringOne of: us, eu, uk.
typology_codesarray[string]Selected FinCEN typology codes.
typology_justificationsarray[object]Per-code justification with name and reasoning.
amount_usdfloatTotal suspicious activity amount in USD.
date_rangeobjectStart and end timestamps of the activity period.
filing_typestringOne of: initial, continuing, joint.
subject_countintegerNumber of subjects identified in the filing.
verification_statusstringOne of: passed, passed_with_warnings, failed.
verification_warningsarray[string]Discrepancies that could not be resolved after retry.
xml_documentstringBase64-encoded regulatory XML (BSA, goAML, or NCA format).

XML Validation

Generated XML documents are validated against the official XSD schemas before delivery:

JurisdictionSchema
US (FinCEN)FinCEN SAR XML schema (current version)
EU (goAML)UNODC goAML 4.x XSD
UK (NCA)NCA SAR Online schema

Documents that fail XSD validation are regenerated. Validation errors are logged to the ai_audit_log for compliance review.

Storage and Encryption

Filed SAR documents are encrypted at rest using AES-256 and stored in the compliance archive. Access requires sar:read permission and generates an audit log entry for every retrieval.

Latency Profile

MetricValue
P5012 seconds
P9518 seconds
P9924 seconds
Hallucination gate1 to 3 seconds
XML generation and validation< 500 milliseconds

Reserve Monitor Agent

Proof of Reserves verification for GENIUS Act compliance.

The Reserve Monitor Agent verifies stablecoin reserve attestations against on-chain circulating supply on a daily schedule. It produces audit-ready Proof of Reserves reports formatted for GENIUS Act compliance and triggers alerts when reserve shortfalls exceed configurable thresholds.

Schedule

The agent executes daily at 07:00 UTC. This timing ensures reports are available at the start of business hours across US and European time zones. Ad-hoc executions can be triggered via the API for on-demand verification.

Function

The agent performs the following operations in sequence:

  1. Query circulating supply: Retrieve total supply for each monitored stablecoin by querying on-chain token contracts across all indexed chains (Ethereum, Base, Polygon, Arbitrum).
  2. Retrieve reserve attestations: Pull the latest reserve attestation data from issuer feeds and on-chain proof mechanisms.
  3. Compute reserve ratio: Calculate reserve_value / circulating_supply for each token.
  4. Evaluate thresholds: Compare the reserve ratio against configured alert thresholds.
  5. Generate report: Produce a structured Proof of Reserves report.

Monitored Stablecoins

TokenIssuerReserve Type
USDCCircleMonthly attestation (Grant Thornton) + real-time on-chain
USDTTetherQuarterly reserve report + on-chain
DAIMakerDAOOn-chain collateral (fully transparent)
FRAXFrax FinanceOn-chain AMO + off-chain reserves

Additional tokens can be added through the configuration API without code changes.

Shortfall Detection

Thresholds are configurable per token and per client. Default thresholds:

ThresholdReserve RatioAction
Healthy>= 1.00No action. Report filed as clean attestation.
Warning0.98 to 0.9999reserve.warning webhook fired. Report flagged for review.
Critical0.95 to 0.9799reserve.critical webhook fired. Analyst notification.
Emergency< 0.95reserve.emergency webhook fired. All pending transactions for the affected token are held for manual review.

Output: Proof of Reserves Report

The report conforms to the disclosure requirements outlined in the GENIUS Act (Guiding and Establishing National Innovation for U.S. Stablecoins).

{
  "report_id": "por_2026032407",
  "generated_at": "2026-03-24T07:00:14Z",
  "reporting_period": {
    "start": "2026-03-23T00:00:00Z",
    "end": "2026-03-23T23:59:59Z"
  },
  "tokens": [
    {
      "symbol": "USDC",
      "circulating_supply": 43217890456.12,
      "reserve_value": 43298100000.00,
      "reserve_ratio": 1.00186,
      "status": "healthy",
      "chains": [
        {"chain": "ethereum", "supply": 28450000000.00},
        {"chain": "base", "supply": 8120000000.00},
        {"chain": "polygon", "supply": 4230000000.00},
        {"chain": "arbitrum", "supply": 2417890456.12}
      ],
      "attestation_source": "Grant Thornton monthly attestation (2026-02-28)",
      "attestation_lag_days": 24
    }
  ],
  "aggregate": {
    "total_circulating": 87654321000.00,
    "total_reserves": 87890100000.00,
    "aggregate_ratio": 1.00269,
    "status": "healthy"
  },
  "genius_act_compliance": {
    "daily_disclosure": true,
    "reserve_composition_disclosed": true,
    "third_party_attestation": true,
    "redemption_capability_confirmed": true
  }
}

Field Reference

FieldTypeDescription
report_idstringUnique identifier for the report. Format: por_YYYYMMDDHH.
generated_atstringISO 8601 timestamp of report generation.
reporting_periodobject24-hour window covered by the report.
tokensarray[object]Per-token reserve verification results.
tokens[].reserve_ratiofloatReserve value divided by circulating supply.
tokens[].attestation_lag_daysintegerDays since the most recent third-party attestation.
aggregateobjectCross-token aggregate reserve metrics.
genius_act_complianceobjectBoolean checklist for GENIUS Act disclosure requirements.

Webhook Notifications

When a threshold is breached, the agent fires a webhook to all configured endpoints.

{
  "event": "reserve.warning",
  "token": "USDT",
  "reserve_ratio": 0.9923,
  "circulating_supply": 44100000000.00,
  "reserve_value": 43760430000.00,
  "shortfall_usd": 339570000.00,
  "timestamp": "2026-03-24T07:00:14Z",
  "report_id": "por_2026032407"
}

Data Retention

Proof of Reserves reports are retained for 7 years in compliance with BSA record-keeping requirements. Reports are stored in the reserve_attestations hypertable with 30-day compression.

Latency Profile

MetricValue
Full report generation8 to 15 seconds
Per-chain supply query1 to 3 seconds
Attestation retrieval2 to 5 seconds

Travel Rule Agent

FATF Recommendation 16 compliance for every qualifying transfer.

The Travel Rule Agent captures originator and beneficiary personally identifiable information (PII) for every transaction exceeding the $3,000 threshold, ensuring compliance with FATF Recommendation 16 and its national implementations.

Trigger Condition

The agent activates on every transfer event where amount_usd >= 3000.00. This threshold aligns with:

The threshold is configurable per client to accommodate jurisdiction-specific requirements. EU-supervised clients typically set this to 0 to comply with TFR requirements.

Function

For each qualifying transfer, the agent:

  1. Identifies the originator VASP: Resolves the sending address to its hosting Virtual Asset Service Provider using the Veris address intelligence database.
  2. Identifies the beneficiary VASP: Resolves the receiving address to its hosting VASP.
  3. Retrieves originator PII: Queries the originator VASP via the IVMS 101 messaging protocol for required data fields.
  4. Retrieves beneficiary PII: Queries the beneficiary VASP for required data fields.
  5. Validates completeness: Ensures all mandatory fields are present per the applicable jurisdiction's requirements.
  6. Files the record: Persists the structured PII record linked to the transfer event.

Required Data Fields (FATF Recommendation 16)

Originator Information

FieldRequiredIVMS 101 Path
Full nameYesoriginator.naturalPerson.name
Account number (wallet address)Yesoriginator.accountNumber
Physical address OR national ID OR date/place of birthYes (at least one)originator.geographicAddress / originator.nationalIdentification / originator.dateAndPlaceOfBirth

Beneficiary Information

FieldRequiredIVMS 101 Path
Full nameYesbeneficiary.naturalPerson.name
Account number (wallet address)Yesbeneficiary.accountNumber

IVMS 101 Compliance

All PII records conform to the InterVASP Messaging Standard (IVMS 101), the industry standard data model for travel rule information exchange. The agent supports the following transport protocols for inter-VASP communication:

ProtocolStatusNotes
TRISASupportedTravel Rule Information Sharing Architecture
TRP (OpenVASP)SupportedTravel Rule Protocol by OpenVASP Association
Sygna BridgeSupportedUsed primarily in APAC jurisdictions

Output Schema

{
  "transfer_id": "txn_abc123def456",
  "tx_hash": "0x789...012",
  "amount_usd": 47500.00,
  "threshold_applied": 3000.00,
  "jurisdiction": "us",
  "originator": {
    "vasp_name": "Exchange Alpha",
    "vasp_lei": "529900T8BM49AURSDO55",
    "natural_person": {
      "name": "[REDACTED]",
      "account_number": "0xabc...def",
      "geographic_address": "[REDACTED]"
    },
    "data_complete": true
  },
  "beneficiary": {
    "vasp_name": "Exchange Beta",
    "vasp_lei": "213800WSGIIZCXF1P572",
    "natural_person": {
      "name": "[REDACTED]",
      "account_number": "0x111...222",
      "geographic_address": "[REDACTED]"
    },
    "data_complete": true
  },
  "compliance_status": "complete",
  "filed_at": "2026-03-24T14:22:31Z",
  "ivms_version": "101.2020"
}

Note: PII fields are redacted in API responses. Full PII is accessible only through the compliance portal with travel_rule:read permission and generates an audit log entry on every access.

Compliance Status Values

StatusMeaning
completeAll required PII fields present for both originator and beneficiary.
partialOne or more required fields missing. Counterparty VASP has been notified. 72-hour remediation window active.
failedCounterparty VASP did not respond within the remediation window. Alert generated for compliance team.
exemptTransfer is exempt from travel rule (self-hosted wallet below jurisdiction threshold).

Unhosted Wallet Handling

When the originator or beneficiary address resolves to an unhosted (self-custody) wallet, the agent applies jurisdiction-specific rules:

JurisdictionUnhosted Wallet Rule
US (FinCEN)No additional requirements below $3,000. Above $3,000, VASP must collect and retain counterparty information.
EU (TFR)Above EUR 1,000, VASP must verify ownership of the unhosted wallet. Below EUR 1,000, simplified due diligence.
UK (FCA)VASP must collect beneficiary information regardless of wallet type for transfers above GBP 1,000.

Data Retention

Travel rule records are retained for 5 years from the date of the transfer, per FATF Recommendation 11 and BSA record-keeping requirements. PII is encrypted at rest using AES-256.

Latency Profile

MetricValue
VASP identification< 500 milliseconds
PII retrieval (cooperative VASP)1 to 5 seconds
PII retrieval (slow VASP)Up to 30 seconds (async completion)
Record filing< 200 milliseconds

Regulatory Change Agent

8 regulatory feeds. Daily monitoring. Actionable impact analysis.

The Regulatory Change Agent monitors 8 regulatory feeds daily, detects rule changes that affect compliance operations, maps the impact to Veris configuration parameters, and fires webhooks with actionable payloads. It enables institutions to respond to regulatory shifts within hours rather than weeks.

Schedule

The agent executes daily at 08:00 UTC. This runs 1 hour after the Reserve Monitor Agent, ensuring that reserve-related regulatory changes are evaluated against the most recent Proof of Reserves data.

Ad-hoc executions can be triggered via the API when a breaking regulatory announcement requires immediate assessment.

Monitored Feeds

FeedJurisdictionContent Type
FinCENUnited StatesAdvisories, rules, interpretive guidance, SAR filing updates
OFACUnited StatesSDN list updates, sectoral sanctions, general licenses, FAQs
FATFInternationalRecommendations, mutual evaluation reports, high-risk jurisdiction lists
EU AMLAEuropean UnionAnti-Money Laundering Authority directives, MiCA implementing measures
FCAUnited KingdomPolicy statements, consultation papers, supervisory notices
MASSingaporeNotices, guidelines, consultation papers
SFCHong KongCirculars, codes of conduct, licensing updates
JFSAJapanCabinet orders, supervisory guidelines, no-action letters

Detection Process

The agent performs the following steps for each feed:

  1. Fetch: Retrieve the latest publications since the last successful check. Each feed has a dedicated parser tuned to its publication format (RSS, HTML scraping, API, or PDF extraction).
  2. Classify: Determine whether each publication constitutes a rule change, guidance update, enforcement action, or informational notice.
  3. Extract parameters: Identify specific regulatory parameters that changed (thresholds, reporting deadlines, entity definitions, prohibited activities).
  4. Map impact: Correlate changed parameters to Veris configuration values, screening rules, and filing templates.
  5. Score urgency: Assign an urgency level based on effective date proximity and operational impact.
  6. Notify: Fire webhooks to all configured endpoints with the change payload.

Urgency Levels

LevelDefinitionExpected Response Time
criticalRule takes effect within 7 days or has retroactive application. Immediate configuration change required.Same business day
highRule takes effect within 30 days. Configuration change required before effective date.Within 5 business days
mediumRule takes effect within 90 days. Planning and implementation required.Within 30 days
lowGuidance or interpretive update. No immediate configuration change, but policy review recommended.Next quarterly review

Output: Webhook Payload

When the agent detects a rule change, it fires a policy.update_required webhook with the following payload:

{
  "event": "policy.update_required",
  "change_id": "rc_20260324_fincen_001",
  "source": "fincen",
  "jurisdiction": "us",
  "urgency": "high",
  "publication": {
    "title": "FinCEN Issues Final Rule on Convertible Virtual Currency Mixing",
    "url": "https://www.fincen.gov/news/news-releases/...",
    "published_at": "2026-03-23T16:00:00Z",
    "document_type": "final_rule"
  },
  "impact": {
    "summary": "New reporting requirements for transactions involving CVC mixing services. Threshold lowered from $10,000 to $1,000 for mixer-related transactions.",
    "affected_parameters": [
      {
        "parameter": "mixer_reporting_threshold_usd",
        "current_value": 10000.00,
        "required_value": 1000.00,
        "effective_date": "2026-04-22T00:00:00Z"
      }
    ],
    "affected_modules": [
      "triage_agent.deterministic_prefilter",
      "investigation_agent.typology_matching",
      "sar_generation_agent.filing_templates"
    ]
  },
  "recommended_actions": [
    "Update mixer reporting threshold from $10,000 to $1,000 in screening configuration.",
    "Review and update SAR narrative templates for mixer-related filings.",
    "Retrain triage model with updated threshold boundary."
  ],
  "timestamp": "2026-03-24T08:00:32Z"
}

Field Reference

FieldTypeDescription
eventstringAlways policy.update_required.
change_idstringUnique identifier. Format: rc_YYYYMMDD_source_seq.
sourcestringRegulatory feed identifier.
jurisdictionstringISO 3166-1 jurisdiction code or international for FATF.
urgencystringOne of: critical, high, medium, low.
publicationobjectSource document metadata with URL and publication date.
impact.summarystringPlain-language description of the change and its operational impact.
impact.affected_parametersarray[object]Specific configuration values that require update, with current and required values.
impact.affected_modulesarray[string]Veris modules affected by the change.
recommended_actionsarray[string]Ordered list of steps to achieve compliance with the new rule.

OFAC SDN List Updates

OFAC SDN list changes receive special handling due to their immediate legal effect. When the agent detects an SDN list update:

  1. The updated list is ingested and indexed within 15 minutes of publication.
  2. A retroactive sweep executes against all monitored addresses to identify new matches.
  3. New matches trigger immediate alert generation with immediate_sar classification.
  4. A policy.update_required webhook fires with urgency: critical.

Feed Health Monitoring

Each feed is monitored for availability and freshness. If a feed fails to return new data for longer than its expected publication cadence, the agent fires a feed.health_warning webhook.

FeedExpected CadenceAlert After
OFACWeekly (with ad-hoc updates)14 days
FinCENMonthly45 days
FATFQuarterly120 days
EU AMLAMonthly45 days
FCAWeekly21 days
MASMonthly45 days
SFCMonthly45 days
JFSAMonthly45 days

Data Retention

Regulatory change records are retained indefinitely. They form the compliance audit trail demonstrating that the institution was aware of and responded to regulatory changes in a timely manner.

Standard Authentication (API Key + JWT)

Standard authentication combines a long-lived API key for service identification with short-lived JWT tokens for session-scoped access. This method is appropriate for Tier 3 (Emerging) integrations and automated compliance pipelines where mutual TLS is not required.

API Key Format

Veris API keys follow a deterministic format that encodes environment context:

vrs_live_<32-256 alphanumeric characters>
vrs_test_<32-256 alphanumeric characters>

Keys prefixed with vrs_live_ operate against production data. Keys prefixed with vrs_test_ operate against the sandbox environment. All API requests must include the key in the X-API-Key header.

PropertyValue
Header nameX-API-Key
Key length32 to 256 characters after prefix
Character setAlphanumeric (a-z, A-Z, 0-9)
Environment separationvrs_live_ (production), vrs_test_ (sandbox)
RevocationImmediate via API or dashboard

API keys authenticate the calling organization. They do not expire automatically but can be rotated or revoked at any time through the Veris dashboard or the key management API.

JWT Bearer Tokens

For session-scoped operations, Veris issues JWT tokens signed with RS256. Tokens are obtained by authenticating against the login endpoint with valid credentials and a valid API key.

Token Properties

PropertyValue
AlgorithmRS256
Expiry60 minutes
Issuerapi.useveris.finance
HeaderAuthorization: Bearer <token>

Obtaining a Token

POST /api/v1/auth/login

Request body:

{
  "email": "compliance-team@example.com",
  "password": "your-password"
}

Required header:

X-API-Key: vrs_live_abc123...

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "vrs_rt_d8f2a1..."
}

Token Refresh

Refresh tokens before expiry to maintain uninterrupted access. The refresh endpoint issues a new access token and rotates the refresh token. Previous refresh tokens are invalidated immediately upon use.

POST /api/v1/auth/refresh

Request body:

{
  "refresh_token": "vrs_rt_d8f2a1..."
}

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "vrs_rt_e9a3b2..."
}

Code Samples

curl

Authenticate and obtain a JWT:

# Step 1: Obtain JWT token
TOKEN=$(curl -s -X POST https://api.useveris.finance/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "X-API-Key: vrs_live_abc123def456ghi789jkl012mno345" \
  -d '{"email": "compliance-team@example.com", "password": "your-password"}' \
  | jq -r '.access_token')

# Step 2: Use JWT for subsequent requests
curl -s https://api.useveris.finance/api/v1/alerts \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-API-Key: vrs_live_abc123def456ghi789jkl012mno345"

API key only (for endpoints that do not require JWT):

curl -s https://api.useveris.finance/api/v1/health \
  -H "X-API-Key: vrs_live_abc123def456ghi789jkl012mno345"

Python

import httpx

API_KEY = "vrs_live_abc123def456ghi789jkl012mno345"
BASE_URL = "https://api.useveris.finance/api/v1"


def authenticate(email: str, password: str) -> dict:
    """Obtain JWT and refresh tokens."""
    response = httpx.post(
        f"{BASE_URL}/auth/login",
        headers={"X-API-Key": API_KEY},
        json={"email": email, "password": password},
    )
    response.raise_for_status()
    return response.json()


def refresh_token(refresh_token: str) -> dict:
    """Rotate the refresh token and obtain a new access token."""
    response = httpx.post(
        f"{BASE_URL}/auth/refresh",
        headers={"X-API-Key": API_KEY},
        json={"refresh_token": refresh_token},
    )
    response.raise_for_status()
    return response.json()


# Usage
tokens = authenticate("compliance-team@example.com", "your-password")
access_token = tokens["access_token"]

# Authenticated request
response = httpx.get(
    f"{BASE_URL}/alerts",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-API-Key": API_KEY,
    },
)
alerts = response.json()

Node.js

const API_KEY = "vrs_live_abc123def456ghi789jkl012mno345";
const BASE_URL = "https://api.useveris.finance/api/v1";

async function authenticate(email, password) {
  const response = await fetch(`${BASE_URL}/auth/login`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
    },
    body: JSON.stringify({ email, password }),
  });

  if (!response.ok) {
    throw new Error(`Authentication failed: ${response.status}`);
  }

  return response.json();
}

async function refreshToken(refreshToken) {
  const response = await fetch(`${BASE_URL}/auth/refresh`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
    },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });

  if (!response.ok) {
    throw new Error(`Token refresh failed: ${response.status}`);
  }

  return response.json();
}

// Usage
const tokens = await authenticate(
  "compliance-team@example.com",
  "your-password"
);

const alertsResponse = await fetch(`${BASE_URL}/alerts`, {
  headers: {
    Authorization: `Bearer ${tokens.access_token}`,
    "X-API-Key": API_KEY,
  },
});

const alerts = await alertsResponse.json();

When to Use Standard Authentication

Standard authentication is appropriate for the following scenarios:

ScenarioRecommendation
Tier 3 (Emerging) integrationsRequired minimum
Automated compliance pipelinesRecommended with payload signatures
Internal tooling and dashboardsSufficient when combined with IP whitelisting
Development and sandbox testingUse vrs_test_ keys

For Tier 2 (Enterprise) and Tier 1 (Sovereign) integrations, use mTLS authentication instead. Standard authentication can be combined with payload signatures and IP whitelisting for additional security layers.

Error Responses

HTTP StatusError CodeDescription
401invalid_api_keyThe API key is malformed, revoked, or does not exist
401token_expiredThe JWT has expired. Obtain a new token via refresh
401invalid_tokenThe JWT signature verification failed
403insufficient_scopeThe API key does not have permission for this endpoint
429rate_limit_exceededToo many authentication attempts. Retry after the period specified in Retry-After

Security Considerations

  1. Store API keys in a secrets manager (HashiCorp Vault, AWS Secrets Manager, or equivalent). Never embed keys in source code or client-side applications.
  2. Rotate API keys on a regular schedule. Veris supports multiple active keys per organization to enable zero-downtime rotation.
  3. Use the narrowest possible scope when generating API keys. Assign read-only keys to monitoring systems and full-access keys only to systems that require write operations.
  4. Monitor the API key usage dashboard for anomalous request patterns. Veris flags unusual geographic origins and request volume spikes automatically.

Enterprise Authentication (mTLS)

Mutual TLS authentication provides cryptographic verification of both client and server identity at the transport layer. Veris requires mTLS for Tier 2 (Enterprise) and Tier 1 (Sovereign) integrations. All financial-grade API operations at these tiers must authenticate over a mutually authenticated TLS channel.

Overview

In standard TLS, only the server presents a certificate. With mTLS, the client also presents a certificate that the server validates against a trusted certificate authority. This establishes bidirectional trust before any application-layer data is exchanged.

Client                                    Veris API
  |                                           |
  |--- ClientHello ------------------------->|
  |<-- ServerHello + Server Certificate ------|
  |<-- CertificateRequest -------------------|
  |--- Client Certificate ------------------->|
  |--- CertificateVerify -------------------->|
  |<-- Finished ------------------------------|
  |--- Application Data (encrypted) -------->|

Certificate Requirements

Client certificates must meet the following specifications:

PropertyRequirement
FormatX.509 v3
Key algorithmRSA 2048-bit minimum, or ECDSA P-256
Signature algorithmSHA-256 or stronger
Validity periodMaximum 1 year
Key usageDigital Signature, Key Encipherment
Extended key usageTLS Client Authentication (OID 1.3.6.1.5.5.7.3.2)
Subject DNMust include O (Organization) matching your Veris account name
SANAt least one DNS name or URI identifying the client system

Veris does not accept self-signed certificates. Certificates must be issued by a publicly trusted CA or by your organization's private CA after you upload the CA certificate to Veris.

Configuration Steps

Step 1: Generate a Private Key and CSR

Generate a private key and certificate signing request. The CSR must include your organization name as registered in your Veris account.

RSA 2048-bit:

# Generate private key
openssl genrsa -out veris-client.key 2048

# Generate CSR
openssl req -new -key veris-client.key -out veris-client.csr \
  -subj "/C=DE/ST=Berlin/L=Berlin/O=Your Organization/CN=veris-client.your-org.com"

ECDSA P-256:

# Generate private key
openssl ecparam -genkey -name prime256v1 -out veris-client.key

# Generate CSR
openssl req -new -key veris-client.key -out veris-client.csr \
  -subj "/C=DE/ST=Berlin/L=Berlin/O=Your Organization/CN=veris-client.your-org.com"

Step 2: Obtain a Signed Certificate

Submit the CSR to your certificate authority. The resulting certificate must satisfy the requirements listed above. If you use a private CA, proceed to Step 3 before uploading the client certificate.

Step 3: Upload Certificates to Veris

Upload your client certificate (and optionally your CA certificate) through the Veris API:

# Upload CA certificate (required for private CAs)
curl -X POST https://api.useveris.finance/api/v1/security/ca-certificates \
  -H "X-API-Key: vrs_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "your-org-internal-ca",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
  }'

# Upload client certificate
curl -X POST https://api.useveris.finance/api/v1/security/client-certificates \
  -H "X-API-Key: vrs_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-compliance-service",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "environment": "production"
  }'

Step 4: Bind Certificate to API Endpoints

After uploading, bind the certificate to the mTLS-protected endpoint:

curl -X POST https://api.useveris.finance/api/v1/security/mtls-bindings \
  -H "X-API-Key: vrs_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "certificate_id": "cert_a1b2c3d4",
    "endpoint_pattern": "/api/v1/*",
    "enforce": true
  }'

Once enforce is set to true, all requests to matching endpoints that do not present a valid client certificate will be rejected with HTTP 403.

Making Authenticated Requests

curl

curl -s https://mtls.api.useveris.finance/api/v1/alerts \
  --cert /path/to/veris-client.crt \
  --key /path/to/veris-client.key \
  --cacert /path/to/veris-ca-bundle.crt \
  -H "X-API-Key: vrs_live_abc123..."

Python (httpx)

import httpx

client = httpx.Client(
    base_url="https://mtls.api.useveris.finance/api/v1",
    cert=("/path/to/veris-client.crt", "/path/to/veris-client.key"),
    verify="/path/to/veris-ca-bundle.crt",
    headers={"X-API-Key": "vrs_live_abc123..."},
)

response = client.get("/alerts")
alerts = response.json()

Node.js

const https = require("node:https");
const fs = require("node:fs");

const agent = new https.Agent({
  cert: fs.readFileSync("/path/to/veris-client.crt"),
  key: fs.readFileSync("/path/to/veris-client.key"),
  ca: fs.readFileSync("/path/to/veris-ca-bundle.crt"),
});

const response = await fetch(
  "https://mtls.api.useveris.finance/api/v1/alerts",
  {
    agent,
    headers: {
      "X-API-Key": "vrs_live_abc123...",
    },
  }
);

const alerts = await response.json();

When to Use mTLS

Integration TiermTLS Requirement
Tier 1 (Sovereign)Mandatory
Tier 2 (Enterprise)Mandatory
Tier 3 (Emerging)Optional (recommended for production)

mTLS is required when:

Certificate Rotation

Certificates must be rotated before expiry. Veris supports overlapping certificate validity to enable zero-downtime rotation.

Rotation Procedure

  1. Generate a new private key and CSR. Do not reuse the previous private key.
  2. Obtain a signed certificate from your CA.
  3. Upload the new certificate to Veris.
  4. Bind the new certificate to your endpoints. Both old and new certificates remain valid during the overlap window.
  5. Update your client systems to use the new certificate and key.
  6. Verify connectivity with the new certificate.
  7. Revoke the old certificate through the Veris API.
# Upload replacement certificate
curl -X POST https://api.useveris.finance/api/v1/security/client-certificates \
  -H "X-API-Key: vrs_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-compliance-service-v2",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    "environment": "production",
    "replaces": "cert_a1b2c3d4"
  }'

# After confirming the new certificate works, revoke the old one
curl -X DELETE https://api.useveris.finance/api/v1/security/client-certificates/cert_a1b2c3d4 \
  -H "X-API-Key: vrs_live_abc123..."

Rotation Schedule

Certificate TypeRecommended Rotation
Client certificatesEvery 90 days
CA certificatesEvery 2 years
Private keysEvery rotation (never reuse)

Veris sends webhook notifications 30 days, 14 days, and 3 days before a client certificate expires. Configure your alerting systems to act on the security.certificate_expiring event.

Error Responses

HTTP StatusError CodeDescription
403mtls_requiredThe endpoint requires mTLS but no client certificate was presented
403certificate_rejectedThe client certificate is not trusted, expired, or revoked
403certificate_mismatchThe certificate subject does not match the registered organization
403certificate_expiredThe client certificate has passed its validity period

Security Considerations

  1. Store private keys in hardware security modules (HSMs) or equivalent tamper-resistant storage. Never store private keys on disk in production systems without filesystem encryption.
  2. Use separate certificates for each environment (sandbox, staging, production). Cross-environment certificate use is rejected by default.
  3. Implement automated certificate rotation with monitoring. Manual rotation processes are a reliability risk at scale.
  4. Combine mTLS with IP whitelisting for defense-in-depth. Transport-layer authentication and network-layer restrictions together satisfy the strictest regulatory requirements.

Payload Signatures

Payload signatures provide request integrity verification independent of transport-layer security. Every mutating API request (POST, PUT, PATCH, DELETE) must include an HMAC-SHA256 signature computed over the request timestamp and body. This prevents tampering, ensures non-repudiation, and blocks replay attacks.

Signature Scheme

Veris uses HMAC-SHA256 with a shared signing secret. The signing secret is issued alongside your API key and is distinct from the API key itself. The signing secret never leaves your infrastructure and is never transmitted in API requests.

ComponentValue
AlgorithmHMAC-SHA256
Signature headerX-Veris-Signature
Timestamp headerX-Veris-Timestamp
Nonce headerX-Veris-Nonce
Timestamp tolerance30 seconds
Signing secret formatvrs_sig_<64 hex characters>

Signing Algorithm

The signature is computed as follows:

signature = HMAC-SHA256(signing_secret, timestamp + "." + request_body)

Where:

The resulting signature is hex-encoded and placed in the X-Veris-Signature header.

Required Headers

Every signed request must include all three headers:

HeaderFormatDescription
X-Veris-Signature64-character hex stringHMAC-SHA256 signature
X-Veris-TimestampUnix epoch (seconds)Request creation time
X-Veris-NonceUUID v4 or 32+ character unique stringOne-time request identifier

Timestamp Validation

Veris rejects requests where the timestamp differs from server time by more than 30 seconds. This window accounts for reasonable clock drift while preventing replay attacks with captured requests.

Ensure your client systems synchronize time via NTP. A clock skew beyond 30 seconds will cause all signed requests to fail.

Replay Attack Prevention

The X-Veris-Nonce header provides replay protection. Veris stores each nonce for 30 seconds. Any request that reuses a previously seen nonce within this window is rejected with HTTP 409.

Nonce values must be unique per request. UUID v4 is the recommended format. Sequential or predictable nonce values are accepted but provide weaker security guarantees.

Code Samples

Python

import hashlib
import hmac
import time
import uuid

import httpx

API_KEY = "vrs_live_abc123..."
SIGNING_SECRET = "vrs_sig_a1b2c3d4e5f6..."
BASE_URL = "https://api.useveris.finance/api/v1"


def sign_request(body: str) -> dict:
    """Generate signature headers for a request body."""
    timestamp = str(int(time.time()))
    nonce = str(uuid.uuid4())

    message = f"{timestamp}.{body}"
    signature = hmac.new(
        SIGNING_SECRET.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return {
        "X-Veris-Signature": signature,
        "X-Veris-Timestamp": timestamp,
        "X-Veris-Nonce": nonce,
    }


# Example: Create a screening request
body = '{"address": "0x1234...", "chain": "ethereum"}'
signature_headers = sign_request(body)

response = httpx.post(
    f"{BASE_URL}/screening",
    headers={
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
        **signature_headers,
    },
    content=body,
)

Node.js

const crypto = require("node:crypto");

const API_KEY = "vrs_live_abc123...";
const SIGNING_SECRET = "vrs_sig_a1b2c3d4e5f6...";
const BASE_URL = "https://api.useveris.finance/api/v1";

function signRequest(body) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomUUID();

  const message = `${timestamp}.${body}`;
  const signature = crypto
    .createHmac("sha256", SIGNING_SECRET)
    .update(message)
    .digest("hex");

  return {
    "X-Veris-Signature": signature,
    "X-Veris-Timestamp": timestamp,
    "X-Veris-Nonce": nonce,
  };
}

// Example: Create a screening request
const body = JSON.stringify({ address: "0x1234...", chain: "ethereum" });
const signatureHeaders = signRequest(body);

const response = await fetch(`${BASE_URL}/screening`, {
  method: "POST",
  headers: {
    "X-API-Key": API_KEY,
    "Content-Type": "application/json",
    ...signatureHeaders,
  },
  body,
});

Verification (Server-Side Reference)

The following demonstrates how Veris verifies incoming signatures. Use this logic if you implement signature verification for Veris webhooks delivered to your systems.

Python

import hashlib
import hmac
import time


def verify_signature(
    signing_secret: str,
    signature: str,
    timestamp: str,
    body: str,
    tolerance_seconds: int = 30,
) -> bool:
    """Verify an HMAC-SHA256 payload signature.

    Returns True if the signature is valid and the timestamp
    is within the tolerance window.
    """
    # Check timestamp freshness
    request_time = int(timestamp)
    current_time = int(time.time())
    if abs(current_time - request_time) > tolerance_seconds:
        return False

    # Compute expected signature
    message = f"{timestamp}.{body}"
    expected = hmac.new(
        signing_secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    # Constant-time comparison to prevent timing attacks
    return hmac.compare_digest(expected, signature)

Node.js

const crypto = require("node:crypto");

function verifySignature(signingSecret, signature, timestamp, body, toleranceSeconds = 30) {
  // Check timestamp freshness
  const requestTime = parseInt(timestamp, 10);
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - requestTime) > toleranceSeconds) {
    return false;
  }

  // Compute expected signature
  const message = `${timestamp}.${body}`;
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(message)
    .digest("hex");

  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

Error Responses

HTTP StatusError CodeDescription
401signature_missingThe X-Veris-Signature header is absent
401signature_invalidThe computed signature does not match the provided signature
401timestamp_missingThe X-Veris-Timestamp header is absent
401timestamp_expiredThe timestamp is outside the 30-second tolerance window
409nonce_reusedThe X-Veris-Nonce value was already used within the replay window

Security Considerations

  1. Store signing secrets with the same rigor as private keys. Use a secrets manager. Never log signing secrets or include them in error messages.
  2. Always use constant-time comparison functions (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) when verifying signatures. Standard string equality allows timing side-channel attacks.
  3. Compute signatures over the raw request body, not a re-serialized version. JSON serialization is not deterministic across libraries. Changing key order or whitespace will invalidate the signature.
  4. Combine payload signatures with mTLS for Tier 1 and Tier 2 integrations. Payload signatures protect against application-layer tampering. mTLS protects against transport-layer attacks. Neither substitutes for the other.

IP Whitelisting

IP whitelisting restricts API access to requests originating from pre-approved network addresses. Veris evaluates the source IP of every inbound request against your organization's whitelist before processing any authentication or application logic. Requests from non-whitelisted addresses are rejected at the network edge.

Configuration

Adding IP Addresses

Register allowed IP addresses or CIDR ranges through the Veris API. Each entry requires a descriptive label for audit trail purposes.

POST /api/v1/security/ip-whitelist

Request body:

{
  "entries": [
    {
      "cidr": "203.0.113.0/24",
      "label": "Production datacenter - Frankfurt",
      "environment": "production"
    },
    {
      "cidr": "198.51.100.42/32",
      "label": "CI/CD runner - GitHub Actions",
      "environment": "production"
    },
    {
      "cidr": "10.0.0.0/8",
      "label": "Internal VPN",
      "environment": "sandbox"
    }
  ]
}

Response:

{
  "entries": [
    {
      "id": "ipw_a1b2c3d4",
      "cidr": "203.0.113.0/24",
      "label": "Production datacenter - Frankfurt",
      "environment": "production",
      "created_at": "2026-03-24T10:00:00Z",
      "status": "active"
    }
  ],
  "total_active": 3
}

CIDR Notation

Veris accepts both individual addresses and CIDR ranges:

FormatExampleDescription
Single IPv4198.51.100.42/32One specific address
IPv4 subnet203.0.113.0/24256 addresses (203.0.113.0 through 203.0.113.255)
Single IPv62001:db8::1/128One specific address
IPv6 subnet2001:db8::/48IPv6 prefix block

The minimum prefix length is /16 for IPv4 and /48 for IPv6. Broader ranges are rejected to prevent accidental over-permissioning.

Listing Whitelisted Entries

GET /api/v1/security/ip-whitelist

Response:

{
  "entries": [
    {
      "id": "ipw_a1b2c3d4",
      "cidr": "203.0.113.0/24",
      "label": "Production datacenter - Frankfurt",
      "environment": "production",
      "created_at": "2026-03-24T10:00:00Z",
      "status": "active",
      "last_seen": "2026-03-24T14:32:17Z"
    }
  ],
  "total_active": 3
}

The last_seen field records the most recent request from an address within the CIDR range. Use this to identify stale entries during periodic reviews.

Removing Entries

DELETE /api/v1/security/ip-whitelist/{entry_id}
curl -X DELETE https://api.useveris.finance/api/v1/security/ip-whitelist/ipw_a1b2c3d4 \
  -H "X-API-Key: vrs_live_abc123..."

Removal takes effect within 60 seconds. During propagation, requests from the removed range may still be accepted. Plan removal operations accordingly.

Enforcement Behavior

When IP whitelisting is active for your organization:

ConditionHTTP StatusError CodeBehavior
Source IP matches a whitelist entryN/AN/ARequest proceeds to authentication
Source IP does not match any entry403ip_not_whitelistedRequest rejected before authentication
Whitelist is empty (no entries)N/AN/AAll IPs accepted (whitelisting disabled)

The rejection response includes the source IP observed by Veris, which is useful for diagnosing NAT or proxy misconfigurations:

{
  "error": {
    "code": "ip_not_whitelisted",
    "message": "Request origin 192.0.2.50 is not in the allowed IP list.",
    "source_ip": "192.0.2.50"
  }
}

Environment Separation

Whitelist entries are scoped to environments. A production whitelist entry does not grant access to the sandbox API, and vice versa. Configure entries for each environment independently.

EnvironmentAPI Base URLWhitelist Scope
Productionapi.useveris.financeproduction entries only
Sandboxsandbox.api.useveris.financesandbox entries only

Combining with mTLS

For Tier 1 (Sovereign) and Tier 2 (Enterprise) integrations, combine IP whitelisting with mTLS authentication to establish defense-in-depth:

  1. Network layer: IP whitelisting restricts which networks can reach the API.
  2. Transport layer: mTLS verifies the cryptographic identity of the client.
  3. Application layer: API key and payload signatures authenticate the request and verify integrity.

This layered approach satisfies regulatory expectations under MiCAR Article 45 (operational resilience) and aligns with NIST 800-53 SC-7 (boundary protection).

Request Flow:

[Client] --> [IP Whitelist Check] --> [mTLS Handshake] --> [API Key Auth] --> [Signature Verify] --> [Application]
              Network Edge              TLS Termination     App Layer          App Layer             Business Logic

Operational Notes

Proxy and Load Balancer Considerations

If your requests pass through a proxy, CDN, or load balancer, the source IP seen by Veris is the address of the last hop, not your origin server. Whitelist the egress IP of your proxy infrastructure. Veris respects X-Forwarded-For headers only from trusted proxy networks configured during onboarding.

Dynamic IP Addresses

Cloud environments with dynamic IP allocation (autoscaling groups, serverless functions) require a stable egress point. Route API traffic through a NAT gateway or static IP proxy to maintain a predictable source address.

Audit and Compliance

All whitelist modifications are recorded in the Veris audit log with the following details:

Retrieve audit records via the /api/v1/audit/ip-whitelist endpoint for SOC 2 and regulatory evidence collection.

Error Responses

HTTP StatusError CodeDescription
403ip_not_whitelistedThe request source IP is not in the allowed list
400invalid_cidrThe CIDR notation is malformed
400cidr_too_broadThe prefix length is below the minimum (/16 IPv4, /48 IPv6)
404whitelist_entry_not_foundThe specified entry ID does not exist
409duplicate_entryA whitelist entry with the same CIDR already exists

Event Catalog

Veris delivers structured webhook events to your registered endpoints when compliance-relevant state changes occur. Each event carries a typed payload with full context, enabling downstream automation without polling.

This catalog documents every event type, its trigger condition, and payload structure.

Event Format

All webhook events share a common envelope:

{
  "id": "evt_a1b2c3d4e5f6",
  "type": "alert.resolved",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T14:32:17.000Z",
  "organization_id": "org_x1y2z3",
  "data": { }
}
FieldTypeDescription
idstringUnique event identifier. Use for idempotency.
typestringEvent type from this catalog
api_versionstringAPI version that generated the event
created_atstring (ISO 8601)Timestamp of event creation
organization_idstringYour organization identifier
dataobjectEvent-specific payload (documented below)

Resolution Events

Events triggered by alert lifecycle transitions and compliance workflow completions.

alert.resolved

Fired when an alert reaches a terminal state (closed, dismissed, or escalated to a case).

Trigger: Alert status changes to resolved, dismissed, or escalated.

{
  "id": "evt_r001",
  "type": "alert.resolved",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T14:32:17.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "alert_id": "alt_5f8a2b",
    "resolution": "escalated",
    "case_id": "cas_9d4e1c",
    "risk_score": 87,
    "address": "0x1a2b3c...",
    "chain": "ethereum",
    "resolved_by": "ai_triage",
    "resolved_at": "2026-03-24T14:32:15.000Z"
  }
}

investigation.complete

Fired when an AI investigation finishes processing a case.

Trigger: Investigation agent completes analysis and writes results to the case record.

{
  "id": "evt_r002",
  "type": "investigation.complete",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T14:33:42.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "case_id": "cas_9d4e1c",
    "alert_id": "alt_5f8a2b",
    "recommendation": "file_sar",
    "confidence": 0.94,
    "risk_indicators": ["layering", "rapid_consolidation", "mixer_interaction"],
    "investigation_duration_seconds": 18,
    "completed_at": "2026-03-24T14:33:40.000Z"
  }
}

sar.ready

Fired when a SAR narrative has been generated and is awaiting human review.

Trigger: SAR agent completes narrative generation for a case.

{
  "id": "evt_r003",
  "type": "sar.ready",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T15:01:22.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "case_id": "cas_9d4e1c",
    "sar_id": "sar_7b3f2a",
    "filing_type": "initial",
    "subject_count": 2,
    "transaction_count": 47,
    "total_amount_usd": 284500.00,
    "generated_at": "2026-03-24T15:01:20.000Z"
  }
}

sar.filed

Fired when a SAR has been filed with the regulatory authority (after human approval).

Trigger: SAR status transitions to filed following compliance officer approval and submission.

{
  "id": "evt_r004",
  "type": "sar.filed",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T16:45:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "case_id": "cas_9d4e1c",
    "sar_id": "sar_7b3f2a",
    "filing_type": "initial",
    "bsa_id": "31000012345678",
    "filed_at": "2026-03-24T16:44:58.000Z",
    "filing_deadline": "2026-04-23T00:00:00.000Z"
  }
}

Risk Events

Events triggered by changes in address or entity risk assessments. These events support continuous KYT (Know Your Transaction) monitoring.

risk.score_changed

Continuous monitoring. When an address risk score changes after initial screening, Veris delivers a risk.score_changed event to your webhook endpoint. This enables real-time response to evolving risk without re-screening.

Trigger: An address risk score changes by more than 5 points, or crosses a tier boundary (low/medium/high/critical).

{
  "id": "evt_k001",
  "type": "risk.score_changed",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T14:50:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "address": "0x1a2b3c...",
    "chain": "ethereum",
    "previous_score": 32,
    "current_score": 78,
    "previous_tier": "low",
    "current_tier": "high",
    "contributing_factors": [
      {
        "factor": "mixer_interaction",
        "weight": 0.35,
        "detail": "Received 12.5 ETH from Tornado Cash pool in 3 transactions"
      },
      {
        "factor": "rapid_movement",
        "weight": 0.20,
        "detail": "Funds moved through 4 intermediaries within 45 minutes"
      }
    ],
    "monitored_since": "2026-01-15T09:00:00.000Z",
    "evaluated_at": "2026-03-24T14:49:58.000Z"
  }
}

sanctions.match_detected

Fired when a monitored address is linked to a newly listed sanctions target. This event requires immediate action under OFAC, EU, and UN sanctions frameworks.

Trigger: An address in your monitoring set matches a new or updated entry on OFAC SDN, EU Consolidated List, or UN Security Council sanctions lists.

{
  "id": "evt_k002",
  "type": "sanctions.match_detected",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T08:15:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "address": "0x4d5e6f...",
    "chain": "ethereum",
    "match_type": "exact",
    "match_confidence": 1.0,
    "sanctions_list": "OFAC_SDN",
    "list_entry_id": "SDN-36892",
    "listed_entity": "Example Sanctioned Entity LLC",
    "listing_date": "2026-03-24",
    "source_url": "https://sanctionssearch.ofac.treas.gov/Details.aspx?id=36892",
    "recommended_action": "freeze_and_report",
    "detected_at": "2026-03-24T08:14:55.000Z"
  }
}

Regulatory Events

Events related to regulatory framework changes and sanctions list updates that may require policy adjustments.

policy.update_required

Fired when Veris detects a regulatory change that affects your configured compliance policies.

Trigger: A regulatory framework update (MiCAR, BSA/AML, Travel Rule) requires changes to your screening thresholds, reporting timelines, or risk parameters.

{
  "id": "evt_g001",
  "type": "policy.update_required",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T06:00:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "regulation": "MiCAR",
    "article": "Article 76(3)",
    "jurisdiction": "EU",
    "effective_date": "2026-06-30",
    "summary": "Travel Rule threshold reduced from EUR 1000 to EUR 0 for crypto-asset transfers.",
    "affected_policies": ["travel_rule_threshold", "screening_minimum_amount"],
    "recommended_changes": [
      {
        "policy": "travel_rule_threshold",
        "current_value": "1000.00",
        "recommended_value": "0.00",
        "currency": "EUR"
      }
    ],
    "documentation_url": "https://docs.useveris.finance/regulatory/micar-2026-06"
  }
}

sanctions.list_updated

Fired when Veris ingests an updated sanctions list. This event is informational. If any of your monitored addresses match new entries, separate sanctions.match_detected events are delivered.

Trigger: Veris completes ingestion of an updated sanctions list from any supported source.

{
  "id": "evt_g002",
  "type": "sanctions.list_updated",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T04:30:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "list_name": "OFAC_SDN",
    "list_version": "2026-03-24",
    "entries_added": 12,
    "entries_removed": 3,
    "entries_modified": 7,
    "total_entries": 14892,
    "crypto_addresses_added": 4,
    "ingested_at": "2026-03-24T04:29:45.000Z"
  }
}

System Events

Events related to platform operations, testing, and generated reports.

webhook.test

A test event for verifying webhook endpoint connectivity and signature validation. Carries no compliance data.

Trigger: Manual invocation via the API or the Veris dashboard.

{
  "id": "evt_s001",
  "type": "webhook.test",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T10:00:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "message": "Webhook endpoint verified successfully."
  }
}

report.generated

Fired when a scheduled or on-demand compliance report has been generated and is available for download.

Trigger: Report generation completes (transaction monitoring summary, risk assessment report, regulatory filing summary).

{
  "id": "evt_s002",
  "type": "report.generated",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T02:00:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "report_id": "rpt_8c2d4f",
    "report_type": "transaction_monitoring_summary",
    "period_start": "2026-03-01T00:00:00.000Z",
    "period_end": "2026-03-31T23:59:59.000Z",
    "format": "pdf",
    "size_bytes": 2847561,
    "download_url": "https://api.useveris.finance/api/v1/reports/rpt_8c2d4f/download",
    "download_expires_at": "2026-03-31T02:00:00.000Z",
    "generated_at": "2026-03-24T01:59:48.000Z"
  }
}

reserve.attestation

Fired when a stablecoin reserve attestation is published or when reserve composition changes materially.

Trigger: New reserve attestation data is recorded, or reserve ratio deviates by more than 0.5% from the previous attestation.

{
  "id": "evt_s003",
  "type": "reserve.attestation",
  "api_version": "2026-03-01",
  "created_at": "2026-03-24T12:00:00.000Z",
  "organization_id": "org_x1y2z3",
  "data": {
    "issuer": "USDC",
    "attestation_date": "2026-03-24",
    "total_supply": 52400000000.00,
    "total_reserves": 52480000000.00,
    "reserve_ratio": 1.0015,
    "reserve_composition": {
      "us_treasuries": 0.78,
      "cash_deposits": 0.15,
      "reverse_repo": 0.07
    },
    "auditor": "Deloitte",
    "attestation_url": "https://transparency.centre.io/2026-03-24",
    "recorded_at": "2026-03-24T11:59:50.000Z"
  }
}

Event Summary Table

EventCategoryTrigger
alert.resolvedResolutionAlert reaches terminal state
investigation.completeResolutionAI investigation finishes
sar.readyResolutionSAR narrative generated
sar.filedResolutionSAR submitted to regulator
risk.score_changedRisk (KYT)Address risk score changes materially
sanctions.match_detectedRisk (KYT)Monitored address matches sanctions entry
policy.update_requiredRegulatoryRegulation change affects your policies
sanctions.list_updatedRegulatorySanctions list ingestion completed
webhook.testSystemManual connectivity test
report.generatedSystemCompliance report ready for download
reserve.attestationSystemReserve attestation published

Subscribing to Events

Configure which events your webhook endpoint receives through the Webhook Configuration API. You can subscribe to individual event types, entire categories, or all events using the wildcard *.

Webhook Configuration

Veris delivers real-time event notifications to HTTPS endpoints you control. This document covers endpoint registration, signature verification, retry behavior, and failure handling.

Registering a Webhook Endpoint

Register an endpoint to receive events from the Event Catalog.

POST /api/v1/webhooks/endpoints

Request body:

{
  "url": "https://compliance.your-org.com/webhooks/veris",
  "description": "Production compliance event handler",
  "events": [
    "alert.resolved",
    "investigation.complete",
    "sar.ready",
    "sar.filed",
    "risk.score_changed",
    "sanctions.match_detected"
  ],
  "environment": "production",
  "enabled": true
}

Response:

{
  "id": "wh_a1b2c3d4",
  "url": "https://compliance.your-org.com/webhooks/veris",
  "description": "Production compliance event handler",
  "events": [
    "alert.resolved",
    "investigation.complete",
    "sar.ready",
    "sar.filed",
    "risk.score_changed",
    "sanctions.match_detected"
  ],
  "environment": "production",
  "enabled": true,
  "signing_secret": "whsec_k9m2n4p6q8r0s2t4u6v8w0x2y4z6a8b0",
  "created_at": "2026-03-24T10:00:00.000Z"
}

The signing_secret is returned only once, at creation time. Store it securely. If lost, rotate the secret through the endpoint update API.

Event Subscriptions

Subscribe to specific event types, categories, or all events:

PatternDescription
alert.resolvedSingle event type
risk.*All events in the Risk category
*All events

Managing Endpoints

List endpoints:

GET /api/v1/webhooks/endpoints

Update an endpoint:

PATCH /api/v1/webhooks/endpoints/{endpoint_id}
{
  "events": ["alert.resolved", "risk.*"],
  "enabled": false
}

Delete an endpoint:

DELETE /api/v1/webhooks/endpoints/{endpoint_id}

Rotate signing secret:

POST /api/v1/webhooks/endpoints/{endpoint_id}/rotate-secret

This invalidates the previous secret immediately. Update your verification logic before calling this endpoint.

Delivery Headers

Every webhook delivery includes the following headers:

HeaderDescription
X-Veris-Webhook-IDUnique delivery identifier (for idempotency and support reference)
X-Veris-Webhook-TimestampUnix epoch (seconds) when the delivery was initiated
X-Veris-Webhook-SignatureHMAC-SHA256 signature of the delivery payload
Content-Typeapplication/json
User-AgentVeris-Webhooks/1.0

Signature Verification

Veris signs every webhook delivery using HMAC-SHA256. Verify the signature before processing any event data to confirm the delivery originates from Veris and has not been modified in transit.

Signature Algorithm

The signature is computed as:

signature = HMAC-SHA256(signing_secret, webhook_id + "." + timestamp + "." + body)

Where:

Verification: Python

import hashlib
import hmac
import time


def verify_webhook(
    signing_secret: str,
    webhook_id: str,
    timestamp: str,
    body: str,
    signature: str,
    tolerance_seconds: int = 300,
) -> bool:
    """Verify a Veris webhook signature.

    Args:
        signing_secret: The endpoint signing secret (whsec_...).
        webhook_id: Value of X-Veris-Webhook-ID header.
        timestamp: Value of X-Veris-Webhook-Timestamp header.
        body: Raw request body as a string.
        signature: Value of X-Veris-Webhook-Signature header.
        tolerance_seconds: Maximum allowed age of the delivery.

    Returns:
        True if the signature is valid and the timestamp is fresh.
    """
    # Reject stale deliveries
    delivery_time = int(timestamp)
    current_time = int(time.time())
    if abs(current_time - delivery_time) > tolerance_seconds:
        return False

    # Compute expected signature
    message = f"{webhook_id}.{timestamp}.{body}"
    expected = hmac.new(
        signing_secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected, signature)


# Flask example
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_k9m2n4p6q8r0s2t4u6v8w0x2y4z6a8b0"


@app.route("/webhooks/veris", methods=["POST"])
def handle_webhook():
    is_valid = verify_webhook(
        signing_secret=WEBHOOK_SECRET,
        webhook_id=request.headers.get("X-Veris-Webhook-ID", ""),
        timestamp=request.headers.get("X-Veris-Webhook-Timestamp", ""),
        body=request.get_data(as_text=True),
        signature=request.headers.get("X-Veris-Webhook-Signature", ""),
    )

    if not is_valid:
        abort(401)

    event = request.get_json()

    if event["type"] == "sanctions.match_detected":
        # Immediate action required
        handle_sanctions_match(event["data"])
    elif event["type"] == "risk.score_changed":
        handle_risk_change(event["data"])

    return "", 200

Verification: Node.js

const crypto = require("node:crypto");

const WEBHOOK_SECRET = "whsec_k9m2n4p6q8r0s2t4u6v8w0x2y4z6a8b0";

function verifyWebhook(signingSecret, webhookId, timestamp, body, signature, toleranceSeconds = 300) {
  // Reject stale deliveries
  const deliveryTime = parseInt(timestamp, 10);
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - deliveryTime) > toleranceSeconds) {
    return false;
  }

  // Compute expected signature
  const message = `${webhookId}.${timestamp}.${body}`;
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(message)
    .digest("hex");

  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex")
  );
}

// Express example
const express = require("express");
const app = express();

app.post(
  "/webhooks/veris",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const isValid = verifyWebhook(
      WEBHOOK_SECRET,
      req.headers["x-veris-webhook-id"],
      req.headers["x-veris-webhook-timestamp"],
      req.body.toString("utf-8"),
      req.headers["x-veris-webhook-signature"]
    );

    if (!isValid) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(req.body);

    switch (event.type) {
      case "sanctions.match_detected":
        handleSanctionsMatch(event.data);
        break;
      case "risk.score_changed":
        handleRiskChange(event.data);
        break;
    }

    res.status(200).send();
  }
);

Retry Policy

When your endpoint returns a non-2xx HTTP status or fails to respond within 30 seconds, Veris retries the delivery with exponential backoff.

AttemptDelay After Previous AttemptCumulative Time
1Immediate0
21 minute1 minute
310 minutes11 minutes
41 hour1 hour 11 minutes
56 hours7 hours 11 minutes

After 5 failed attempts (spanning approximately 7 hours), the delivery is marked as failed. The total retry window does not exceed 24 hours. Failed deliveries are retained for 30 days and can be replayed manually through the API.

Idempotency

Your endpoint may receive the same event more than once due to retries or network conditions. Use the X-Veris-Webhook-ID header as an idempotency key. Store processed delivery IDs and skip duplicates.

Expected Response

Veris considers a delivery successful when your endpoint returns any 2xx HTTP status code within 30 seconds. The response body is ignored. Return 200 OK with an empty body for the fastest acknowledgment.

Testing Webhooks

Send a test event to verify your endpoint configuration and signature validation.

POST /api/v1/webhooks/endpoints/{endpoint_id}/test

Response:

{
  "delivery_id": "dlv_t1u2v3w4",
  "event_type": "webhook.test",
  "status": "delivered",
  "response_status": 200,
  "response_time_ms": 142,
  "delivered_at": "2026-03-24T10:05:00.000Z"
}

If the test delivery fails, the response includes diagnostic information:

{
  "delivery_id": "dlv_t1u2v3w4",
  "event_type": "webhook.test",
  "status": "failed",
  "error": "Connection refused",
  "response_status": null,
  "response_time_ms": null,
  "delivered_at": null
}

Failure Handling and Alerting

Automatic Disabling

If an endpoint fails to accept deliveries for 7 consecutive days, Veris automatically disables the endpoint and sends a notification to the organization's administrative contacts. Re-enable the endpoint after resolving the underlying issue:

PATCH /api/v1/webhooks/endpoints/{endpoint_id}
{
  "enabled": true
}

Veris replays all missed events from the retention window (30 days) upon re-enablement if you request a backfill.

Delivery Logs

Inspect recent deliveries for an endpoint:

GET /api/v1/webhooks/endpoints/{endpoint_id}/deliveries

Response:

{
  "deliveries": [
    {
      "id": "dlv_a1b2c3d4",
      "event_type": "risk.score_changed",
      "event_id": "evt_k001",
      "status": "delivered",
      "attempts": 1,
      "response_status": 200,
      "response_time_ms": 87,
      "first_attempted_at": "2026-03-24T14:50:01.000Z",
      "delivered_at": "2026-03-24T14:50:01.000Z"
    },
    {
      "id": "dlv_e5f6g7h8",
      "event_type": "alert.resolved",
      "event_id": "evt_r001",
      "status": "failed",
      "attempts": 5,
      "response_status": 503,
      "response_time_ms": 30000,
      "first_attempted_at": "2026-03-24T10:00:00.000Z",
      "last_attempted_at": "2026-03-24T17:11:00.000Z",
      "next_attempt_at": null,
      "error": "Service Unavailable"
    }
  ],
  "pagination": {
    "total": 156,
    "page": 1,
    "per_page": 20
  }
}

Manual Replay

Replay a specific failed delivery:

POST /api/v1/webhooks/deliveries/{delivery_id}/replay

Replay a time range of events:

POST /api/v1/webhooks/endpoints/{endpoint_id}/replay
{
  "from": "2026-03-24T00:00:00.000Z",
  "to": "2026-03-24T23:59:59.000Z",
  "event_types": ["risk.score_changed", "sanctions.match_detected"]
}

Endpoint Requirements

Your webhook endpoint must satisfy the following:

RequirementDetail
ProtocolHTTPS only. HTTP endpoints are rejected.
TLS versionTLS 1.2 or 1.3
CertificateValid certificate from a publicly trusted CA
Response timeUnder 30 seconds
Response code2xx for successful receipt
Availability99.9% uptime recommended
IdempotencyHandle duplicate deliveries gracefully

Error Responses

HTTP StatusError CodeDescription
400invalid_urlThe webhook URL is not a valid HTTPS endpoint
400invalid_eventsOne or more event types are not recognized
404endpoint_not_foundThe specified endpoint ID does not exist
409duplicate_urlAn endpoint with this URL already exists for this environment
422endpoint_disabledThe endpoint is disabled and must be re-enabled before testing

Idempotency

Veris supports idempotent requests to prevent duplicate processing of compliance-critical operations. Any mutating endpoint can accept an Idempotency-Key header. Endpoints that create regulatory filings or resolve entities require it.

How It Works

  1. The client generates a UUID v4 and sends it in the Idempotency-Key header.
  2. Veris stores the request fingerprint (method, path, body hash) alongside the key.
  3. If a request arrives with a previously seen key, Veris returns the cached response without re-executing the operation.
  4. Keys are retained for 24 hours, then automatically purged.

Required Endpoints

The following endpoints reject requests that omit the Idempotency-Key header with a 400 Bad Request response.

EndpointReason
POST /v1/resolveEntity resolution must not produce duplicate records for the same input.
POST /v1/regulatory/reportSAR and STR filings must never be submitted twice for the same case.
POST /v1/casesCase creation from the same alert set must be deduplicated.
POST /v1/screening/batchBatch screening jobs must not be duplicated on retry.

All other mutating endpoints (POST, PUT, PATCH) accept the header optionally. GET, DELETE, and HEAD requests ignore the header.

Request Format

POST /v1/regulatory/report HTTP/1.1
Host: api.useveris.finance
Authorization: Bearer vrs_live_...
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

{
  "case_id": "case_abc123",
  "jurisdiction": "us",
  "filing_type": "initial"
}

Behavior on Duplicate Request

When a duplicate key is received, Veris checks three conditions:

ConditionBehavior
Same key, same request bodyReturn the cached response with X-Idempotent-Replayed: true header. HTTP status matches the original response.
Same key, different request bodyReturn 409 Conflict. The key has already been used with a different payload.
Same key, original request still processingReturn 409 Conflict with retry_after field. The original request has not yet completed.

Response Headers

HeaderValuePresent When
X-Idempotent-ReplayedtrueResponse is served from cache (duplicate key with matching body).
X-Idempotency-KeyThe submitted UUIDEvery response to a request that included the header.

Key Retention

ParameterValue
Retention period24 hours from first use
StorageRedis with TTL
Key formatUUID v4 (RFC 4122). Non-UUID values are rejected with 400 Bad Request.

Code Examples

Python

import uuid
import requests

idempotency_key = str(uuid.uuid4())

response = requests.post(
    "https://api.useveris.finance/v1/regulatory/report",
    headers={
        "Authorization": "Bearer vrs_live_...",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "case_id": "case_abc123",
        "jurisdiction": "us",
        "filing_type": "initial",
    },
)

# Safe to retry with the same key on network failure
if response.status_code in (502, 503, 504):
    response = requests.post(
        "https://api.useveris.finance/v1/regulatory/report",
        headers={
            "Authorization": "Bearer vrs_live_...",
            "Content-Type": "application/json",
            "Idempotency-Key": idempotency_key,
        },
        json={
            "case_id": "case_abc123",
            "jurisdiction": "us",
            "filing_type": "initial",
        },
    )

cURL

curl -X POST https://api.useveris.finance/v1/regulatory/report \
  -H "Authorization: Bearer vrs_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "case_id": "case_abc123",
    "jurisdiction": "us",
    "filing_type": "initial"
  }'

TypeScript

const idempotencyKey = crypto.randomUUID();

const response = await fetch(
  "https://api.useveris.finance/v1/regulatory/report",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer vrs_live_...",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify({
      case_id: "case_abc123",
      jurisdiction: "us",
      filing_type: "initial",
    }),
  }
);

Error Responses

Missing Required Key

{
  "error": {
    "code": "IDEMPOTENCY_KEY_REQUIRED",
    "message": "This endpoint requires an Idempotency-Key header. Provide a UUID v4 value.",
    "request_id": "req_xyz789"
  }
}

Key Conflict

{
  "error": {
    "code": "IDEMPOTENCY_KEY_CONFLICT",
    "message": "This idempotency key has already been used with a different request body.",
    "request_id": "req_xyz790"
  }
}

Invalid Key Format

{
  "error": {
    "code": "IDEMPOTENCY_KEY_INVALID",
    "message": "Idempotency-Key must be a valid UUID v4.",
    "request_id": "req_xyz791"
  }
}

Rate Limits

Veris enforces rate limits to ensure platform stability and fair resource allocation across all clients. Limits are applied per API key and vary by subscription tier.

Tier-Based Limits

TierRequests per MinuteRequests per HourBurst Allowance
Emerging1001,00020 requests in 1 second
Enterprise1,00010,000100 requests in 1 second
SovereignCustomCustomCustom

Burst allowance defines the maximum number of requests accepted within a 1-second window. Sustained request rates must remain within the per-minute limit. Burst capacity is designed for batch operations that send multiple requests in rapid succession, not for sustained throughput above the per-minute rate.

Sovereign tier limits are negotiated during onboarding and documented in the service agreement.

Rate Limit Headers

Every API response includes rate limit headers. These headers reflect the per-minute window.

HeaderTypeDescription
X-RateLimit-LimitintegerMaximum requests allowed in the current window.
X-RateLimit-RemainingintegerRequests remaining in the current window.
X-RateLimit-ResetintegerUnix timestamp (seconds) when the current window resets.

Example Response Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1711267200
Content-Type: application/json

429 Too Many Requests

When a client exceeds its rate limit, Veris returns a 429 Too Many Requests response. The response body includes a retry_after field indicating how many seconds to wait before retrying.

Response

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711267200
Retry-After: 34
Content-Type: application/json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Retry after 34 seconds.",
    "request_id": "req_abc123",
    "details": {
      "limit": 100,
      "window": "1m",
      "retry_after": 34
    }
  }
}

Recommended Client Behavior

Exponential Backoff

Clients should implement exponential backoff when receiving 429 responses. The recommended strategy:

  1. Wait for the duration specified in the Retry-After header.
  2. If the retry also receives a 429, wait 2 * Retry-After.
  3. Continue doubling the wait time up to a maximum of 60 seconds.
  4. After 5 consecutive 429 responses, pause all requests for 120 seconds.

Proactive Throttling

Monitor the X-RateLimit-Remaining header. When remaining requests drop below 10% of the limit, reduce request frequency proactively rather than waiting for a 429 response.

remaining = int(response.headers["X-RateLimit-Remaining"])
limit = int(response.headers["X-RateLimit-Limit"])

if remaining < limit * 0.10:
    time.sleep(1)  # Slow down before hitting the wall

Endpoint-Specific Limits

Certain endpoints have additional per-endpoint limits that apply independently of the global tier limits.

EndpointLimitRationale
POST /v1/regulatory/report10 per minuteSAR generation is compute-intensive.
POST /v1/screening/batch5 per minuteBatch screening jobs consume significant database resources.
GET /v1/alerts (with export=true)2 per minuteFull alert export generates large result sets.

When an endpoint-specific limit is exceeded, the 429 response includes the endpoint path in the details object:

{
  "error": {
    "code": "ENDPOINT_RATE_LIMIT_EXCEEDED",
    "message": "Endpoint-specific rate limit exceeded for POST /v1/regulatory/report.",
    "request_id": "req_def456",
    "details": {
      "endpoint": "POST /v1/regulatory/report",
      "limit": 10,
      "window": "1m",
      "retry_after": 42
    }
  }
}

Rate Limit Scope

ScopeKeyDescription
API keyAuthorization header valueAll limits are tracked per API key. Multiple keys under the same organization have independent limits.
IP addressClient IPNot used for primary rate limiting. Used only for abuse detection on unauthenticated endpoints.

Monitoring

Current rate limit usage is available through the Veris dashboard under Settings > API Usage. Historical usage data is retained for 90 days.

For programmatic monitoring, query the /v1/usage/rate-limits endpoint to retrieve current consumption against all applicable limits.

{
  "global": {
    "limit": 1000,
    "used": 153,
    "remaining": 847,
    "resets_at": "2026-03-24T14:01:00Z"
  },
  "endpoints": {
    "POST /v1/regulatory/report": {
      "limit": 10,
      "used": 2,
      "remaining": 8,
      "resets_at": "2026-03-24T14:01:00Z"
    }
  }
}

Error Codes

Veris uses conventional HTTP status codes and returns structured error responses with machine-readable error codes, human-readable messages, and request tracing identifiers. Every error response follows the same format regardless of the endpoint or failure type.

Error Response Format

{
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "No entity found with ID ent_abc123.",
    "request_id": "req_7f3a2b1c",
    "details": {
      "entity_id": "ent_abc123"
    }
  }
}
FieldTypeDescription
codestringMachine-readable error code. Stable across API versions. Use this for programmatic error handling.
messagestringHuman-readable description. May change between versions. Do not parse this field programmatically.
request_idstringUnique identifier for the request. Include this in support inquiries. Format: req_ prefix followed by 8 hex characters.
detailsobjectAdditional context specific to the error. Structure varies by error code. May be omitted when no additional context is available.

HTTP Status Codes

400 Bad Request

The request is malformed, missing required fields, or contains invalid values.

Error CodeDescriptionResolution
INVALID_REQUEST_BODYRequest body failed JSON parsing.Verify the request body is valid JSON.
MISSING_REQUIRED_FIELDA required field is absent. details.field identifies the missing field.Include the specified field in the request body.
INVALID_FIELD_VALUEA field value does not meet validation constraints.Check field constraints in the endpoint documentation.
IDEMPOTENCY_KEY_REQUIREDA required Idempotency-Key header is missing.Provide a UUID v4 in the Idempotency-Key header.
IDEMPOTENCY_KEY_INVALIDThe Idempotency-Key value is not a valid UUID v4.Use a properly formatted UUID v4.
INVALID_DATE_RANGEThe specified date range is invalid (start after end, or exceeds maximum span).Correct the date range parameters.

401 Unauthorized

Authentication credentials are missing or invalid.

Error CodeDescriptionResolution
AUTHENTICATION_REQUIREDNo Authorization header provided.Include a valid bearer token or API key.
INVALID_API_KEYThe provided API key does not exist or has been revoked.Verify the API key in the Veris dashboard.
TOKEN_EXPIREDThe bearer token has expired.Obtain a new token via the authentication endpoint.
INVALID_SIGNATUREHMAC signature verification failed (Oris SDK).Verify the signing key and canonical request format.

403 Forbidden

The authenticated identity lacks permission for the requested operation.

Error CodeDescriptionResolution
INSUFFICIENT_PERMISSIONSThe API key does not have the required scope. details.required_scope identifies the needed permission.Request the required scope through the Veris dashboard.
TIER_RESTRICTIONThe requested feature is not available on the current subscription tier.Contact sales to upgrade the subscription tier.
IP_NOT_ALLOWLISTEDThe request originates from an IP address not on the allowlist.Add the IP address to the allowlist in the dashboard.

404 Not Found

The requested resource does not exist.

Error CodeDescriptionResolution
ENTITY_NOT_FOUNDNo entity matches the provided identifier.Verify the entity ID.
ALERT_NOT_FOUNDNo alert matches the provided identifier.Verify the alert ID.
CASE_NOT_FOUNDNo case matches the provided identifier.Verify the case ID.
ENDPOINT_NOT_FOUNDThe requested API endpoint does not exist.Check the API reference for valid endpoints.

409 Conflict

The request conflicts with the current state of the resource.

Error CodeDescriptionResolution
IDEMPOTENCY_KEY_CONFLICTThe idempotency key was already used with a different request body.Generate a new idempotency key for the new request.
CASE_STATUS_CONFLICTThe requested status transition is not valid from the current state. details.current_status and details.requested_status identify the conflict.Review the case status state machine.
DUPLICATE_ENTITYAn entity with the same identifying attributes already exists.Use the existing entity or update it.

422 Unprocessable Entity

The request is syntactically valid but semantically incorrect.

Error CodeDescriptionResolution
INVALID_ADDRESS_FORMATThe provided blockchain address is not a valid format for the specified chain.Verify the address format (e.g., 0x-prefixed, 42 characters for EVM).
UNSUPPORTED_CHAINThe specified blockchain is not supported.Check the list of supported chains.
INVALID_JURISDICTIONThe specified jurisdiction code is not recognized.Use a valid ISO 3166-1 alpha-2 code.
AMOUNT_OUT_OF_RANGEThe specified amount exceeds allowable bounds.Check the endpoint documentation for valid ranges.

429 Too Many Requests

Rate limit exceeded. See the Rate Limits section for detailed guidance.

Error CodeDescriptionResolution
RATE_LIMIT_EXCEEDEDGlobal rate limit exceeded.Wait for the duration in the Retry-After header, then retry.
ENDPOINT_RATE_LIMIT_EXCEEDEDEndpoint-specific rate limit exceeded.Wait for the duration in the Retry-After header, then retry.

500 Internal Server Error

An unexpected error occurred on the server. These errors are automatically reported to the Veris engineering team.

Error CodeDescriptionResolution
INTERNAL_ERRORAn unexpected server error occurred.Retry the request. If the error persists, contact support with the request_id.

503 Service Unavailable

A downstream dependency is unavailable. The circuit breaker has engaged to protect system stability.

Error CodeDescriptionResolution
AGENT_UNAVAILABLEThe requested AI agent (triage, investigation, or SAR generation) is temporarily unavailable.Retry after 30 seconds. The circuit breaker will re-test the downstream dependency automatically.
DATABASE_UNAVAILABLEThe database is temporarily unreachable.Retry after 10 seconds.
SERVICE_MAINTENANCEThe platform is undergoing scheduled maintenance. details.estimated_completion provides the expected completion time.Wait until the maintenance window closes.

Circuit Breaker Behavior

Veris implements circuit breakers on all downstream dependencies (AI models, database, vector store, sanctions list providers). When a dependency fails repeatedly, the circuit opens and subsequent requests receive a 503 response immediately rather than waiting for a timeout.

StateBehavior
ClosedRequests flow normally. Failures are counted.
OpenAll requests return 503 immediately. The breaker re-tests the dependency every 30 seconds.
Half-OpenA single test request is sent to the dependency. If it succeeds, the breaker closes. If it fails, the breaker re-opens.

The circuit breaker opens after 5 consecutive failures or a failure rate exceeding 50% within a 60-second window.

Retry Guidance Summary

Status CodeRetryableStrategy
400NoFix the request.
401NoFix authentication.
403NoFix permissions.
404NoFix the resource identifier.
409NoResolve the conflict.
422NoFix the request payload.
429YesExponential backoff with Retry-After header.
500YesRetry up to 3 times with exponential backoff (1s, 2s, 4s).
503YesRetry after 30 seconds. Check the status page if errors persist beyond 5 minutes.

SLA and Uptime

Veris provides tiered Service Level Agreements that define uptime commitments, resolution latency guarantees, and incident response obligations. All SLA metrics are measured over calendar month windows and reported through the public status page.

Uptime SLA by Tier

TierMonthly UptimeMaximum Downtime per MonthCredit Threshold
Emerging99.5%3 hours 39 minutesBelow 99.0%
Enterprise99.9%43 minutesBelow 99.5%
Sovereign99.99%4 minutes 19 secondsBelow 99.9%

Uptime is calculated as:

Uptime % = ((Total Minutes in Month - Downtime Minutes) / Total Minutes in Month) * 100

Downtime is defined as any period during which the Veris API returns 5xx responses to more than 5% of requests within a 1-minute measurement window. Scheduled maintenance windows announced at least 72 hours in advance are excluded from downtime calculations.

Resolution Latency SLA

Resolution latency measures the time from alert creation to completed triage output. This is the primary performance metric for the Veris real-time pipeline.

MetricTargetMeasurement
P50 resolution latency< 5 secondsMedian across all alerts in the billing period
P95 resolution latency< 40 seconds95th percentile across all alerts in the billing period
P99 resolution latency< 120 seconds99th percentile across all alerts in the billing period

Resolution latency covers the full pipeline: alert ingestion, ML pre-triage scoring, deterministic pre-filter evaluation, AI triage classification, and result persistence. It does not include investigation or SAR generation, which are triggered asynchronously after triage.

Agent-Specific Latency Targets

AgentP95 Target
Triage Agent< 3 seconds
Investigation Agent< 25 seconds
SAR Generation Agent< 20 seconds
Reserve Monitor Agent< 15 seconds

API Response Time SLA

Independent of agent processing, the API itself has response time commitments for synchronous endpoints.

Endpoint CategoryP95 Target
Read operations (GET)< 200 milliseconds
Write operations (POST, PUT, PATCH)< 500 milliseconds
List operations with pagination< 400 milliseconds
Search and filter operations< 600 milliseconds

Monitoring Infrastructure

Prometheus

All Veris services export metrics to Prometheus, which scrapes at 15-second intervals. Key metrics:

MetricTypeDescription
veris_api_request_duration_secondshistogramAPI response time by endpoint and status code
veris_triage_duration_secondshistogramTriage agent processing time
veris_investigation_duration_secondshistogramInvestigation agent processing time
veris_alert_queue_depthgaugeNumber of alerts awaiting triage
veris_circuit_breaker_stategaugeCircuit breaker state (0=closed, 1=half-open, 2=open)

Grafana

Grafana dashboards provide real-time visibility into all SLA metrics. Dashboards are organized by domain:

DashboardContents
API PerformanceRequest rate, latency percentiles, error rate, status code distribution
Agent PipelineTriage/Investigation/SAR latency, queue depth, success rate
InfrastructureCPU, memory, disk, network across all containers
SLA ComplianceMonthly uptime tracking, latency SLA adherence, trend analysis

Alerting

Prometheus Alertmanager fires alerts when metrics approach SLA thresholds:

AlertConditionNotification
HighErrorRate5xx rate exceeds 1% for 5 minutesPagerDuty + Slack
HighLatencyP95 response time exceeds 180ms for 10 minutesSlack
AgentQueueBacklogAlert queue depth exceeds 100 for 5 minutesPagerDuty + Slack
CircuitBreakerOpenAny circuit breaker in open state for 2 minutesPagerDuty

Incident Response

Severity Levels

SeverityDefinitionResponse TimeResolution Target
SEV-1Complete service outage or data integrity issue15 minutes1 hour
SEV-2Degraded performance affecting more than 10% of requests30 minutes4 hours
SEV-3Partial functionality impaired, workaround available2 hours24 hours
SEV-4Minor issue, no customer impact24 hours5 business days

Incident Communication

ChannelUpdate FrequencyContent
Status pageEvery 15 minutes during SEV-1/SEV-2Current state, impact scope, estimated resolution
Email notificationAt incident start, major updates, and resolutionSummary for affected customers
Post-incident reportWithin 48 hours of resolutionRoot cause, timeline, corrective actions

Enterprise and Sovereign tier customers receive direct communication from their assigned technical account manager during SEV-1 and SEV-2 incidents.

Status Page

The Veris status page is available at:

https://status.useveris.finance

The status page displays:

Sovereign tier customers receive a dedicated status page endpoint that reflects only the components relevant to their deployment.

SLA Credits

When the monthly uptime falls below the committed SLA, customers are eligible for service credits applied to the next billing cycle.

Uptime AchievedCredit (% of Monthly Fee)
99.0% to SLA commitment10%
95.0% to 98.99%25%
Below 95.0%50%

Credit requests must be submitted within 30 days of the month in which the SLA was not met. Credits are capped at 50% of the monthly fee and do not apply to one-time fees or professional services.

Exclusions

The following are excluded from SLA calculations: