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

# Managing Rules via API and MCP

> Create, tune, backtest, and migrate KYT rules programmatically: the Management API endpoints, the MCP tools, and the create - backtest - activate workflow.

Every rule capability of the Business Console is also available programmatically: through the **Management API** with your application API key, and through the **Didit MCP** for AI agents acting as a signed-in console user. Use them to manage rules from code, keep rule sets in version control, or let an AI assistant build and migrate rules for you.

## When to use which surface

| Surface | Auth | Best for |
| - | - | - |
| **Business Console** | Console login | Human review of every change; visual rule building and backtesting |
| **Management API** | `x-api-key` | Automation from your backend, rule sets in code, CI-managed rule deployments |
| **Didit MCP** | Sign in with Didit (OAuth) | AI assistants and agents: the Didit console Copilot, Claude, Cursor, or any MCP client |

## The endpoints

| Endpoint | Purpose |
| - | - |
| [`GET /v3/transactions/rules/`](/management-api/transactions/rules-list) | List rules with filters and performance ordering |
| [`POST /v3/transactions/rules/`](/management-api/transactions/rules-create) | Create a custom rule |
| [`GET /v3/transactions/rules/{rule_uuid}/`](/management-api/transactions/rules-get) | Read one rule with execution counters |
| [`PATCH /v3/transactions/rules/{rule_uuid}/`](/management-api/transactions/rules-update) | Update a custom rule; switch any rule's mode |
| [`DELETE /v3/transactions/rules/{rule_uuid}/`](/management-api/transactions/rules-delete) | Delete a custom rule (permanent) |
| [`POST /v3/transactions/rules/backtest/`](/management-api/transactions/rules-backtest) | Evaluate a hypothetical rule against your history |
| [`GET /v3/transactions/rules/library/`](/management-api/transactions/rules-library) | Browse the 150+ preset library with install status |
| [`POST /v3/transactions/rules/install/`](/management-api/transactions/rules-install) | Install presets by key, bundle, or the intersection of both |
| [`DELETE /v3/transactions/rules/install/`](/management-api/transactions/rules-uninstall) | Uninstall presets by key, bundle, or the intersection of both |

The matching MCP tools are `didit_transaction_rule_list`, `didit_transaction_rule_get`, `didit_transaction_rule_create`, `didit_transaction_rule_update`, `didit_transaction_rule_delete`, `didit_transaction_rule_backtest`, `didit_transaction_rule_library_list`, `didit_transaction_rule_install`, and `didit_transaction_rule_uninstall` - see the [MCP tool reference](/integration/mcp/tools).

The full schema - the field catalog, all 19 condition operators, velocity windows, grouped conditions, and the six action types - is documented on the [Rules & Scoring](/transaction-monitoring/rules) page and embedded in the API reference.

## Create, backtest, activate

The safe rollout pattern for any new rule:

<Steps>
  <Step title="Create the rule in TEST mode">
    ```bash theme={null}
    curl -X POST 'https://verification.didit.me/v3/transactions/rules/' \
      -H 'x-api-key: YOUR_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{
        "title": "Structuring - inbound (custom)",
        "description": "20+ inbound EUR transfers, all under 10,000 EUR, 30 days",
        "category": "finance",
        "mode": "TEST",
        "evaluation_mode": "ALL",
        "scope": {
          "transaction_types": ["finance"],
          "directions": ["INBOUND"]
        },
        "conditions": [
          {"field": "currency", "operator": "eq", "value": "EUR"},
          {"field": "amount",
           "operator": "lt", "value": 10000}
        ],
        "aggregation": [
          {
            "metric": "count", "operator": "gte", "value": 20,
            "window": "30d",
            "filters": {"direction": "INBOUND",
                        "transaction_type": "finance",
                        "currency": "EUR",
                        "subject_vendor_data": "__current__"}
          },
          {
            "metric": "max", "field": "amount",
            "operator": "lt", "value": 10000, "window": "30d",
            "filters": {"direction": "INBOUND",
                        "transaction_type": "finance",
                        "currency": "EUR",
                        "subject_vendor_data": "__current__"}
          }
        ],
        "actions": [{"type": "add_score", "value": 35}]
      }'
    ```

    Read the payload in two halves. `scope` and `conditions` describe **the transaction being evaluated**: an inbound finance transaction in EUR with a raw `amount` below 10,000. The `aggregation` entries describe **the 30-day window for the same subject**: at least 20 inbound EUR finance transactions, and none of them 10,000 EUR or more. Both aggregation entries must hold, whatever `evaluation_mode` says - that setting only governs conditions.

    This example uses raw `amount` and pins `currency` to EUR on both halves, so it does not depend on FX normalization. To backtest sandbox fixtures, submit at least 20 inbound EUR finance transactions for the same subject within 30 days, each with `amount` below 10,000. Neither `preferred_currency_amount` nor `amount_in_default_currency` is required. Sandbox ingestion stores these transactions; run the backtest explicitly.

    A `TEST` rule evaluates on every new transaction and records what it would have matched, but never touches the transaction's score or status.

    <Note>
      **An aggregation filter is not a condition.** A condition only ever reads the transaction being evaluated; an aggregation filter only ever narrows the historical window, and it compares by equality or list membership - there is no `lt` filter, so you cannot count *only the transfers under 10,000*.

      The `max ... lt 10000` entry is the supported way to put a ceiling on the window: it requires **every** transaction in the window to be under 10,000, which is stricter than counting just the small ones - one 12,000 transfer among the twenty stops the rule from firing. Drop that entry if you would rather the count stood on its own, and then say so in the title: without it the rule counts inbound transfers of any size, and nineteen 50,000 transfers followed by one small one match it.
    </Note>
  </Step>

  <Step title="Backtest against your history">
    ```bash theme={null}
    curl -X POST 'https://verification.didit.me/v3/transactions/rules/backtest/' \
      -H 'x-api-key: YOUR_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{
        "conditions": [
          {"field": "currency", "operator": "eq", "value": "EUR"},
          {"field": "amount",
           "operator": "lt", "value": 10000}
        ],
        "aggregation": [
          {
            "metric": "count", "operator": "gte", "value": 20,
            "window": "30d",
            "filters": {"direction": "INBOUND",
                        "transaction_type": "finance",
                        "currency": "EUR",
                        "subject_vendor_data": "__current__"}
          },
          {
            "metric": "max", "field": "amount",
            "operator": "lt", "value": 10000, "window": "30d",
            "filters": {"direction": "INBOUND",
                        "transaction_type": "finance",
                        "currency": "EUR",
                        "subject_vendor_data": "__current__"}
          }
        ],
        "evaluation_mode": "ALL",
        "scope": {
          "transaction_types": ["finance"],
          "directions": ["INBOUND"]
        },
        "period_days": 90
      }'
    ```

    ```json theme={null}
    { "evaluated": 4820, "matched": 12, "affected_entities": 4, "period_days": 90 }
    ```

    A surprising `matched` count means the thresholds need tuning before the rule can act on anything.

    <Warning>
      Backtest the **exact** configuration you are about to activate: the same `conditions`, `aggregation`, `evaluation_mode` and `scope`. Omitted `conditions`, `aggregation` and `scope` add no constraints of their own; dropping the scope can include directions or transaction types the saved rule skips. Omitted `evaluation_mode` defaults to `ALL`, so omitting it for an `ANY` rule can reduce matches. Omitted `period_days` defaults to 90 days; it does not select unlimited history.
    </Warning>
  </Step>

  <Step title="Activate">
    ```bash theme={null}
    curl -X PATCH 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \
      -H 'x-api-key: YOUR_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{"mode": "ACTIVE"}'
    ```

    The rule applies to all new transactions of a **live** application immediately.

    <Warning>
      Sandbox applications never evaluate rules. A sandbox transaction is stored and
      nothing more, so activating a rule on a sandbox application changes nothing you
      can observe. Backtest is the supported way to exercise a rule before it goes
      live - see [Transaction Monitoring in sandbox](/integration/sandbox-testing#transaction-monitoring-in-sandbox).
    </Warning>
  </Step>
</Steps>

## Migrating rules from another provider

Because rule creation is a plain API call with a fully documented schema, an AI assistant can translate another provider's rule export into Didit rules for you.

1. **Export** your rules from the current provider - most tools export to CSV or Excel.
2. **Drop the file** into the Didit console assistant (or any MCP-connected agent like Claude with the [Didit MCP](/integration/mcp/overview) attached). Spreadsheets are converted to text automatically.
3. The assistant **maps each row** to the Didit schema - conditions, velocity windows, actions - and shows you the mapping, calling out anything that has no Didit equivalent, before creating anything.
4. On your go-ahead it **creates the rules in TEST mode**, backtests them against your own transaction history, and reports created / failed / unmappable rows.
5. **Review the numbers and activate** the rules that look right, from the console or with a `PATCH {"mode": "ACTIVE"}`.

A provider export like this:

```csv theme={null}
Rule Name,Condition,Threshold,Window (days),Action
Structuring - Small Transfers,count of outbound transfers under amount,20 transfers under 10000 EUR,30,Flag for review
High Value Wire,single transaction amount,> 50000 USD,,Decline
```

becomes rules like this:

```json theme={null}
{
  "title": "Structuring - Small Transfers",
  "description": "20+ outbound EUR transfers, all under 10,000 EUR, 30 days",
  "category": "finance",
  "mode": "TEST",
  "evaluation_mode": "ALL",
  "scope": {
    "transaction_types": ["finance"],
    "directions": ["OUTBOUND"]
  },
  "conditions": [
    {"field": "currency", "operator": "eq", "value": "EUR"},
    {"field": "amount", "operator": "lt", "value": 10000}
  ],
  "aggregation": [
    {
      "metric": "count", "operator": "gte", "value": 20,
      "window": "30d",
      "filters": {"direction": "OUTBOUND",
                  "transaction_type": "finance",
                  "currency": "EUR",
                  "subject_vendor_data": "__current__"}
    },
    {
      "metric": "max", "field": "amount",
      "operator": "lt", "value": 10000, "window": "30d",
      "filters": {"direction": "OUTBOUND",
                  "transaction_type": "finance",
                  "currency": "EUR",
                  "subject_vendor_data": "__current__"}
    }
  ],
  "actions": [
    {"type": "add_score", "value": 35},
    {"type": "change_status", "value": "IN_REVIEW"}
  ]
}
```

The CSV row names a direction and a currency, so both halves of the rule carry them: `scope` and `conditions` pin the transaction being evaluated to an outbound EUR transfer under 10,000, and the aggregation filters pin the 30-day window to that same subject's outbound EUR transfers. Raw `amount` is only comparable inside one currency - keep the currency pinned as above. A cross-currency ceiling requires a normalized amount for every transaction in the window. Switching to `preferred_currency_amount` alone does not enforce availability: missing historical normalized amounts can be treated as zero by numeric aggregations.

The `max` entry is what keeps the window below 10,000 EUR, because an aggregation filter cannot express *under 10,000* - see the note on the [create step](#create-backtest-activate) above for the trade-off it makes. Anything the schema cannot express exactly is worth saying out loud in the rule's `description` rather than approximating silently.

<Tip>
  Before recreating a provider's rule as custom, check the [rule library](/management-api/transactions/rules-library): Didit ships 150+ presets covering the common typologies (structuring, velocity, sanctions exposure, device reuse, crypto risk), and installing a preset is one call.
</Tip>

## Validation errors are precise on purpose

The create and update endpoints validate conditions, aggregation, and scope values against exactly what the rule engine accepts: unknown operators or metrics, malformed windows (`"30days"` instead of `"30d"`), invalid scope directions, `contains_any` without a list, `fuzzy_match` without a `score`, and `change_status` to `AWAITING_USER` without a `workflow_id` all return a `400` naming the index and the problem - instead of storing a rule that silently never matches. Agents and scripts should read the error body and fix the payload. Extra scope keys beyond `transaction_types`, `directions`, and `action_types` are stored but ignored by the engine, matching how the Business Console has always saved them.

## Next steps

<CardGroup cols={2}>
  <Card title="Rules & Scoring" icon="sliders" href="/transaction-monitoring/rules">
    The full rule schema: fields, operators, velocity windows, actions.
  </Card>

  <Card title="Create Rule API" icon="code" href="/management-api/transactions/rules-create">
    Endpoint reference with request and response schemas.
  </Card>

  <Card title="Rules library" icon="book" href="/transaction-monitoring/rules-library">
    The 150+ preset catalogue and its bundles.
  </Card>

  <Card title="MCP overview" icon="plug" href="/integration/mcp/overview">
    Connect an AI agent to your Didit workspace.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.