Skip to content

Rules and Thresholds

Compliance rules decide what happens to a transfer once it is screened: flag it, hold it for review, or block it. Regulatory thresholds decide, per jurisdiction, when the Travel Rule applies and when a suspicion report is due. Both are also managed in the dashboard, under Rules.

Method Endpoint Scope Description
GET /api/v2/rules rules:read List rules
POST /api/v2/rules rules:write Create a rule
GET /api/v2/rules/:id rules:read Get a rule, with its conditions
PATCH /api/v2/rules/:id rules:write Change a rule
DELETE /api/v2/rules/:id rules:write Deactivate a rule
GET /api/v2/thresholds thresholds:read List thresholds
POST /api/v2/thresholds thresholds:write Create a threshold (live key)
GET /api/v2/thresholds/:id thresholds:read Get a threshold
PATCH /api/v2/thresholds/:id thresholds:write Change a threshold (live key)

Rules belong to the key's environment. The key's owner's role needs rules:view to read, rules:create to create, and rules:update to change or delete. Promoting test rules to live is a dashboard action: the API has no route for it.

Terminal window
curl "$COVALENT_URL/api/v2/rules?category=aml&isActive=true" \
-H "x-api-key: $COVALENT_API_KEY"
Parameter Description
category aml or sanctions.
isActive true or false.
severity low, medium or high.
stage precheck, postcheck or both.
search Part of the rule's name or description, any case, at most 200 characters.
limit Items per page, 1 to 100, default 50.
cursor meta.cursor of the previous page.

For a choice filter, an empty value or all means no filter. A value the list cannot read is 400 INVALID_QUERY, whose details.issues lists each problem as { "path", "message" }. Rules are listed newest first:

{
"id": "rule_id",
"name": "Large transfers",
"description": null,
"category": "aml",
"stage": "both",
"action": "require_review",
"severity": "high",
"isActive": true,
"threshold": 10000,
"thresholdCurrency": "USD",
"jurisdictions": ["GB", "EU"],
"asset": null,
"network": null,
"thresholdOperator": "gt",
"conditionCount": 1,
"environment": "live",
"createdAt": "2026-10-09T12:00:00.000Z",
"updatedAt": "2026-10-09T12:00:00.000Z"
}

GET /api/v2/rules/:id adds createdBy, conditions (each branch, with the integration's name) and hasLegacyConditions. A rule of the other environment is 404 RULE_NOT_FOUND.

Terminal window
curl -X POST "$COVALENT_URL/api/v2/rules" \
-H "x-api-key: $COVALENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Large transfers",
"category": "aml",
"action": "require_review",
"severity": "high",
"jurisdictions": ["GB", "EU"],
"condition": {
"rules": [
{
"type": "if",
"field": "amountUSD",
"operator": "gte",
"value": "10000",
"action": "require_review"
}
]
}
}'
Field Required Description
name Yes 1 to 200 characters.
category Yes aml or sanctions.
stage No precheck, postcheck or both (the default).
action No flag (the default: record and continue), require_review (hold for review) or block.
severity No low, medium (the default) or high.
isActive No true (the default) or false.
description No At most 500 characters, or null.
jurisdictions No Up to 50 ISO 3166-1 codes, EU, or GLOBAL alone (the rule applies everywhere).
threshold No A number from 0, or null.
thresholdCurrency No A fiat currency code, or null.
metadata No thresholdOperator (lt, eq or gt), asset and network (a code, or all), and nothing else; or null.
condition No The rule's branches, or null. See below.

No other field is accepted. The answer is 201 with the rule and its conditions.

condition.rules holds up to 20 branches, in order: one if first, then any elseIf, then at most one else, last. Every branch has an action (flag, require_review or block). A branch other than else compares either:

  • a field: a transfer attribute (amount, amountUSD, amountLocal, asset, network, jurisdiction, direction, walletType, originatorName, beneficiaryName, originatorAddress, beneficiaryAddress), or, with an integrationId, a dotted path into that integration's response; or
  • a query: a JSON response query into the response of the integration named by integrationId. A query branch needs integrationId, operator and value.

operator is eq, neq, contains, gt, gte, lt or lte, and value is text of at most 100 characters. integrationId is the ID of one of your company's enabled integrations: any other is refused, on the path condition.rules.<n>.integrationId. A branch names its integration with integrationId only; a providerId is not read, so a query branch with providerId and no integrationId is refused.

PATCH /api/v2/rules/:id takes any of the create fields, isActive included, and at least one. The answer is the rule.

DELETE /api/v2/rules/:id deactivates the rule and keeps it, for its cases and its audit trail. The answer is the rule, with isActive: false.

A body that fails validation is 422 VALIDATION_ERROR. Its details.issues lists each problem as { "path", "message" }:

{
"error": "Invalid request body",
"code": "VALIDATION_ERROR",
"details": {
"issues": [
{ "path": "condition.rules.0", "message": "JSON response conditions require an integration, operator, and value" }
]
},
"meta": {
"apiVersion": 2,
"timestamp": "2026-10-09T12:00:00.000Z"
}
}

Thresholds belong to your company, not to an environment: one set applies to test and live alike. Any key with thresholds:read reads them, but only a live key changes them (403 LIVE_KEY_REQUIRED), and every change is recorded in the live audit log. The key's owner's role needs thresholds:view, thresholds:create or thresholds:update; switching an active threshold off also needs thresholds:disable.

While your company has no active threshold, built-in FATF fallbacks apply. Its first active threshold ends them.

Terminal window
curl "$COVALENT_URL/api/v2/thresholds?type=tr" \
-H "x-api-key: $COVALENT_API_KEY"
Parameter Description
type tr (Travel Rule) or str (suspicious transaction reporting).
status active or inactive.
search Part of the jurisdiction code, name, regulation or regulatory body, any case. At most 100 characters.
limit Items per page, 1 to 100, default 50.
cursor meta.cursor of the previous page: a threshold's ID.

Thresholds are listed by jurisdiction, then type. meta adds activeCount: how many thresholds are active in all, whatever the filters (0 means the fallbacks apply).

{
"id": "threshold_id",
"countryCode": "GB",
"type": "tr",
"name": "UK Travel Rule",
"thresholdAmount": "1000",
"thresholdCurrency": "GBP",
"regulation": "UK MLRs 2017",
"regulatoryBody": "FCA",
"effectiveDate": null,
"filingDeadlineDays": null,
"isEUMember": false,
"requiresOriginatorAccount": true,
"isActive": true,
"notes": null,
"createdAt": "2026-10-09T12:00:00.000Z",
"updatedAt": "2026-10-09T12:00:00.000Z",
"createdBy": "user_id",
"updatedBy": "user_id"
}
Field Required Description
countryCode Yes An ISO 3166-1 alpha-2 code, EU, or DEFAULT (the fallback for any other jurisdiction).
type Yes tr or str.
name Yes 1 to 200 characters.
thresholdAmount Yes A decimal of at least 0, as a number or a string, with at most 8 decimals.
thresholdCurrency No An ISO 4217 code, default USD.
regulation Yes 1 to 200 characters.
regulatoryBody No At most 200 characters.
effectiveDate No A day or an ISO 8601 instant; null means in effect already.
filingDeadlineDays No str thresholds only: the days a report must be filed within, 1 to 365.
isEUMember No Default false.
requiresOriginatorAccount No Default true.
isActive No Default true.
notes No At most 2,000 characters.

No other field is accepted. The answer is 201 with the threshold. A jurisdiction has at most one threshold of each type: another is 409 THRESHOLD_EXISTS.

PATCH /api/v2/thresholds/:id takes any field but the jurisdiction and type, which are fixed: sending another countryCode or type is 422 IMMUTABLE_FIELD. Setting isActive: false on an active threshold needs the role's thresholds:disable. The answer is the threshold.