Aller au contenu

Rules API

Ce contenu n’est pas encore disponible dans votre langue.

These endpoints manage the rules described in Managing rules. The web app’s Rules screens use them. Most people never call them directly; this page is for teams that script rule changes, export rule definitions for review, or check behaviour precisely.

Who can call them. Rule management is for signed-in people, not API keys. Every change is attributed to a named user, checked against their permissions and the four-eyes rule, and written to the audit log. An API key gets 401 sign in required.

Signing in with curl. Sign in once and keep the session cookie in a file:

Terminal window
export SP=https://speculapro.example.com
curl -s -c ~/.sp-cookies -H 'Content-Type: application/json' \
-d '{"workspace":"acme-pay","email":"[email protected]","password":"<your password>","totp_code":"123456"}' \
"$SP/api/v1/auth/login"

Then add -b ~/.sp-cookies to every request below. The session follows the usual rules: it ends after 30 minutes without activity, or 12 hours at most.

Conventions. IDs are UUIDs. Times are RFC 3339 in UTC. Bodies are JSON with Content-Type: application/json. Amounts inside rule parameters are in major units of your base currency (50000 is GHS 50,000), because that is how rules compare them.

Endpoint Does Permission Success
GET /api/v1/rule-packs List the packs available to your installation and which are installed rules.view 200
POST /api/v1/rule-packs/{key}/install Install a pack; its rules arrive stopped rules.approve 200 {"added": n}
POST /api/v1/rule-packs/{key}/upgrade Move an installed pack to the newest available version: new rules arrive stopped, changed rules get a new version for approval rules.approve 200 with the upgrade report
POST /api/v1/rule-packs/bundles Load a signed pack file, through the same upgrade path as /upgrade rules.approve (forcing a downgrade also needs rules.packs_downgrade) 200
GET /api/v1/rules/catalog Every field a rule can read, the aggregates it can compute and the actions per mode rules.view 200
POST /api/v1/rules/test Try a definition on a sample transaction, without saving rules.view 200
GET /api/v1/rules List rules with their live and shadow versions and 30-day hit counts rules.view 200
GET /api/v1/rules/{id} One rule with its full version history rules.view 200
POST /api/v1/rules Create a custom rule, as draft version 1 rules.author 201 {"id": …}
POST /api/v1/rules/validate Check a definition without saving it rules.author 200 {"valid": …, "problems": […]}
POST /api/v1/rules/{id}/versions Draft a new version rules.author 201 {"id": …}
POST /api/v1/rule-versions/{id}/submit Send a draft for approval rules.author 204
POST /api/v1/rule-versions/{id}/approve Approve (never your own version) rules.approve 204
POST /api/v1/rule-versions/{id}/reject Reject rules.approve 204
POST /api/v1/rules/{id}/deploy Put an approved version in the active (live) or shadow slot rules.approve 204
DELETE /api/v1/rules/{id}/shadow Stop the shadow test rules.approve 204
POST /api/v1/rules/{id}/enable Start running the rule rules.approve 204
POST /api/v1/rules/{id}/disable Stop running the rule rules.approve 204
GET /api/v1/rules/{id}/runs A scheduled rule’s schedule, next run and latest runs rules.view 200
PUT /api/v1/rules/{id}/schedule Change when a scheduled rule runs rules.approve 204

Errors use Problem Details: a JSON object with status, title and detail, plus an errors list for validation problems.

Status Meaning Examples
401 Not signed in, or called with an API key sign in required
403 Missing permission, approving your own version, or (self-hosted) installing, upgrading or loading a pack, or creating a rule or version, while the licence is restricted a version must be approved by someone other than its author; only an administrator can force a downgrade
404 No such rule, version or pack in your workspace unknown rule pack "…"
409 The action doesn’t fit the current state pack gh-psp is not installed (upgrade); pack core 1.2.0 is installed; the newest available is 1.1.0 (upgrade); only draft versions can be submitted; only versions pending approval can be decided; only approved versions can be deployed (this one is draft); version belongs to another rule; rule not found or has no approved active version
422 The definition or request is invalid. Every problem is listed, not just the first features.out_7d is used but not declared; explanation {{= no_such_var }}: ERROR: <input>:1:1: undeclared reference to 'no_such_var' …; params.threshold is used but has no value; action "block" isn't available to post-event rules (use alert, tag, score_only)

A refused request changes nothing.

Terminal window
curl -s -b ~/.sp-cookies "$SP/api/v1/rule-packs"
[
{
"key": "core",
"version": "1.1.0",
"name": "Core AML & fraud",
"description": "Market-neutral rules for every institution. Thresholds are starting points: calibrate with backtesting before go-live.",
"jurisdiction": null,
"rules": 24,
"installed_version": "1.0.2",
"source": "embedded"
},
{
"key": "gh-bank",
"version": "1.0.0",
"name": "Ghana banks",
"description": "Typologies for Ghanaian universal and rural banks: branch cash and structuring across branches, …",
"jurisdiction": "GH",
"rules": 10,
"installed_version": null,
"source": "embedded"
}
]
Field Meaning
version The newest version available to your installation: the one built into your SpeculaPro, or a newer one from a signed pack file
installed_version The version your workspace has, or null if the pack isn’t installed. When it differs from version, as for core above, an upgrade is available
rules How many rules that version has
source embedded (built into your SpeculaPro) or bundle (from a signed pack file)
amounts_currency, workspace_currency The currency the pack’s default amounts are written in (empty when the pack states none, as for core and fraud), and your workspace’s base currency
amounts_for_base_currency true when the pack has default amounts for your base currency; install and upgrade use them
currency_mismatch true when installing would bring in amounts meant for another currency: set every amount before enabling the pack’s rules. See Packs written in another currency
Terminal window
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/rule-packs/core/install"
{ "added": 16 }

Each rule arrives as version 1, approved, in the live slot, but stopped. Nothing runs until someone enables it.

  • Installing again is harmless. If the pack is already installed, nothing changes and added is 0.
  • Your rules are never overwritten. If a rule with the same key already exists in your workspace, the pack skips it.

Audit: rule_pack.install, with the pack, its version and the rules added.

Terminal window
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/rule-packs/core/upgrade"
{
"pack": "core",
"from": "1.0.2",
"to": "1.1.0",
"added": 8,
"items": [
{ "rule_key": "core.large_transaction", "library_key": "A1", "name": "Large single transaction",
"rule_id": "0192f1c4-…", "outcome": "updated", "version_id": "0193a7e2-…", "kept_params": ["threshold"],
"detail": "A new version awaits approval by someone else; your current version keeps running until it is deployed." },
{ "rule_key": "core.count_spike", "library_key": "C1", "name": "Transaction count spike against the account's own history",
"outcome": "added" },
{ "rule_key": "core.structuring", "library_key": "B1", "name": "Repeated deposits just below a threshold",
"rule_id": "0192f1c4-…", "outcome": "customised",
"detail": "Your workspace changed this rule's logic, so the pack's new definition wasn't applied. Compare them and update it by hand if you want it." }
]
}

Moves an installed pack to the newest version available, and reports what happened to every rule in it, in items:

outcome Meaning
added New in this version: added like an install, version 1, approved, live slot, stopped
updated The packaged definition changed. A new version, version_id, was drafted from it with your tuning carried over (kept_params lists the parameters, and severity or score, whose values you had changed and which were kept) and sent for approval. The upgrading person is its author, so someone else must approve and deploy it. Your current version keeps running
needs_attention The definition changed, but a value you tuned doesn’t validate against the new one, or the pack moved the rule to another mode. Nothing was created; detail says why
customised You changed this rule’s expression or features, so the new definition wasn’t applied
unchanged Nothing to do. Also used, with a detail, for a rule with the same key that belongs to another pack or that you wrote
  • If the pack is already at the newest version, from and to are equal and nothing changes.
  • If the installed version is newer than the newest available, you get 409 pack … … is installed; the newest available is …. Packs never go backwards through this endpoint.
  • If the pack isn’t installed, you get 409 pack … is not installed. Install it instead.

See How to upgrade a pack for how tuning is carried over.

Audit: rule_pack.upgrade, with the old version, the new version, rules_added and report: every item except the unchanged ones.

Loads a signed pack file (.sppack) from SpeculaPro. Send the file’s contents as text:

Terminal window
jq -Rs '{bundle: .}' gh-bank-1.1.0.sppack | \
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' -d @- "$SP/api/v1/rule-packs/bundles"
{
"key": "gh-bank",
"version": "1.1.0",
"name": "Ghana banks",
"key_id": "pack-2026-1",
"previous_version": "1.0.0",
"action": "upgraded",
"rules_added": 0,
"report": { "…": "the same per-rule report as /upgrade" }
}
Field Meaning
bundle The file’s contents (request)
force true to install a version older than the installed one (request, optional). Needs rules.packs_downgrade as well as rules.approve; otherwise 403 forcing a pack downgrade needs the rules.packs_downgrade permission (the MLRO role has it)
action installed (the pack wasn’t installed), upgraded, downgraded (forced) or unchanged (same version)
report For upgraded and downgraded: what happened to each rule, as for /upgrade
key_id The SpeculaPro key that signed the file

The signature is checked before anything in the file is read. The file is stored for the whole installation, and other workspaces can then upgrade the pack with /upgrade. In this workspace, loading the file is an upgrade like /upgrade: new rules are added, stopped; rules whose definition changed get new versions with your tuning, awaiting someone else’s approval; rules you changed are left alone and reported.

Status detail Why
422 rule-pack bundle rejected: rule-pack bundle is not valid: signature does not match (or signed with an unknown key …, signed for a different purpose …, not a signed SpeculaPro document …) The file is damaged, altered, or not a pack file
409 conflict: pack … differs from the built-in pack of the same version Same version as the pack built into SpeculaPro, different rules
409 conflict: pack … is already present with different content A different file with this pack and version was loaded before
409 conflict: … is older than the installed version …; someone allowed to downgrade packs can force it Send "force": true (with rules.packs_downgrade) to go back

Audit: rule_pack.install, rule_pack.upgrade or rule_pack.downgrade, with source: bundle, key_id, the versions, the rules added and, for an upgrade or downgrade, the per-rule report. A downgrade also records forced: true.

The rule builder is built from these two endpoints. Both only need rules.view, and neither saves anything.

Terminal window
curl -s -b ~/.sp-cookies "$SP/api/v1/rules/catalog"
{
"fields": [
{ "path": "txn.amount", "type": "number", "label": "Amount", "doc": "In the workspace's base currency, major units (e.g. cedis)", "modes": ["realtime", "post_event"] },
{ "path": "txn.channel", "type": "string", "label": "Channel", "doc": "", "enum": ["momo", "bank_transfer", "…"], "modes": ["realtime", "post_event"] },
{ "path": "customer.hours_since", "type": "map", "label": "Hours since each account event", "doc": "customer.hours_since.sim_swap; check presence with \"sim_swap\" in customer.hours_since", "enum": ["login", "login_failed", "…"], "modes": ["realtime", "post_event"] }
],
"features": {
"entities": ["customer", "account", "counterparty", "device"],
"aggs": ["count", "sum", "max", "avg", "distinct_counterparties", "distinct_customers"],
"directions": ["any", "in", "out", "internal", "event"],
"types": ["transfer", "payment", "…"],
"event_types": ["login", "login_failed", "…"],
"channels": ["momo", "bank_transfer", "…"],
"windows": ["5m", "10m", "15m", "30m", "1h", "2h", "6h", "24h", "48h", "168h", "720h", "2160h", "4320h"]
},
"actions": {
"realtime": ["block", "review", "step_up", "allow", "tag", "score_only"],
"post_event": ["alert", "tag", "score_only"]
}
}
Part What it holds
fields Every variable an expression can read: its path, type (number, integer, string, bool, list or map), a label and doc, the allowed values in enum where there is a fixed set, and the modes it carries data in. It matches the CEL reference
features What a feature can be built from. windows are suggestions: any duration from 1m to 4320h works
actions The actions each mode allows

Compiles a definition and evaluates it once against a sample you describe. Nothing is saved and no real data is read.

Terminal window
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' -d '{
"mode": "realtime",
"definition": {
"expression": "txn.direction == '\''out'\'' && device.is_new && features.payouts_10m >= double(params.max_payouts)",
"features": { "payouts_10m": { "entity": "customer", "agg": "count", "direction": "out", "window": "10m" } },
"params": { "max_payouts": 3 },
"severity": "high", "score": 70, "action": "review",
"explanation": "{{features.payouts_10m}} payouts in 10 minutes from a new device."
},
"sample": {
"txn": { "direction": "out", "amount": 2500 },
"device": { "known": true, "is_new": true },
"features": { "payouts_10m": 4 }
}
}' "$SP/api/v1/rules/test"
{ "valid": true, "hit": true, "explanation": "4 payouts in 10 minutes from a new device." }
Field Required Meaning
mode Yes realtime or post_event. The action must be one the mode allows
definition Yes As for validate
sample Yes (may be {}) txn, customer, account, counterparty and device objects with the fields you want to set; features by the names the definition declares; lists as {"key": ["value", …]}

Fields you leave out take the same neutral defaults as live evaluation (for example -1 for unknown ages and distances, false for the new-device and new-country signals). The sample’s transaction defaults to an outgoing transfer on mobile_app at 12:00 on a Wednesday. A list you don’t put in sample.lists reads as empty. Features are not worked out from history: they take the value you give, or 0.

Response field Meaning
valid false if the definition doesn’t compile or the action doesn’t fit the mode; problems then lists why
hit Whether the rule fires on this sample
explanation The explanation, filled in, when it fires
error The sample couldn’t be evaluated, for example a field with the wrong type; hit is then false

Lists every rule in your workspace. Each rule looks like this (shortened):

{
"id": "0192f1c4-7d1e-7a3b-9c55-2f8e1a6b0d41",
"key": "core.structuring",
"pack_key": "core",
"library_key": "B1",
"name": "Repeated deposits just below a threshold",
"category": "structuring",
"typology": "Structuring / smurfing",
"mode": "post_event",
"entity_scope": "customer",
"enabled": false,
"active": {
"id": "0192f1c4-7d1f-7c02-8e11-5b0f3e2a9c77",
"version": 1,
"state": "approved",
"expression": "txn.type == 'cash_in' && txn.amount >= params.threshold * (1.0 - params.margin) && …",
"features": { "cash_in_count_48h": { "entity": "account", "agg": "count", "direction": "in", "type": "cash_in", "window": "48h" } },
"params": { "threshold": 10000, "margin": 0.1, "min_count": 3 },
"params_schema": { "threshold": { "type": "number", "title": "Threshold being avoided", "minimum": 0 } },
"severity": "high",
"score": 70,
"action": "alert",
"explanation": "{{features.cash_in_count_48h}} cash-ins totalling {{txn.base_currency}} {{features.cash_in_sum_48h}} in 48h; …",
"change_note": "Installed from pack core 1.0.0",
"created_at": "2026-10-01T08:12:40Z"
},
"pending_versions": 0,
"hits_30d": 0,
"shadow_hits_30d": 0
}
Field Meaning
key The rule’s unique name in your workspace. Pack rules start with the pack’s prefix (core., gh.); custom rules with custom.
library_key Its code in the rule library, for example B1
entity_scope Who its alerts are about: customer, account or counterparty, or agent for rules about an agent (the pack rules G1 and ng.agent_daily_cash_out, or a custom rule created through the API): their alerts are about the agent as a counterparty (for the scheduled G1, the agent’s account when it has one)
mode post_event, realtime or scheduled. Real-time rules run only in real-time decisions; scheduled rules on their timetable
schedule Scheduled rules only: when it runs, such as daily 02:00, in your workspace’s time zone
enabled true while the rule is running
active The live version, or absent if none has been deployed
shadow The version being shadow-tested, or absent
pending_versions How many versions are waiting for approval
hits_30d, shadow_hits_30d Live and shadow hits in the last 30 days. For a real-time rule, the decisions it fired in

Each version carries params (the numbers), params_schema (what each parameter means: its title, type and allowed range), severity, score, action, explanation, and the logic (expression, features). A version saved from the rule builder also carries builder: the builder’s own description of the conditions, so it reopens there. It is informational; the expression is what runs. It also carries change_note, created_by and, once decided, decided_by and decided_at. created_by and decided_by are display names; versions installed from a pack have no created_by.

The same object, plus versions: every version of the rule, newest first, in any state.

Drafts a new version. Send only what changes: any field you leave out (expression, features, params, severity, score, action, explanation) is copied from the latest version of the rule.

Terminal window
RULE=0192f1c4-8a20-7b51-a3e9-6c1d0f4e2b18 # id of core.large_transaction
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' \
-d '{
"params": { "threshold": 75000, "channels": ["bank_transfer", "instant_payment"] },
"change_note": "Week-1 review: 41 of 44 hits were routine supplier payments under GHS 75k. Raise threshold, limit to bank channels. Ticket CMP-112."
}' \
"$SP/api/v1/rules/$RULE/versions"
{ "id": "0192f1d0-11aa-7e4c-9b20-4a7d3c8e5f01" }

The response is 201 Created; the id is the new version’s id. The version is a draft: it does nothing until it is submitted, approved and deployed.

Field Required Meaning
change_note Yes, 3 to 500 characters Why the change. The approver reads it first, and it stays in the audit trail
params No The complete set of parameters (see below)
severity No low, medium, high or critical
score No 0 to 100
action No One the rule’s mode allows: alert, tag or score_only for post-event rules; block, review, step_up, allow, tag or score_only for real-time rules. See what a rule produces
explanation No Up to 500 characters. Plain placeholders {{txn.amount}} and computed ones {{= <CEL> }}; a computed one that doesn’t compile is refused with explanation {{= … }}: …. See Explanations
expression, features No The logic. See CEL reference and Features reference
params_schema No Replaces the parameter descriptions (title, type, range), for example when you add or remove a parameter
builder No The rule builder’s model. If you send expression without builder, the previous builder model is dropped, so the rule next opens as code

The draft is checked when you save it. If anything is wrong you get 422 with every problem:

{
"title": "Unprocessable Entity",
"status": 422,
"detail": "rule is not valid",
"errors": [
{ "message": "features.out_7d is used but not declared" },
{ "message": "params.threshold is used but has no value" }
]
}

Audit: rule_version.create, with the previous version and parameters and the new draft.

Terminal window
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/rule-versions/0192f1d0-11aa-7e4c-9b20-4a7d3c8e5f01/submit"

204 means the version is now pending_approval. Only a draft can be submitted; anything else returns 409 only draft versions can be submitted. Audit: rule_version.submit.

POST /api/v1/rule-versions/{id}/approve and /reject

Section titled “POST /api/v1/rule-versions/{id}/approve and /reject”

Called by someone with rules.approve who is not the version’s author. The note is optional (up to 500 characters) but strongly recommended, especially when rejecting:

Terminal window
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' \
-d '{ "note": "Agreed. Revisit after month-end volumes." }' \
"$SP/api/v1/rule-versions/0192f1d0-11aa-7e4c-9b20-4a7d3c8e5f01/approve"
Response Meaning
204 Decided
403 a version must be approved by someone other than its author You drafted this version. The database refuses it too, so no script can get around it
409 only versions pending approval can be decided It hasn’t been submitted, or it was already decided

Once approved, a version’s logic is frozen: its expression, features, parameters, severity, score and action can never change. A rejected version can’t be resubmitted; draft a new one. Audit: rule_version.approve or rule_version.reject, with the note.

Puts an approved version in one of the rule’s two slots:

slot Effect
active The version goes live: its hits raise alerts. If it was the shadow version, the shadow slot empties
shadow The version runs on live traffic next to the live one and records hits, but never raises alerts. It replaces any version already in shadow
Terminal window
curl -s -b ~/.sp-cookies -H 'Content-Type: application/json' \
-d '{ "version_id": "0192f1d0-11aa-7e4c-9b20-4a7d3c8e5f01", "slot": "shadow" }' \
"$SP/api/v1/rules/$RULE/deploy"

The running system picks the change up within about 15 seconds. To roll back, deploy an older approved version to active; no new approval is needed, because it was already approved.

Response Meaning
204 Deployed
409 only approved versions can be deployed (this one is draft) Approve it first
409 version belongs to another rule The version id and rule id don’t match

Audit: rule.deploy, with what was in each slot before.

Stops the shadow test by emptying the shadow slot. The version stays approved and its hits stay on record. 204 on success, also when no test was running. 404 if there is no such rule in your workspace. Audit: rule.clear_shadow.

POST /api/v1/rules/{id}/enable and /disable

Section titled “POST /api/v1/rules/{id}/enable and /disable”
Terminal window
curl -s -b ~/.sp-cookies -X POST "$SP/api/v1/rules/$RULE/enable"

204 on success. A rule starts running on new transactions within about 15 seconds; it does not go back over transactions that arrived earlier. Disabling stops both the live and the shadow version; hits and alerts already created stay.

409 rule not found or has no approved active version means the id is wrong, or nothing has been deployed to the live slot yet (for example a new custom rule). Audit: rule.enable or rule.disable.

A scheduled rule’s versions store "expression": "scenario:<key>" (for example scenario:fan_in_fan_out) and no features: the query is built into SpeculaPro. Draft new versions with POST /api/v1/rules/{id}/versions as for any rule, changing only params, severity, score, action and explanation. Parameters are checked against the query’s hard limits, for example params.window_hours must be at most 720, and a computed placeholder in the explanation is refused. You can’t create a scheduled rule.

Query parameter Meaning
limit How many runs, newest first: 1 to 200, default 30
Terminal window
curl -s -b ~/.sp-cookies "$SP/api/v1/rules/0192f1c4-…/runs?limit=2"
{
"schedule": "daily 02:00",
"timezone": "Africa/Accra",
"enabled": true,
"next_run_at": "2026-10-10T02:00:00Z",
"runs": [
{
"id": "0193b2d0-…",
"version_id": "0192f1c4-…",
"version": 1,
"shadow": false,
"scenario": "fan_in_fan_out",
"period_start": "2026-10-08T02:00:00Z",
"period_end": "2026-10-09T02:00:00Z",
"data_from": "2026-10-06T02:00:00Z",
"catch_up": false,
"truncated": false,
"status": "succeeded",
"attempts": 1,
"started_at": "2026-10-09T02:00:41Z",
"finished_at": "2026-10-09T02:00:42Z",
"duration_ms": 812,
"candidates": 3,
"hits": 2,
"suppressed": 0
}
],
"backoff": []
}
Field Meaning
next_run_at The next slot, while the rule is running; absent when it is stopped
period_end The slot this run was for
period_start Where the previous successful run ended (or one interval before, for the first)
data_from The earliest transaction time the run read
catch_up true when the run covered several missed slots
truncated true when the missed span was longer than 31 days and was cut
status running, succeeded or failed, with error when it failed
attempts How many times this window has been tried
candidates Subjects the query matched
hits Hits written, after leaving out subjects flagged within the last rule window. suppressed counts those a suppression swallowed
backoff Versions whose runs keep failing and are waiting before the next try: version_id, failures (consecutive failed runs), retry_at and last_error. Empty while all is well. See Scheduled rules: missed runs

A rule that isn’t scheduled, or isn’t in your workspace, returns 404 no scheduled rule with this id.

Terminal window
curl -s -b ~/.sp-cookies -X PUT -H 'Content-Type: application/json' \
-d '{"schedule": "weekly mon 03:30"}' "$SP/api/v1/rules/0192f1c4-…/schedule"

Returns 204. schedule is hourly, hourly :MM, daily HH:MM or weekly DAY HH:MM (DAY is mon to sun), in your workspace’s time zone. The change takes effect at the next slot, and the next run still covers everything since the last one. It isn’t a new version and needs no second approval.

Status detail
422 rule is not valid, with schedule "…": use "hourly", "hourly :MM", "daily HH:MM" or "weekly DAY HH:MM" (DAY = mon … sun)
404 not found: no scheduled rule with this id

Audit: rule.schedule, with the schedule before and after.

Checks a definition (expression, features, params, severity, score, action, explanation) without saving anything. It always answers 200:

{ "valid": false, "problems": ["features.out_7d is used but not declared"] }

It checks that the definition compiles, not that it catches what you want, and it doesn’t check the action against a mode. To try it on an example, use test; see Writing a custom rule.

Creates a custom rule as draft version 1. Its key is stored with a custom. prefix.

Field Required Meaning
key Yes 3 to 64 characters: lowercase letters, digits, _, . and -
name Yes 3 to 120 characters
category Yes 2 to 40 characters, for example cash or structuring. The Rules screen groups rules by it
entity_scope Yes customer, account, counterparty or agent: who its alerts are about. agent is the agent on the other side of the customer’s cash-in or cash-out, that is the transaction’s counterparty; when the counterparty isn’t known, the alert is about the customer
mode No post_event (default) or realtime. It can’t be changed later, and the definition’s action must fit it
definition Yes The same fields as validate, plus an optional builder
params_schema No A description per parameter (type, title, minimum, maximum), so people can tune values on screen
description, typology No Shown on the rule’s page
change_note No Up to 500 characters

Response: 201 {"id": "<rule id>"}. A key that already exists returns 409 a rule with key custom.… already exists. Then submit, approve and deploy version 1 like any other version. The full walkthrough is in Writing a custom rule. Audit: rule.create.