> ## Documentation Index
> Fetch the complete documentation index at: https://docs.verifow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Transactions

> Submit transactions for real-time AML/CFT screening and view screening history

## Overview

The Transactions module is the entry point for all AML/CFT screening. Every financial transaction your institution processes should be submitted to the `/screen` endpoint. The platform returns a verdict in under 200ms with a risk score, outcome, and triggered rules.

## Common Workflows

**Real-time screening:** Your core banking system calls `/screen` on every outgoing transfer → The API returns an outcome → Your system routes the transaction based on `APPROVE`, `REVIEW`, `ESCALATE`, or `BLOCK`.

**Post-hoc review:** A compliance officer queries the transaction list to review recent screenings, drill into a specific transaction, and view the full verdict breakdown.

## Permissions

| Action                 | Who Can Do It           |
| ---------------------- | ----------------------- |
| Screen transaction     | All authenticated users |
| List transactions      | All authenticated users |
| Get transaction detail | All authenticated users |

## Endpoints

| Method | Endpoint                      | Description                        |
| ------ | ----------------------------- | ---------------------------------- |
| `POST` | `/api/v1/transactions/screen` | Submit a transaction for screening |
| `GET`  | `/api/v1/transactions`        | List screened transactions         |
| `GET`  | `/api/v1/transactions/:id`    | Retrieve a specific transaction    |

***

### Screen Transaction

Submit a single transaction for real-time screening. The response includes the final outcome, risk breakdown per engine, triggered rules, and recommended actions.

**Request Body**

| Field                    | Type     | Required | Description                                                                                                                 |
| ------------------------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `externalId`             | `string` | ✅        | Your unique transaction reference                                                                                           |
| `type`                   | `string` | ✅        | `CASH_DEPOSIT`, `CASH_WITHDRAWAL`, `TRANSFER`, `INTERNATIONAL_TRANSFER`, `POS`, `ATM`, `MOBILE`, `USSD`, `INTERNET_BANKING` |
| `channel`                | `string` | ✅        | `BRANCH`, `MOBILE_APP`, `INTERNET_BANKING`, `POS_TERMINAL`, `ATM`, `USSD`, `AGENT`, `API`                                   |
| `amount`                 | `number` | ✅        | Amount in base currency unit (e.g. `5000000` = ₦5M)                                                                         |
| `currency`               | `string` |          | ISO 4217 code. Defaults to `NGN`                                                                                            |
| `senderAccountNumber`    | `string` | ✅        | Sender's bank account number (NUBAN, 10 digits)                                                                             |
| `senderBvn`              | `string` |          | Sender's BVN. Enables identity risk scoring                                                                                 |
| `senderKycStatus`        | `string` |          | `VERIFIED`, `PENDING`, `IN_PROGRESS`, `REJECTED`, `EXPIRED`, `NONE`                                                         |
| `senderKycTier`          | `string` |          | `TIER_1`, `TIER_2`, `TIER_3`. Required in EXTERNAL mode; optional in HYBRID mode                                            |
| `senderKycVerifiedAt`    | `string` |          | ISO 8601 timestamp of last KYC verification                                                                                 |
| `senderKycExternalRef`   | `string` |          | Internal KYC reference (e.g. `CORE-BANK-KYC-78432`)                                                                         |
| `senderName`             | `string` | ✅        | Sender's full name. Screened against sanctions                                                                              |
| `senderBankCode`         | `string` |          | Sender's bank CBN code (e.g. `"058"` for GTBank)                                                                            |
| `receiverAccountNumber`  | `string` |          | Receiver's account number                                                                                                   |
| `receiverName`           | `string` |          | Receiver's full name. Also screened                                                                                         |
| `receiverKycStatus`      | `string` |          | `VERIFIED`, `PENDING`, `IN_PROGRESS`, `REJECTED`, `EXPIRED`, `NONE`                                                         |
| `receiverKycVerifiedAt`  | `string` |          | ISO 8601 timestamp of last KYC verification                                                                                 |
| `receiverKycExternalRef` | `string` |          | Internal KYC reference for the receiver                                                                                     |
| `receiverBvn`            | `string` |          | Receiver's BVN                                                                                                              |
| `receiverBankCode`       | `string` |          | Receiver's bank CBN code (e.g. `"058"`)                                                                                     |
| `receiverCountry`        | `string` |          | ISO 3166-1 alpha-2. Triggers FTR if ≠ `NG`                                                                                  |
| `narration`              | `string` |          | Transaction description                                                                                                     |
| `deviceId`               | `string` |          | Device fingerprint for behavioral analysis                                                                                  |
| `ipAddress`              | `string` |          | Client IP for geo-anomaly detection                                                                                         |
| `latitude`               | `number` |          | GPS latitude. Used for geo-anomaly detection                                                                                |
| `longitude`              | `number` |          | GPS longitude. Used for geo-anomaly detection                                                                               |
| `timestamp`              | `string` | ✅        | ISO 8601 timestamp                                                                                                          |
| `metadata`               | `object` |          | Optional metadata for business transactions (see KYB Screening)                                                             |

**Example Request**

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST /v1/transactions/screen \
    -H "Authorization: Bearer <access_token>" \
    -H "Content-Type: application/json" \
    -d '{
      "externalId": "TXN-2026-001",
      "type": "TRANSFER",
      "channel": "MOBILE_APP",
      "amount": 5000000,
      "currency": "NGN",
      "senderAccountNumber": "0123456789",
      "senderBvn": "22012345678",
      "senderKycStatus": "VERIFIED",
      "senderKycTier": "TIER_2",
      "senderKycVerifiedAt": "2026-05-01T10:00:00Z",
      "senderKycExternalRef": "CORE-BANK-KYC-78432",
      "senderName": "John Doe",
      "senderBankCode": "058",
      "receiverAccountNumber": "9876543210",
      "receiverBvn": "22087654321",
      "receiverName": "Jane Smith",
      "receiverBankCode": "033",
      "receiverCountry": "NG",
      "receiverKycStatus": "PENDING",
      "receiverKycVerifiedAt": "2026-04-15T09:30:00Z",
      "receiverKycExternalRef": "CORE-BANK-KYC-99123",
      "narration": "Payment for goods",
      "deviceId": "device_abc123",
      "ipAddress": "102.89.47.12",
      "latitude": 6.5244,
      "longitude": 3.3792,
      "timestamp": "2026-05-16T12:00:00Z",
      "metadata": {
        "entityType": "INDIVIDUAL"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("/v1/transactions/screen", {
    method: "POST",
    headers: {
      Authorization: "Bearer <access_token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      externalId: "TXN-2026-001",
      type: "TRANSFER",
      channel: "MOBILE_APP",
      amount: 5000000,
      currency: "NGN",
      senderAccountNumber: "0123456789",
      senderBvn: "22012345678",
      senderKycStatus: "VERIFIED",
      senderKycTier: "TIER_2",
      senderKycVerifiedAt: "2026-05-01T10:00:00Z",
      senderKycExternalRef: "CORE-BANK-KYC-78432",
      senderName: "John Doe",
      senderBankCode: "058",
      receiverAccountNumber: "9876543210",
      receiverBvn: "22087654321",
      receiverName: "Jane Smith",
      receiverBankCode: "033",
      receiverCountry: "NG",
      receiverKycStatus: "PENDING",
      receiverKycVerifiedAt: "2026-04-15T09:30:00Z",
      receiverKycExternalRef: "CORE-BANK-KYC-99123",
      narration: "Payment for goods",
      deviceId: "device_abc123",
      ipAddress: "102.89.47.12",
      latitude: 6.5244,
      longitude: 3.3792,
      timestamp: "2026-05-16T12:00:00Z",
      metadata: {
        entityType: "INDIVIDUAL",
      },
    }),
  });
  const result = await response.json();
  console.log(result.data.outcome); // "APPROVE" | "REVIEW" | "ESCALATE" | "BLOCK"
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "/v1/transactions/screen",
      headers={
          "Authorization": "Bearer <access_token>",
          "Content-Type": "application/json"
      },
      json={
          "externalId": "TXN-2026-001",
          "type": "TRANSFER",
          "channel": "MOBILE_APP",
          "amount": 5000000,
          "currency": "NGN",
          "senderAccountNumber": "0123456789",
          "senderBvn": "22012345678",
          "senderKycStatus": "VERIFIED",
          "senderKycTier": "TIER_2",
          "senderKycVerifiedAt": "2026-05-01T10:00:00Z",
          "senderKycExternalRef": "CORE-BANK-KYC-78432",
          "senderName": "John Doe",
          "senderBankCode": "058",
          "receiverAccountNumber": "9876543210",
          "receiverBvn": "22087654321",
          "receiverName": "Jane Smith",
          "receiverBankCode": "033",
          "receiverCountry": "NG",
          "receiverKycStatus": "PENDING",
          "receiverKycVerifiedAt": "2026-04-15T09:30:00Z",
          "receiverKycExternalRef": "CORE-BANK-KYC-99123",
          "narration": "Payment for goods",
          "deviceId": "device_abc123",
          "ipAddress": "102.89.47.12",
          "latitude": 6.5244,
          "longitude": 3.3792,
          "timestamp": "2026-05-16T12:00:00Z",
          "metadata": {
              "entityType": "INDIVIDUAL"
          }
      }
  )
  result = response.json()
  print(result["data"]["outcome"])  # "APPROVE" | "REVIEW" | "ESCALATE" | "BLOCK"
  ```
</CodeGroup>

**Example Response — 200 OK**

```json theme={null}
{
  "success": true,
  "data": {
    "transactionId": "fae50ecb-d997-4700-bae7-49650678bb06",
    "externalId": "TXN-0791-389803",
    "outcome": "BLOCK",
    "riskLevel": "HIGH",
    "aggregateScore": 100,
    "actions": ["NOTIFY_OFFICER", "CREATE_CASE"],
    "riskBreakdown": [
      {
        "category": "Regulatory Compliance",
        "score": 0,
        "outcome": "APPROVE",
        "rulesTriggered": 0,
        "latencyMs": 3
      },
      {
        "category": "Sanctions & PEP Screening",
        "score": 0,
        "outcome": "APPROVE",
        "rulesTriggered": 0,
        "latencyMs": 3020
      },
      {
        "category": "KYC Verification",
        "score": 100,
        "outcome": "BLOCK",
        "rulesTriggered": 1,
        "latencyMs": 0
      },
      {
        "category": "Custom Rules",
        "score": 0,
        "outcome": "APPROVE",
        "rulesTriggered": 0,
        "latencyMs": 10074
      },
      {
        "category": "Decision Engine",
        "score": 0,
        "outcome": "APPROVE",
        "rulesTriggered": 0,
        "latencyMs": 48
      },
      {
        "category": "Behavioral Analysis",
        "score": 0,
        "outcome": "APPROVE",
        "rulesTriggered": 1,
        "latencyMs": 206
      }
    ],
    "triggeredRules": [
      {
        "name": "KYC Status Non-Verified",
        "category": "KYC Verification",
        "riskScore": 100,
        "details": "No KYC record found for sender (BVN: 22075678966)",
        "createdBy": null,
        "kycSource": "DATABASE",
        "kycExternalRef": null
      }
    ],
    "totalLatencyMs": 13447
  }
}
```

**Example Response — 400 Bad Request**

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      { "field": "amount", "message": "amount must be a positive number" }
    ]
  }
}
```

**Example Response — 409 Conflict**

```json theme={null}
{
  "success": false,
  "error": {
    "code": "DUPLICATE_EXTERNAL_ID",
    "message": "This externalId has already been processed",
    "data": {
      "transactionId": "fae50ecb-d997-4700-bae7-49650678bb06",
      "outcome": "REVIEW",
      "riskLevel": "MEDIUM",
      "aggregateScore": 32
    }
  }
}
```

<Note>
  **Idempotency:** Sending the same `externalId` twice returns the cached
  verdict without re-screening. This makes retries safe.
</Note>

***

### List Transactions

Retrieve a paginated list of all screened transactions in your tenant.

**Query Parameters**

| Parameter | Type      | Default | Description                                        |
| --------- | --------- | ------- | -------------------------------------------------- |
| `page`    | `integer` | `1`     | Page number                                        |
| `limit`   | `integer` | `20`    | Items per page (max 100)                           |
| `outcome` | `string`  | —       | Filter by `APPROVE`, `REVIEW`, `ESCALATE`, `BLOCK` |
| `type`    | `string`  | —       | Filter by transaction type                         |
| `from`    | `string`  | —       | ISO 8601 start date                                |
| `to`      | `string`  | —       | ISO 8601 end date                                  |

**Example Request**

```bash theme={null}
curl -X GET "/v1/transactions?outcome=BLOCK&page=1&limit=10" \
  -H "Authorization: Bearer <access_token>"
```

**Example Response — 200 OK**

```json theme={null}
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "fae50ecb-d997-4700-bae7-49650678bb06",
        "externalId": "TXN-0791-389803",
        "type": "TRANSFER",
        "channel": "MOBILE_APP",
        "amount": "5000000",
        "currency": "NGN",
        "senderAccountNumber": "0123456789",
        "senderName": "John Doe",
        "receiverName": "Jane Smith",
        "receiverCountry": "NG",
        "transactionTimestamp": "2026-05-16T12:00:00Z",
        "verdict": {
          "outcome": "BLOCK",
          "riskLevel": "CRITICAL",
          "aggregateScore": 95,
          "totalLatencyMs": 145
        },
        "createdAt": "2026-05-16T12:00:00Z"
      }
    ],
    "total": 3,
    "page": 1,
    "limit": 10,
    "totalPages": 1
  }
}
```

***

### Get Transaction Detail

Retrieve the full screening result for a single transaction, including the complete verdict with engine breakdowns.

**Path Parameters**

| Parameter | Type     | Description      |
| --------- | -------- | ---------------- |
| `id`      | `string` | Transaction UUID |

**Example Request**

```bash theme={null}
curl -X GET /v1/transactions/fae50ecb-d997-4700-bae7-49650678bb06 \
  -H "Authorization: Bearer <access_token>"
```

**Example Response — 200 OK**

```json theme={null}
{
  "success": true,
  "data": {
    "id": "fae50ecb-d997-4700-bae7-49650678bb06",
    "tenantId": "660e8400-e29b-41d4-a716-446655440001",
    "externalId": "TXN-0791-389803",
    "type": "TRANSFER",
    "channel": "MOBILE_APP",
    "amount": "5000000",
    "currency": "NGN",
    "senderAccountNumber": "0123456789",
    "senderBvn": "22012345678",
    "senderName": "John Doe",
    "senderBankCode": null,
    "receiverAccountNumber": "9876543210",
    "receiverBvn": null,
    "receiverName": "Jane Smith",
    "receiverBankCode": null,
    "receiverCountry": "NG",
    "narration": "Payment for goods",
    "deviceId": null,
    "ipAddress": null,
    "latitude": null,
    "longitude": null,
    "transactionTimestamp": "2026-05-16T12:00:00Z",
    "metadata": null,
    "createdAt": "2026-05-16T12:00:00Z",
    "verdict": {
      "id": "vtx_001",
      "transactionId": "fae50ecb-d997-4700-bae7-49650678bb06",
      "outcome": "REVIEW",
      "riskLevel": "MEDIUM",
      "aggregateScore": 32,
      "engineVerdicts": [
        {
          "category": "KYC Verification",
          "score": 40,
          "outcome": "REVIEW",
          "rulesTriggered": 1,
          "latencyMs": 0
        }
      ],
      "actions": ["NOTIFY_OFFICER"],
      "totalLatencyMs": 13447,
      "processedAt": "2026-05-16T12:00:01Z"
    }
  }
}
```

<Warning>
  Transaction data is immutable once screened. If you need to re-evaluate,
  submit a new transaction with a different `externalId`.
</Warning>

## KYC Trust Modes

Tenants can configure how KYC status is resolved during transaction screening:

| Mode       | Behavior                                                               |
| ---------- | ---------------------------------------------------------------------- |
| `STRICT`   | **Default.** KYC records must exist in the DB. Payload KYC is ignored. |
| `HYBRID`   | Uses payload KYC if provided, falls back to DB lookup.                 |
| `EXTERNAL` | Requires KYC status in every transaction payload. No local DB lookup.  |

Set the mode via `PATCH /api/v1/tenants/me` with `kycTrustMode`.
