# AML Screening

This document describes the Anti-Money Laundering (AML) screening endpoints available in the Vove API. These endpoints allow you to screen individuals and entities against various watchlists, sanctions lists, and PEP (Politically Exposed Persons) databases.

## Overview

The AML API provides two main workflows:

* Case Management Workflow: Create cases that track screening results over time, allowing you to review, update status, and maintain an audit trail.
* Direct Screening Workflow: Perform one-time screenings without creating a case, receiving results immediately in the response.

***

## Managing AML Flows

AML Flows are configuration templates that define how screenings are performed. Each flow specifies which watchlists to search, the type of entities to screen (individuals or entities), and the matching sensitivity level. Flows allow you to standardize your screening process and reuse configurations across multiple screenings.

### Understanding AML Flows

An AML Flow consists of the following components:

* Name: A descriptive name for the flow (e.g., "High-Risk Customer Screening", "Standard Individual Check")
* Entity Type: Whether the flow screens individuals (`INDIVIDUAL`) or entities/companies (`ENTITY`)
* Search Sources: The specific watchlists and databases to search against

### Creating a New Flow

{% stepper %}
{% step %}

### Navigate to AML Flows

* Log in to your Vove Dashboard
* Go to the AML **Screening** section
* Click on **Screening Flows** in the sidebar
  {% endstep %}

{% step %}

### Click "Create New Flow"

* You'll see a form to configure your new flow
  {% endstep %}

{% step %}

### Configure Flow Settings

* Flow Name: Enter a descriptive name (e.g., "Enhanced PEP Screening")
* Entity Type: Select either:
  * `INDIVIDUAL` - For screening natural persons
  * `ENTITY` - For screening companies and legal entities
* Source type: Select the specific watchlists and databases to search against
  {% endstep %}

{% step %}

### Select Search Sources

* Browse the available watchlists and databases
* Select the sources you want to include in this flow

Available source types include:

* Sanctions Lists: Government sanctions lists (OFAC, UN, EU, etc.)
* PEP Lists: Politically Exposed Persons databases
* Other Watchlists: Additional regulatory and law enforcement lists
  {% endstep %}

{% step %}

### Save the Flow

* Click **Save** or **Create Flow**
* The flow will be created and available for use immediately (if active)
  {% endstep %}
  {% endstepper %}

### Flow Management Best Practices

* Naming Convention: Use clear, descriptive names that indicate the flow's purpose (e.g., "High-Risk Individual Screening", "Standard Entity Check", "Enhanced PEP Review")
* Version Control: When making significant changes, consider creating a new flow  — preserves historical configurations and allows A/B testing
* Documentation: Document why specific sources or settings were chosen — helps with compliance audits and troubleshooting
* Regular Review: Periodically review your flows to ensure they meet current requirements — regulatory changes may require source updates
* Testing: Test new flows with sample data before deploying to production — verify expected matches and false positive rates
* Monitoring: Track which flows are used most frequently — optimize popular flows for performance and consider deprecating unused flows

***

### Webhook Events

Vove automatically sends webhook events to notify your system when AML case statuses change. This allows you to integrate AML screening results into your own workflows and systems in real-time.

#### When Webhooks Are Sent

Webhooks are triggered in the following scenarios:

1. **Case Creation**: When a new AML case is created via `POST /aml/case` and a `refId` is provided
2. **Status Updates**: When an AML case status is updated via `PUT /aml/case/:id/status` and the case has a `refId`

**Important**: Webhooks are only sent if the case has a `refId`. If you want to receive webhook notifications, always provide a `refId` when creating cases.

#### Webhook Event Types

The following webhook events are sent based on the AML case status:

| Event Type           | Description                    | Triggered When                                                              |
| -------------------- | ------------------------------ | --------------------------------------------------------------------------- |
| `user.aml.no_match`  | No matches found               | Case is created with `NO_MATCH` status or status is updated to `NO_MATCH`   |
| `user.aml.in_review` | Matches found, requires review | Case is created with `IN_REVIEW` status or status is updated to `IN_REVIEW` |
| `user.aml.cleared`   | Case cleared after review      | Case status is updated to `CLEARED`                                         |
| `user.aml.rejected`  | Case rejected after review     | Case status is updated to `REJECTED`                                        |

#### Webhook Payload Structure

Each webhook event includes the following payload:

```json
{
  "refId": "customer-12345",
  "status": "IN_REVIEW"
}
```

**Payload Fields:**

| Field    | Type   | Description                                                                       |
| -------- | ------ | --------------------------------------------------------------------------------- |
| `refId`  | string | Your internal reference ID that was provided when creating the case               |
| `status` | enum   | Current status of the AML case: `NO_MATCH`, `IN_REVIEW`, `CLEARED`, or `REJECTED` |

#### Example Webhook Payloads

**Case Created - No Match:**

```json
{
  "refId": "customer-12345",
  "status": "NO_MATCH"
}
```

**Case Created - Matches Found:**

```json
{
  "refId": "customer-12345",
  "status": "IN_REVIEW"
}
```

**Case Status Updated - Cleared:**

```json
{
  "refId": "customer-12345",
  "status": "CLEARED"
}
```

**Case Status Updated - Rejected:**

```json
{
  "refId": "customer-12345",
  "status": "REJECTED"
}
```

#### Setting Up Webhooks

To receive webhook events, you need to:

1. **Configure Webhook URL**: Set your webhook endpoint URL in the Vove Dashboard
2. **Provide refId**: Always include a `refId` when creating AML cases
3. **Handle Events**: Implement an endpoint to receive and process webhook events

#### Webhook Best Practices

1. **Idempotency**: Webhook events may be delivered multiple times. Implement idempotency checks using the `refId` and `status` combination
2. **Retry Logic**: Implement retry logic for failed webhook deliveries
3. **Verification**: Verify webhook signatures to ensure events are from Vove
4. **Async Processing**: Process webhooks asynchronously to avoid blocking your main application flow
5. **Logging**: Log all webhook events for audit and debugging purposes

#### Webhook Delivery

* Webhooks are delivered via HTTPS POST requests to your configured endpoint
* Events are sent asynchronously and do not block API responses
* Failed deliveries are retried according to Vove's retry policy
* Ensure your webhook endpoint returns a `2xx` status code to acknowledge receipt

***

## AML APIs

### Create AML Case

Creates a new AML case that automatically runs screening against configured watchlists and returns the case status. This endpoint is ideal for tracking screenings over time and maintaining an audit trail.

Endpoint: POST /aml/case

Request Body:

```json
{
  "flowId": "507f1f77bcf86cd799439011",
  "screeningData": {
    "name": "John Doe",
    "dob": "15/03/1985",
    "gender": "male"
  },
  "refId": "customer-12345"
}
```

Request Parameters:

| Field                  | Type   | Required | Description                                     |
| ---------------------- | ------ | -------- | ----------------------------------------------- |
| `flowId`               | string | Yes      | The ID of the AML flow to use for screening     |
| `screeningData`        | object | Yes      | The data to screen                              |
| `screeningData.name`   | string | Yes      | Full name of the individual or entity to screen |
| `screeningData.dob`    | string | No       | Date of birth in `DD/MM/YYYY` format            |
| `screeningData.gender` | enum   | No       | Gender: `"male"` or `"female"`                  |
| `refId`                | string | Yes      | Your internal reference ID for tracking         |

Response:

```json
{
  "id": "507f1f77bcf86cd799439014",
  "flowId": "507f1f77bcf86cd799439011",
  "screeningData": {
    "name": "John Doe",
    "dob": "15/03/1985",
    "gender": "male"
  },
  "status": "IN_REVIEW",
  "refId": "customer-12345",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}
```

Status Values:

* `NO_MATCH`: No matches found in any watchlists
* `IN_REVIEW`: Matches found, requires manual review

Validation Rules:

* `flowId` must be a valid flow ID
* `screeningData.name` is required and must be a non-empty string
* `dob` must match the format `DD/MM/YYYY` if provided
* `gender` must be either `"male"` or `"female"` if provided
* A `refId` must be provided to track the case updates
* The flow must exist and be active

Example cURL:

```bash
curl -X POST https://api.vove.com/aml/case \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flowId": "507f1f77bcf86cd799439011",
    "screeningData": {
      "name": "John Doe",
      "dob": "15/03/1985",
      "gender": "male"
    },
    "refId": "customer-12345"
  }'
```

Notes:

* The screening runs automatically when the case is created
* If matches are found, the case status is set to `IN_REVIEW`
* If no matches are found, the case status is set to `NO_MATCH`
* A webhook event is triggered if `refId` is provided
* The response excludes sensitive fields like `hits` for security

***

### List AML Cases

Retrieves a paginated list of AML cases for your organization with optional filtering by status and date range.

Endpoint: GET /aml/case

Query Parameters:

| Parameter   | Type                  | Required | Default | Description                                                           |
| ----------- | --------------------- | -------- | ------- | --------------------------------------------------------------------- |
| `page`      | number                | No       | `1`     | Page number (1-indexed)                                               |
| `limit`     | number                | No       | `10`    | Number of items per page                                              |
| `status`    | enum                  | No       | -       | Filter by case status: `NO_MATCH`, `IN_REVIEW`, `CLEARED`, `REJECTED` |
| `startDate` | string (ISO datetime) | No       | -       | Filter cases created on or after this date                            |
| `endDate`   | string (ISO datetime) | No       | -       | Filter cases created on or before this date                           |

Response:

```json
{
  "data": [
    {
      "id": "507f1f77bcf86cd799439014",
      "flowId": "507f1f77bcf86cd799439011",
      "screeningData": {
        "name": "John Doe",
        "dob": "15/03/1985",
        "gender": "male"
      },
      "status": "IN_REVIEW",
      "refId": "customer-12345",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 10
}
```

Response Fields:

| Field   | Type   | Description                               |
| ------- | ------ | ----------------------------------------- |
| `data`  | array  | Array of AML case objects                 |
| `total` | number | Total number of cases matching the filter |
| `page`  | number | Current page number                       |
| `limit` | number | Number of items per page                  |

Example cURL:

```bash
curl -X GET "https://api.vove.com/aml/case?page=1&limit=20&status=IN_REVIEW" \
  -H "x-api-key: YOUR_API_KEY"
```

Example with Date Range:

```bash
curl -X GET "https://api.vove.com/aml/case?startDate=2024-01-01T00:00:00.000Z&endDate=2024-01-31T23:59:59.999Z" \
  -H "x-api-key: YOUR_API_KEY"
```

Status Filter Values:

* `NO_MATCH`: No matches found
* `IN_REVIEW`: Matches found, pending review
* `CLEARED`: Case reviewed and cleared
* `REJECTED`: Case reviewed and rejected

***

### Get AML Case by Reference ID

Retrieves a specific AML case using your internal reference ID. This is useful when you need to check the status of a case using your own tracking identifier.

Endpoint: GET /aml/case/{refId}

Path Parameters:

| Parameter | Type   | Required | Description                |
| --------- | ------ | -------- | -------------------------- |
| `refId`   | string | Yes      | Your internal reference ID |

Response:

```json
{
  "id": "507f1f77bcf86cd799439014",
  "flowId": "507f1f77bcf86cd799439011",
  "screeningData": {
    "name": "John Doe",
    "dob": "15/03/1985",
    "gender": "male"
  },
  "status": "IN_REVIEW",
  "refId": "customer-12345",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}
```

Example cURL:

```bash
curl -X GET https://api.vove.com/aml/case/customer-12345 \
  -H "x-api-key: YOUR_API_KEY"
```

Error Responses:

* `404 Not Found`: Case with the provided `refId` does not exist or belongs to another organization

***

### Screen Without Case Management

Performs a one-time screening against watchlists without creating a case. Results are returned immediately in the response. This endpoint is ideal for quick checks or when you don't need to track the screening over time.

Endpoint: POST /aml/flow/screen

Request Body:

```json
{
  "flowId": "507f1f77bcf86cd799439011",
  "name": "John Doe",
  "dob": "15/03/1985",
  "gender": "male"
}
```

Request Parameters:

| Field    | Type   | Required | Description                                     |
| -------- | ------ | -------- | ----------------------------------------------- |
| `flowId` | string | Yes      | The ID of the AML flow to use for screening     |
| `name`   | string | Yes      | Full name of the individual or entity to screen |
| `dob`    | string | No       | Date of birth in `DD/MM/YYYY` format            |
| `gender` | enum   | No       | Gender: `"male"` or `"female"`                  |

Response:

```json
{
  "timestamp": "2024-01-15T10:30:00.000Z",
  "total_hits": 2,
  "found_records": [
    {
      "id": "record-123",
      "entity_type": "natural",
      "name": "John Doe",
      "date_of_birth": ["15/03/1985"],
      "citizenship": ["US"],
      "source_type": "sanctions",
      "description": ["Sanctioned individual"],
      "alias_names": ["Johnny Doe"],
      "occupations": ["Businessman"]
    },
    {
      "id": "record-456",
      "entity_type": "natural",
      "name": "John Doe",
      "source_type": "pep",
      "pep_type": "domestic",
      "description": ["Politically exposed person"],
      "positions": ["Mayor"]
    }
  ]
}
```

Response Fields:

| Field           | Type                  | Description                               |
| --------------- | --------------------- | ----------------------------------------- |
| `timestamp`     | string (ISO datetime) | Timestamp of the screening                |
| `total_hits`    | number                | Total number of matches found             |
| `found_records` | array                 | Array of matching records from watchlists |

Validation Rules:

* `flowId` must be a valid flow ID
* `name` is required and must be a non-empty string
* `dob` must match the format `DD/MM/YYYY` if provided
* `gender` must be either `"male"` or `"female"` if provided
* The flow must exist and be active

Example cURL:

```bash
curl -X POST https://api.vove.com/aml/flow/screen \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flowId": "507f1f77bcf86cd799439011",
    "name": "John Doe",
    "dob": "15/03/1985",
    "gender": "male"
  }'
```

Error Responses:

* `400 Bad Request`: Invalid request parameters or the flow is not active
* `404 Not Found`: Flow not found or does not belong to your organization

Notes:

* This endpoint does not create a case - results are only returned in the response
* Use this endpoint for quick screenings when you don't need case tracking
* The flow's search configuration (sources, fuzzy search, entity type) is automatically applied
* Results are cached for performance optimization

***

## Error Handling

### Standard HTTP Status Codes

| Status Code                 | Description                                    |
| --------------------------- | ---------------------------------------------- |
| `200 OK`                    | Request successful                             |
| `400 Bad Request`           | Invalid request parameters or validation error |
| `401 Unauthorized`          | Missing or invalid API key                     |
| `404 Not Found`             | Resource not found                             |
| `429 Too Many Requests`     | Rate limit exceeded                            |
| `500 Internal Server Error` | Server error                                   |

### Error Response Format

```json
{
  "statusCode": 400,
  "message": "Validation error message",
  "error": "Bad Request"
}
```

### Common Error Scenarios

Invalid Date Format:

```json
{
  "statusCode": 400,
  "message": "Date of birth must be in DD/MM/YYYY format",
  "error": "Bad Request"
}
```

Missing Required Field:

```json
{
  "statusCode": 400,
  "message": "Either names or search_all must be provided",
  "error": "Bad Request"
}
```

Flow Not Found:

```json
{
  "statusCode": 404,
  "message": "Flow not found",
  "error": "Not Found"
}
```

Case Not Found:

```json
{
  "statusCode": 404,
  "message": "AML case not found",
  "error": "Not Found"
}
```

Flow Not Active:

```json
{
  "statusCode": 400,
  "message": "Flow is not active",
  "error": "Bad Request"
}
```

***

## Use Cases

### Use Case 1: Customer Onboarding with Case Tracking

{% stepper %}
{% step %}

* Call `POST /aml/case` with customer information
  {% endstep %}

{% step %}

* Check the returned `status` field
  {% endstep %}

{% step %}

* If `status` is `IN_REVIEW`, manually review the case
  {% endstep %}

{% step %}

* Use `GET /aml/case/{refId}` to check case status later
  {% endstep %}

{% step %}

* Update case status using the case management endpoints
  {% endstep %}
  {% endstepper %}

### Use Case 2: Quick Pre-Screening Check

{% stepper %}
{% step %}

* Call `POST /aml/flow/screen` with basic information
  {% endstep %}

{% step %}

* Review the `found_records` in the response
  {% endstep %}

{% step %}

* If `total_hits` is 0, proceed with onboarding
  {% endstep %}

{% step %}

* If matches are found, decide whether to proceed or create a case for review
  {% endstep %}
  {% endstepper %}

### Use Case 3: Batch Case Review

{% stepper %}
{% step %}

* Call `GET /aml/case?status=IN_REVIEW&limit=50` to get pending cases
  {% endstep %}

{% step %}

* Review each case using the case details
  {% endstep %}

{% step %}

* Update case status as needed
  {% endstep %}
  {% endstepper %}

### Use Case 4: Case Status Tracking

{% stepper %}
{% step %}

* Use your `refId` to call `GET /aml/case/{refId}`
  {% endstep %}

{% step %}

* Check the `status` field
  {% endstep %}

{% step %}

* Take appropriate action based on the status
  {% endstep %}
  {% endstepper %}

***

## Best Practices

* Always provide `refId`: Use a unique reference ID to track cases in your system
* Handle webhooks: Set up webhook listeners to receive status updates automatically
* Review IN\_REVIEW cases: Cases with `IN_REVIEW` The status requires manual attention
* Use appropriate flows: Configure flows with the right sources and settings for your use case
* Date format: Always use `DD/MM/YYYY` format for dates of birth
* Error handling: Implement proper error handling for all status codes
