Purple Risk & Compliance API OpenAPI

BFSI risk & compliance source system: KYC/CDD profiles & remediation, fraud alerts & investigation cases, AML watchlist, and policy/SOP knowledge. Customer-linked records share the retail-banking `customer_id` (CUST-####). Records carry a `country` (IN | AU | US); narrow any list with the `country` filter. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

This reference is readable without JavaScript. Download the OpenAPI document.

Runtime

/api/purple-suite/risk-compliance

BFSI risk & compliance source system: KYC/CDD profiles & remediation, fraud alerts & investigation cases, AML watchlist, and policy/SOP knowledge. Customer-linked records share the retail-banking `customer_id` (CUST-####). Records carry a `country` (IN | AU | US); narrow any list with the `country` filter. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Servers

[
  {
    "url": "https://marketplace.moveworks.com/api/purple-suite/risk-compliance"
  }
]

Authentication schemes

{
  "patAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}

Each operation lists its security requirements. An empty security array permits an unauthenticated request. Use the scheme and headers declared for the selected contract.

Operations

GET /customers/{id}/risk-summary

Get customer risk summary

One-call risk briefing on a single customer: their KYC profile, open remediation cases, fraud alerts, fraud cases and AML watchlist hits. Prefer this over several separate list calls when the question is whether one named customer is risky. The id is the shared retail-banking customer id (CUST-####).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get customer risk summary",
  "description": "One-call risk briefing on a single customer: their KYC profile, open remediation cases, fraud alerts, fraud cases and AML watchlist hits. Prefer this over several separate list calls when the question is whether one named customer is risky. The id is the shared retail-banking customer id (CUST-####).",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Aggregated risk summary",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "customer_id": {
                "type": "string"
              },
              "kyc_profile": {
                "$ref": "#/components/schemas/KycProfile"
              },
              "open_remediation": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RemediationCase"
                }
              },
              "fraud_alerts": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FraudAlert"
                }
              },
              "fraud_cases": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FraudCase"
                }
              },
              "watchlist_hits": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WatchlistEntry"
                }
              }
            }
          }
        }
      }
    }
  }
}

GET /kyc_profiles

List KYC profiles

KYC/CDD profiles are the per-customer compliance record in Purple Risk & Compliance: verification status (verified | pending | expired | in_remediation), risk level, PEP flag, document checklist, outstanding items and next review date. They key off the retail-banking customer_id (CUST-####); the customer's own profile lives in Purple Retail Banking. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List KYC profiles",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "customer_id",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Filter by customer id"
    },
    {
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "verified",
          "pending",
          "expired",
          "in_remediation"
        ]
      },
      "description": "Filter by KYC status"
    },
    {
      "in": "query",
      "name": "risk_level",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "low",
          "medium",
          "high"
        ]
      },
      "description": "Filter by risk level"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/KycProfile"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "KYC/CDD profiles are the per-customer compliance record in Purple Risk & Compliance: verification status (verified | pending | expired | in_remediation), risk level, PEP flag, document checklist, outstanding items and next review date. They key off the retail-banking customer_id (CUST-####); the customer's own profile lives in Purple Retail Banking. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /kyc_profiles

Create KYC profile

Open a KYC/CDD profile for a customer who does not have one, with initial status, risk level and document checklist.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create KYC profile",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/KycProfile"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/KycProfile"
          }
        }
      }
    }
  },
  "description": "Open a KYC/CDD profile for a customer who does not have one, with initial status, risk level and document checklist."
}

GET /kyc_profiles/{id}

Get KYC profile

Fetch one KYC profile by id with its status, risk level, PEP flag, documents, outstanding items and next review date — the check before onboarding, uplifting or remediating a customer.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get KYC profile",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/KycProfile"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one KYC profile by id with its status, risk level, PEP flag, documents, outstanding items and next review date — the check before onboarding, uplifting or remediating a customer."
}

PATCH /kyc_profiles/{id}

Update KYC profile

Partially update a KYC profile — record a verified document, change risk level or PEP flag, or reschedule the next review. To drive the remediation workflow itself, use the remediation-case tools.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update KYC profile",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/KycProfile"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/KycProfile"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Partially update a KYC profile — record a verified document, change risk level or PEP flag, or reschedule the next review. To drive the remediation workflow itself, use the remediation-case tools."
}

DELETE /kyc_profiles/{id}

Delete KYC profile

Permanently delete a KYC profile. Destructive and irreversible, and it erases regulated CDD evidence — only correct for records created in error.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete KYC profile",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a KYC profile. Destructive and irreversible, and it erases regulated CDD evidence — only correct for records created in error."
}

GET /remediation_cases

List KYC remediation cases

Remediation cases are the compliance workflow to fix a customer's KYC gap — expired document, NRI re-KYC, sanctions hit or address mismatch — with status open | in_progress | pending_customer | completed | closed, owner, due date and follow-up actions. Compliance remediation, not IT tickets and not fraud investigations. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List KYC remediation cases",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "customer_id",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Filter by customer id"
    },
    {
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "open",
          "in_progress",
          "pending_customer",
          "completed",
          "closed"
        ]
      },
      "description": "Filter by status"
    },
    {
      "in": "query",
      "name": "reason",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "expired_doc",
          "re_kyc_nri",
          "sanctions_hit",
          "address_mismatch"
        ]
      },
      "description": "Filter by reason"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RemediationCase"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "Remediation cases are the compliance workflow to fix a customer's KYC gap — expired document, NRI re-KYC, sanctions hit or address mismatch — with status open | in_progress | pending_customer | completed | closed, owner, due date and follow-up actions. Compliance remediation, not IT tickets and not fraud investigations. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /remediation_cases

Open remediation case

Open a KYC remediation case against a customer for a specific reason (e.g. expired_doc, re_kyc_nri). Check for an existing open case first — duplicates chase the customer twice.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Open remediation case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/RemediationCase"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RemediationCase"
          }
        }
      }
    }
  },
  "description": "Open a KYC remediation case against a customer for a specific reason (e.g. expired_doc, re_kyc_nri). Check for an existing open case first — duplicates chase the customer twice."
}

GET /remediation_cases/{id}

Get remediation case

Fetch one remediation case by id with its reason, status, owner, due date and the follow-up actions already logged.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get remediation case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RemediationCase"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one remediation case by id with its reason, status, owner, due date and the follow-up actions already logged."
}

PATCH /remediation_cases/{id}

Update remediation case

Partially update remediation case details such as owner, due date or notes. Use the transition tool for status changes and the actions tool to log an outreach step — both enforce their own rules.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update remediation case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/RemediationCase"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RemediationCase"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Partially update remediation case details such as owner, due date or notes. Use the transition tool for status changes and the actions tool to log an outreach step — both enforce their own rules."
}

DELETE /remediation_cases/{id}

Delete remediation case

Permanently delete a remediation case. Destructive and irreversible, and it loses the regulated remediation trail — transition it to completed or closed instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete remediation case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a remediation case. Destructive and irreversible, and it loses the regulated remediation trail — transition it to completed or closed instead."
}

POST /remediation_cases/{id}/transition

Transition a remediation case

Move a KYC remediation case through open → in_progress → pending_customer → completed | closed, recording the reason. Use this rather than patching status: completed and closed are terminal and cannot be transitioned again.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Transition a remediation case",
  "description": "Move a KYC remediation case through open → in_progress → pending_customer → completed | closed, recording the reason. Use this rather than patching status: completed and closed are terminal and cannot be transitioned again.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "open",
                "in_progress",
                "pending_customer",
                "completed",
                "closed"
              ]
            },
            "reason": {
              "type": "string"
            }
          },
          "required": [
            "status"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RemediationCase"
          }
        }
      }
    },
    "400": {
      "description": "Invalid status"
    },
    "404": {
      "description": "Not found"
    },
    "422": {
      "description": "Invalid state transition"
    }
  }
}

POST /remediation_cases/{id}/actions

Log remediation follow-up action

Append an outreach or follow-up step to a KYC remediation case (e.g. 'Send re-KYC SMS with document upload link'), with an owner and due date. Records what will be done next; it does not change the case status.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Log remediation follow-up action",
  "description": "Append an outreach or follow-up step to a KYC remediation case (e.g. 'Send re-KYC SMS with document upload link'), with an owner and due date. Records what will be done next; it does not change the case status.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "example": "Send re-KYC SMS with document upload link"
            },
            "owner": {
              "type": "string"
            },
            "due": {
              "type": "string",
              "format": "date"
            }
          },
          "required": [
            "action"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RemediationCase"
          }
        }
      }
    },
    "400": {
      "description": "Invalid body"
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /fraud_alerts

List fraud alerts

Fraud alerts are machine-generated signals on customer activity (velocity, geo_anomaly, unauthorized_upi, sim_swap, mule_pattern) with severity, score and status new | triaged | dismissed | escalated. They are the raw detections; a confirmed investigation is a fraud case. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List fraud alerts",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "customer_id",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Filter by customer id"
    },
    {
      "in": "query",
      "name": "indicator",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "velocity",
          "geo_anomaly",
          "unauthorized_upi",
          "sim_swap",
          "mule_pattern"
        ]
      },
      "description": "Filter by indicator"
    },
    {
      "in": "query",
      "name": "severity",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "low",
          "medium",
          "high",
          "critical"
        ]
      },
      "description": "Filter by severity"
    },
    {
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "new",
          "triaged",
          "dismissed",
          "escalated"
        ]
      },
      "description": "Filter by status"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FraudAlert"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "Fraud alerts are machine-generated signals on customer activity (velocity, geo_anomaly, unauthorized_upi, sim_swap, mule_pattern) with severity, score and status new | triaged | dismissed | escalated. They are the raw detections; a confirmed investigation is a fraud case. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /fraud_alerts

Create fraud alert

Raise a fraud alert on a customer's activity with an indicator, severity and a description of the flagged behaviour. Detection normally writes these; a human escalation is usually a fraud case instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create fraud alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/FraudAlert"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudAlert"
          }
        }
      }
    }
  },
  "description": "Raise a fraud alert on a customer's activity with an indicator, severity and a description of the flagged behaviour. Detection normally writes these; a human escalation is usually a fraud case instead."
}

GET /fraud_alerts/{id}

Get fraud alert

Fetch one fraud alert by id with its indicator, severity, score, flagged activity and triage status.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get fraud alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudAlert"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one fraud alert by id with its indicator, severity, score, flagged activity and triage status."
}

PATCH /fraud_alerts/{id}

Update fraud alert

Partially update fraud alert metadata such as notes or score. Use the triage tool to set triaged/dismissed/escalated — it enforces one-time triage and links the resulting case.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update fraud alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/FraudAlert"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudAlert"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Partially update fraud alert metadata such as notes or score. Use the triage tool to set triaged/dismissed/escalated — it enforces one-time triage and links the resulting case."
}

DELETE /fraud_alerts/{id}

Delete fraud alert

Permanently delete a fraud alert. Destructive and irreversible — dismiss it via triage instead, so the decision not to act is recorded.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete fraud alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a fraud alert. Destructive and irreversible — dismiss it via triage instead, so the decision not to act is recorded."
}

POST /fraud_alerts/{id}/triage

Triage a fraud alert

Decide what happens to a fraud alert: triaged (reviewed, no further action), dismissed (false positive) or escalated — passing case_id links it to the fraud case that will investigate. One-way: an alert that has already been triaged, dismissed or escalated is rejected with 422.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Triage a fraud alert",
  "description": "Decide what happens to a fraud alert: triaged (reviewed, no further action), dismissed (false positive) or escalated — passing case_id links it to the fraud case that will investigate. One-way: an alert that has already been triaged, dismissed or escalated is rejected with 422.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "triaged",
                "dismissed",
                "escalated"
              ]
            },
            "case_id": {
              "type": "string"
            }
          },
          "required": [
            "status"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudAlert"
          }
        }
      }
    },
    "400": {
      "description": "Invalid status"
    },
    "404": {
      "description": "Not found"
    },
    "422": {
      "description": "Alert already triaged/dismissed/escalated"
    }
  }
}

GET /fraud_cases

List fraud cases

Fraud cases are the human investigations opened off one or more fraud alerts, with an assigned investigator, risk indicators, status open | investigating | closed and a disposition (confirmed_fraud | false_positive | pending). Cases are the investigation; alerts are the raw signals. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List fraud cases",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "customer_id",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Filter by customer id"
    },
    {
      "in": "query",
      "name": "status",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "open",
          "investigating",
          "closed"
        ]
      },
      "description": "Filter by status"
    },
    {
      "in": "query",
      "name": "disposition",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "confirmed_fraud",
          "false_positive",
          "pending"
        ]
      },
      "description": "Filter by disposition"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FraudCase"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "Fraud cases are the human investigations opened off one or more fraud alerts, with an assigned investigator, risk indicators, status open | investigating | closed and a disposition (confirmed_fraud | false_positive | pending). Cases are the investigation; alerts are the raw signals. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /fraud_cases

Open fraud case

Open a fraud investigation case for a customer. If it stems from an alert, escalating that alert via the triage tool links the two — prefer that route.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Open fraud case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/FraudCase"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudCase"
          }
        }
      }
    }
  },
  "description": "Open a fraud investigation case for a customer. If it stems from an alert, escalating that alert via the triage tool links the two — prefer that route."
}

GET /fraud_cases/{id}

Get fraud case

Fetch one fraud case by id with its status, disposition, assigned investigator, risk indicators and notes.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get fraud case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudCase"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one fraud case by id with its status, disposition, assigned investigator, risk indicators and notes."
}

PATCH /fraud_cases/{id}

Update fraud case

Partially update fraud case details such as investigator, risk indicators or notes. Use the transition tool to change status or close with a disposition — closure is enforced there and is irreversible.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update fraud case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/FraudCase"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudCase"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Partially update fraud case details such as investigator, risk indicators or notes. Use the transition tool to change status or close with a disposition — closure is enforced there and is irreversible."
}

DELETE /fraud_cases/{id}

Delete fraud case

Permanently delete a fraud investigation case. Destructive and irreversible, and it destroys the investigation record — close it with a disposition instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete fraud case",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a fraud investigation case. Destructive and irreversible, and it destroys the investigation record — close it with a disposition instead."
}

POST /fraud_cases/{id}/transition

Transition a fraud case

Move a fraud investigation through open → investigating → closed, with an optional note. Closing requires a disposition (confirmed_fraud | false_positive | pending) and is irreversible — a closed case cannot be reopened, so use it only when the investigation is genuinely finished.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Transition a fraud case",
  "description": "Move a fraud investigation through open → investigating → closed, with an optional note. Closing requires a disposition (confirmed_fraud | false_positive | pending) and is irreversible — a closed case cannot be reopened, so use it only when the investigation is genuinely finished.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "open",
                "investigating",
                "closed"
              ]
            },
            "disposition": {
              "type": "string",
              "enum": [
                "confirmed_fraud",
                "false_positive",
                "pending"
              ]
            },
            "note": {
              "type": "string"
            }
          },
          "required": [
            "status"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/FraudCase"
          }
        }
      }
    },
    "400": {
      "description": "Invalid status"
    },
    "404": {
      "description": "Not found"
    },
    "422": {
      "description": "Invalid transition / disposition required to close"
    }
  }
}

GET /watchlist

List AML watchlist entries

AML screening hits: sanctions, PEP and internal blocklist entries with the matched name, country, list type and match strength, optionally linked to a customer_id. These are screening matches to adjudicate, not blocked cards or frozen accounts. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List AML watchlist entries",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "customer_id",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Filter by customer id"
    },
    {
      "in": "query",
      "name": "list_type",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "sanctions",
          "pep",
          "internal_blocklist"
        ]
      },
      "description": "Filter by list type"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WatchlistEntry"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "AML screening hits: sanctions, PEP and internal blocklist entries with the matched name, country, list type and match strength, optionally linked to a customer_id. These are screening matches to adjudicate, not blocked cards or frozen accounts. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /watchlist

Create watchlist entry

Record an AML watchlist hit (sanctions, PEP or internal blocklist) with the matched name and match strength, optionally tied to a customer.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create watchlist entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/WatchlistEntry"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WatchlistEntry"
          }
        }
      }
    }
  },
  "description": "Record an AML watchlist hit (sanctions, PEP or internal blocklist) with the matched name and match strength, optionally tied to a customer."
}

GET /watchlist/{id}

Get watchlist entry

Fetch one watchlist entry by id with its list type, matched name, country and match strength — the evidence behind a sanctions or PEP flag.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get watchlist entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WatchlistEntry"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one watchlist entry by id with its list type, matched name, country and match strength — the evidence behind a sanctions or PEP flag."
}

PATCH /watchlist/{id}

Update watchlist entry

Partially update a watchlist entry — revise the match strength, or attach it to the customer it actually matches after adjudication.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update watchlist entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/WatchlistEntry"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WatchlistEntry"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Partially update a watchlist entry — revise the match strength, or attach it to the customer it actually matches after adjudication."
}

DELETE /watchlist/{id}

Delete watchlist entry

Permanently delete a watchlist entry, e.g. after a false-positive adjudication. Destructive and irreversible, and it removes regulated screening evidence.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete watchlist entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a watchlist entry, e.g. after a false-positive adjudication. Destructive and irreversible, and it removes regulated screening evidence."
}

GET /policies

List compliance policies and SOPs

The compliance knowledge base: policies, procedures, SOPs and customer scripts by category (KYC, payments, lending, branch, fraud) with the owning regulator and effective dates. Use these for 'what does the rule say / what do I tell the customer' questions; IT knowledge articles live in Purple ITSM. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List compliance policies and SOPs",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Max items per page (default 50)"
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Pass the previous response's nextCursor to fetch the next page"
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    },
    {
      "in": "query",
      "name": "country",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "IN",
          "AU",
          "US",
          "GB",
          "KR",
          "DE"
        ]
      },
      "description": "Filter to a single country (IN | AU | US)"
    },
    {
      "in": "query",
      "name": "policy_type",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "SOP",
          "policy",
          "procedure",
          "customer_script"
        ]
      },
      "description": "Filter by policy type"
    },
    {
      "in": "query",
      "name": "category",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "KYC",
          "payments",
          "lending",
          "branch",
          "fraud"
        ]
      },
      "description": "Filter by category"
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Policy"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "description": "The compliance knowledge base: policies, procedures, SOPs and customer scripts by category (KYC, payments, lending, branch, fraud) with the owning regulator and effective dates. Use these for 'what does the rule say / what do I tell the customer' questions; IT knowledge articles live in Purple ITSM. Narrow results server-side with this endpoint's named filter params (country, customer_id, status, …) or with OData $filter, $orderby, $select, $search, $top, $skip and $count (MCP clients send those as _filter, _orderby, …) instead of paging through everything."
}

POST /policies

Create compliance policy

Publish a new compliance policy, procedure, SOP or customer script with its category, regulator and effective date.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create compliance policy",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Policy"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Policy"
          }
        }
      }
    }
  },
  "description": "Publish a new compliance policy, procedure, SOP or customer script with its category, regulator and effective date."
}

GET /policies/{id}

Get compliance policy

Fetch one policy, SOP or customer script by id with its full text, category, regulator and effective dates — the citation for a compliance answer.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get compliance policy",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Policy"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Fetch one policy, SOP or customer script by id with its full text, category, regulator and effective dates — the citation for a compliance answer."
}

PATCH /policies/{id}

Update compliance policy

Update a policy or SOP — revise its text, effective dates or owning regulator when the rule changes.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update compliance policy",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Policy"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Policy"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Update a policy or SOP — revise its text, effective dates or owning regulator when the rule changes."
}

DELETE /policies/{id}

Delete compliance policy

Permanently delete a policy or SOP from the compliance knowledge base. Destructive and irreversible — supersede it with an updated version instead.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete compliance policy",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              },
              "id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  },
  "description": "Permanently delete a policy or SOP from the compliance knowledge base. Destructive and irreversible — supersede it with an updated version instead."
}

Schemas and reusable components

securitySchemes

patAuth

{
  "type": "http",
  "scheme": "bearer"
}

parameters

InstanceId

{
  "in": "header",
  "name": "X-Instance-ID",
  "required": true,
  "schema": {
    "type": "string"
  }
}

ImpersonateUser

{
  "in": "header",
  "name": "X-PS-Impersonate-User",
  "required": false,
  "schema": {
    "type": "string",
    "format": "email"
  },
  "description": "Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."
}

schemas

KycProfile

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "KYC-0001"
    },
    "customer_id": {
      "type": "string",
      "example": "CUST-0001"
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "verified",
        "pending",
        "expired",
        "in_remediation"
      ],
      "example": "pending"
    },
    "risk_level": {
      "type": "string",
      "enum": [
        "low",
        "medium",
        "high"
      ],
      "example": "medium"
    },
    "last_reviewed_at": {
      "type": "string",
      "format": "date-time"
    },
    "next_review_due": {
      "type": "string",
      "format": "date"
    },
    "pep_flag": {
      "type": "boolean",
      "example": false
    },
    "documents": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "doc_type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "valid",
              "expired",
              "missing",
              "pending_review"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        }
      }
    },
    "outstanding_items": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

RemediationCase

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "RMD-0001"
    },
    "customer_id": {
      "type": "string",
      "example": "CUST-0001"
    },
    "kyc_profile_id": {
      "type": "string"
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "reason": {
      "type": "string",
      "enum": [
        "expired_doc",
        "re_kyc_nri",
        "sanctions_hit",
        "address_mismatch"
      ],
      "example": "re_kyc_nri"
    },
    "status": {
      "type": "string",
      "enum": [
        "open",
        "in_progress",
        "pending_customer",
        "completed",
        "closed"
      ],
      "example": "open"
    },
    "assigned_to": {
      "type": "string",
      "format": "email"
    },
    "due_date": {
      "type": "string",
      "format": "date"
    },
    "actions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string"
          },
          "owner": {
            "type": "string",
            "nullable": true
          },
          "due": {
            "type": "string",
            "nullable": true
          },
          "done": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "status_history": {
      "type": "array",
      "readOnly": true,
      "items": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

FraudAlert

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "FRAUD-0001"
    },
    "customer_id": {
      "type": "string",
      "example": "CUST-0001"
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "indicator": {
      "type": "string",
      "enum": [
        "velocity",
        "geo_anomaly",
        "unauthorized_upi",
        "sim_swap",
        "mule_pattern"
      ],
      "example": "unauthorized_upi"
    },
    "severity": {
      "type": "string",
      "enum": [
        "low",
        "medium",
        "high",
        "critical"
      ],
      "example": "high"
    },
    "status": {
      "type": "string",
      "enum": [
        "new",
        "triaged",
        "dismissed",
        "escalated"
      ],
      "example": "new"
    },
    "score": {
      "type": "integer",
      "example": 82
    },
    "flagged_activity": {
      "type": "string"
    },
    "case_id": {
      "type": "string",
      "nullable": true
    },
    "detected_at": {
      "type": "string",
      "format": "date-time"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

FraudCase

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "FC-0001"
    },
    "customer_id": {
      "type": "string",
      "example": "CUST-0001"
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "alert_ids": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "status": {
      "type": "string",
      "enum": [
        "open",
        "investigating",
        "closed"
      ],
      "example": "open"
    },
    "disposition": {
      "type": "string",
      "enum": [
        "confirmed_fraud",
        "false_positive",
        "pending"
      ],
      "nullable": true
    },
    "risk_indicators": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "assigned_investigator": {
      "type": "string",
      "format": "email"
    },
    "case_notes": {
      "type": "array",
      "readOnly": true,
      "items": {
        "type": "object",
        "properties": {
          "note": {
            "type": "string"
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "status_history": {
      "type": "array",
      "readOnly": true,
      "items": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

WatchlistEntry

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "WL-001"
    },
    "customer_id": {
      "type": "string",
      "nullable": true
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "name": {
      "type": "string"
    },
    "list_type": {
      "type": "string",
      "enum": [
        "sanctions",
        "pep",
        "internal_blocklist"
      ],
      "example": "pep"
    },
    "match_strength": {
      "type": "integer",
      "example": 78
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Policy

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true,
      "example": "POL-001"
    },
    "country": {
      "type": "string",
      "enum": [
        "IN",
        "AU",
        "US",
        "GB",
        "KR",
        "DE"
      ]
    },
    "title": {
      "type": "string",
      "example": "Re-KYC procedure for NRI accounts"
    },
    "policy_type": {
      "type": "string",
      "enum": [
        "SOP",
        "policy",
        "procedure",
        "customer_script"
      ],
      "example": "procedure"
    },
    "category": {
      "type": "string",
      "enum": [
        "KYC",
        "payments",
        "lending",
        "branch",
        "fraud"
      ],
      "example": "KYC"
    },
    "body": {
      "type": "string"
    },
    "customer_script": {
      "type": "string",
      "description": "Verbatim text the frontline staff can read to the customer"
    },
    "regulator": {
      "type": "string",
      "example": "RBI"
    },
    "effective_date": {
      "type": "string",
      "format": "date"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Document metadata and extensions

{
  "openapi": "3.0.0",
  "info": {
    "title": "Purple Risk & Compliance API",
    "version": "1.0.0",
    "description": "BFSI risk & compliance source system: KYC/CDD profiles & remediation, fraud alerts & investigation cases, AML watchlist, and policy/SOP knowledge. Customer-linked records share the retail-banking `customer_id` (CUST-####). Records carry a `country` (IN | AU | US); narrow any list with the `country` filter.\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`."
  },
  "security": [
    {
      "patAuth": []
    }
  ]
}
Interactive API explorer (requires JavaScript)