Recommendation Engine — Integration Guide
This guide documents the merchant-facing API for managing rules in the DEUNA Recommendation Engine. A rule is a set of conditions (what to match) plus an ordered list of members — the providers the rule uses, which can be payment providers, fraud providers, or authentication providers — and optional weighted children (for A/B testing). Rules are evaluated by priority until one matches.
Base URLsProduction:
https://api.deuna.io
Sandbox:https://api.sandbox.deuna.io
All rule-management endpoints are namespaced under/routing/v1. Use your sandbox API key against the sandbox host, and your production key against production.
The sandbox is a full, production-grade environment available 24/7 — same contract as production — and can simulate every case you can run live: approvals, declines, authentication outcomes (3DS Data Only, 3DS Authentication, Unico ID), liability-shift results, fraud provider score ranges, and provider outages to exercise your failover path.
How to read the examplesThe examples use realistic sample values so you can copy, paste and adapt them. Swap in your own IDs (API key, provider IDs, rule IDs). The field tables under each object define the type, whether it's required, and the allowed values.
Authentication
All endpoints authenticate with an API key sent in the X-Api-Key header — this is the only supported authentication scheme. Your merchant is resolved from the API key, so the merchant identifier does not appear in the URL.
X-Api-Key: {{DEUNA's API Key}}
X-Idempotency-Key: {{UUID v4}}
Content-Type: application/json
| Header | Required | Description |
|---|---|---|
X-Api-Key | Yes | Your API key. Determines the environment and the merchant scope. |
Content-Type | Yes | application/json. |
X-Idempotency-Key | Recommended | A unique key you generate per logical request (a UUID works well). Retrying with the same key returns the original result instead of applying the operation twice — send it so network retries never create duplicate rules or double-count an action. |
Keep your API key secret. Use environment-specific keys (sandbox vs production) and never commit a key to source control or embed it in a shared document. The key above is a non-functional example.
Endpoint summary
| Method | Path | Purpose |
|---|---|---|
GET | /routing/v1/rules | List all rules (priority order) |
POST | /routing/v1/rules | Create a rule |
GET | /routing/v1/rules/{rule_id} | Get a single rule by ID |
PUT | /routing/v1/rules/{rule_id} | Update a rule (full replace) |
PUT | /routing/v1/rules/{rule_id}/reorder | Change a rule's priority |
There is no delete endpointRules are never destroyed. To take a rule out of production, update its
statustodisabled. This preserves the rule and its audit history and lets you re-enable it later.
Roles & permissionsRule management is governed by role-based access control — e.g. view-only, write, and admin roles. Your API key inherits the permissions of the role it's issued for, so a read-only key can
GETrules but cannot create, update, reorder or disable them. This keeps rule changes least-privilege and auditable.
Path parameters:
| Parameter | Type | Notes |
|---|---|---|
rule_id | integer | Numeric rule identifier returned by create/list. |
The Rule object
A rule is the core resource returned and accepted by these endpoints.
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Response only | Server-assigned rule identifier. |
label | string | Yes | Human-readable rule name. |
data_type | string | Yes | Method family this rule applies to: credit_card, debit_card, prepaid_card. |
priority | integer | Yes | Evaluation order — lower runs first, first match wins (non-negative numbers) |
status | enum | Yes | enabled, disabled, draft |
is_default | boolean | Yes | If true, this is the fallback rule when no other matches. A default rule must have no conditions and must not set ignore_next_rules. |
trigger | enum | Yes | payment, reject, or merchant_rule. See Triggers. |
conditions | array<Condition> | Conditional | Match criteria, AND-combined. Required unless is_default = true. |
members | array<Member> | Conditional | Ordered providers to attempt. Required for payment; required (or children) for merchant_rule; forbidden for reject. |
children | array<Child> | Optional | Weighted child branches for A/B testing (only with trigger = merchant_rule). |
ignore_next_rules | boolean | Yes | If true, stop evaluating further rules once this one matches. Forbidden for reject and for default rules. |
created_at | string (RFC3339) | Response only | Creation timestamp. |
Triggers
trigger | Meaning | members | children | ignore_next_rules | is_default |
|---|---|---|---|---|---|
payment | Route the payment to the listed providers. | Required (≥1) | Not allowed | Allowed | Allowed |
reject | Block the transaction outright. | Not allowed | Not allowed | Not allowed | Not allowed |
merchant_rule | Group/branch: route via members or weighted children. | Required if no children | Allowed | Allowed | Allowed |
The Condition object
Conditions define what a transaction must match. All conditions on a rule are combined with AND.
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Response only | Server-assigned condition ID. |
rule_option | object | Yes | The field to match: { "id": 5, "label": "currency" }. id must be > 0. See Rule Options. |
operator | enum | Yes | Comparison operator. Must be valid for that rule option |
operand | string | Conditional | The value(s) to compare against, always serialized as a string. Format depends on the operator. Required for every operator except is_present, which takes no operand. |
operand_type | string | No | values (default) or list (operand references a custom list by UUID). |
operand_config | object | null | Conditional | Used when operand_type = list: { "custom_list": { "field_to_evaluate": "email" } }. |
metadata_field_name | string | null | Conditional | Required when rule_option.id = 16 (metadata). The metadata key to evaluate. |
metadata_field_type | string | null | Conditional | Required when rule_option.id = 16. One of text, numeric. |
error_code | string | Response only | Set when async processing of a condition (e.g. list import) failed. |
error_message | string | Response only | Human-readable detail for error_code. |
Eachrule_optionmay appear only once per rule — exceptmetadata, which may repeat with differentmetadata_field_names.
Example — match Mastercard-branded cards
{
"rule_option": { "id": 3, "label": "branch" },
"operator": "in",
"operand": "mastercard"
}The Member object
Members are the providers a matched rule uses, attempted in sort order (the cascade). A member references one provider — a payment provider, a fraud provider, or an authentication provider.
| Field | Type | Required | Description |
|---|---|---|---|
payment_provider_id | integer | Conditional | DEUNA payment provider ID. Required if merchant_payment_provider_id is present. |
payment_provider_name | string | Response only | Provider name (echoed back). |
merchant_payment_provider_id | UUID | Conditional | Merchant's specific provider connection. If sent, payment_provider_id must also be sent. |
merchant_payment_provider_name | string | Response only | Connection name (echoed back). |
fraud_provider | string | Conditional | Fraud/anti-fraud provider name. Mutually exclusive with payment- and authentication-provider fields. |
fraud_provider_id | string | Response only | Fraud provider ID (echoed back). |
authentication_provider | string | Conditional | Authentication provider name (e.g. UNICO_ID, CYBERSOURCE_3DS). Mutually exclusive with payment- and fraud-provider fields. |
authentication_provider_id | string | Response only | Authentication provider ID (echoed back). |
authentication_type | enum | Conditional | The authentication method. One of 3ds_authentication, 3ds_data_only, unico_id. Required when the member is an authentication provider. |
failover | object | null | Optional | Backup authentication used when the primary provider is unavailable: { "authentication_provider": "...", "authentication_type": "..." }. Valid only on authentication-provider members. |
sort | integer | Yes | Cascade order. Must be unique within the rule. |
strategy | enum | Yes | Currently only cascade. |
capabilities | array<string> | Yes | Provider capabilities to use, e.g. ["3ds"]. Send [] if none. |
enabled3ds | boolean | No | Request 3DS authentication on this member (create payload). |
post_authorization | boolean | Yes | Only a fraud provider may set true, and it must be the last member by sort. |
shadow_mode | boolean | Yes | Evaluate the provider without affecting the route (safe trial). |
enabled | boolean | Response only | Whether the provider is currently available to the merchant. |
Example — payment-provider member
{
"sort": 1,
"strategy": "cascade",
"payment_provider_id": 45,
"merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8",
"capabilities": ["3ds"],
"post_authorization": false,
"shadow_mode": false
}Example — fraud-provider member
{
"sort": 1,
"strategy": "cascade",
"fraud_provider": "CYBERSOURCE",
"capabilities": [],
"post_authorization": false,
"shadow_mode": false
}Example — authentication-provider member (with failover)
{
"sort": 1,
"strategy": "cascade",
"authentication_provider": "CYBERSOURCE_3DS",
"authentication_type": "3ds_authentication",
"failover": {
"authentication_provider": "CYBERSOURCE_3DS",
"authentication_type": "3ds_data_only"
},
"capabilities": [],
"shadow_mode": false
}A/B testing — split traffic between two routes
To A/B test, add two routes to a rule (as children) and give each a percentage weight. The engine sends that share of matching transactions to each route — e.g. 70% to Route A, 30% to Route B — so you can compare them on live traffic.
| Field | Type | Required | Description |
|---|---|---|---|
weight | integer | Yes | Percentage of matching traffic sent to this route. The two routes' weights must sum to 100. |
members | array<Member> | Yes | The provider(s) for this route. |
Rules:
- Exactly two routes.
weightvalues must sum to 100.- The rule must set
ignore_next_rules: true. - Assignment is sticky per
transaction_id(a transaction always gets the same route), and the route that ran is echoed back in the/triggersresponse.
Example — 70/30 split
{
"label": "A/B test — MXN cards",
"ignore_next_rules": true,
"conditions": [
{ "rule_option": { "id": 5, "label": "currency" }, "operator": "in", "operand": "MXN" }
],
"children": [
{
"weight": 70,
"members": [
{ "sort": 1, "strategy": "cascade", "payment_provider_id": 45, "merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8" }
]
},
{
"weight": 30,
"members": [
{ "sort": 1, "strategy": "cascade", "payment_provider_id": 52, "merchant_payment_provider_id": "8b3d7c90-1a2b-4c3d-9e0f-5a6b7c8d9e01" }
]
}
]
}Create a rule
POST /routing/v1/rules
The example below creates a merchant_rule for Mastercard credit cards in MXN, runs a fraud check, and then branches by fraud_risk: block high, and route medium/low to Cybersource.
cURL
curl -X POST 'https://api.sandbox.deuna.io/routing/v1/rules' \
-H 'X-Api-Key: {{API KEY}}' \
-H 'X-Idempotency-Key: b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1' \
-H 'Content-Type: application/json' \
-d '{
"label": "MX Mastercard — Cybersource",
"priority": 2,
"status": "enabled",
"trigger": "merchant_rule",
"is_default": false,
"data_type": "credit_card",
"ignore_next_rules": true,
"conditions": [
{ "rule_option": { "id": 3, "label": "branch" }, "operator": "in", "operand": "mastercard" },
{ "rule_option": { "id": 5, "label": "currency" }, "operator": "in", "operand": "MXN" }
],
"members": [
{ "sort": 1, "strategy": "cascade", "fraud_provider": "CYBERSOURCE" }
],
"children": [
{ "conditions": [ { "rule_option": { "id": 15 }, "operator": "eq", "operand": "high" } ], "members": [] },
{ "conditions": [ { "rule_option": { "id": 15 }, "operator": "eq", "operand": "medium" } ],
"members": [ { "sort": 1, "strategy": "cascade", "payment_provider_id": 45, "merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8", "capabilities": ["3ds"], "enabled3ds": false } ] },
{ "conditions": [ { "rule_option": { "id": 15 }, "operator": "eq", "operand": "low" } ],
"members": [ { "sort": 1, "strategy": "cascade", "payment_provider_id": 45, "merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8", "capabilities": ["3ds"], "enabled3ds": false } ] }
]
}'Response 201
201{
"id": 21866,
"label": "MX Mastercard — Cybersource",
"data_type": "credit_card",
"priority": 2,
"status": "enabled",
"is_default": false,
"trigger": "merchant_rule",
"conditions": [
{
"id": 44264,
"rule_option": { "id": 3, "label": "branch" },
"operator": "in",
"operand_config": null,
"operand": "mastercard",
"operand_type": "values",
"metadata_field_name": null,
"metadata_field_type": null,
"error_code": "",
"error_message": ""
},
{
"id": 44265,
"rule_option": { "id": 5, "label": "currency" },
"operator": "in",
"operand": "MXN",
"operand_type": "values"
}
],
"members": [
{
"payment_provider_name": null,
"merchant_payment_provider_name": null,
"fraud_provider": "CYBERSOURCE",
"sort": 1,
"capabilities": [],
"strategy": "cascade",
"post_authorization": false,
"shadow_mode": false
}
],
"ignore_next_rules": true,
"created_at": "2026-07-08T01:07:29.439794Z"
}Get a rule by ID
GET /routing/v1/rules/{rule_id}
Returns a single rule, including its conditions, members and any children. rule_id is the numeric identifier.
cURL
curl 'https://api.sandbox.deuna.io/routing/v1/rules/21866' \
-H 'X-Api-Key: {{API KEY}}'Response 200
200{
"id": 21866,
"label": "MX Mastercard — Cybersource",
"data_type": "credit_card",
"priority": 2,
"status": "enabled",
"is_default": false,
"trigger": "merchant_rule",
"conditions": [
{
"id": 44269,
"rule_option": { "id": 3, "label": "branch" },
"operator": "in",
"operand": "mastercard",
"operand_type": "values"
},
{
"id": 44270,
"rule_option": { "id": 5, "label": "currency" },
"operator": "in",
"operand": "MXN",
"operand_type": "values"
}
],
"members": [
{
"fraud_provider_id": "3",
"fraud_provider": "CYBERSOURCE",
"sort": 1,
"capabilities": [],
"enabled": true,
"strategy": "cascade",
"post_authorization": false,
"shadow_mode": false
}
],
"ignore_next_rules": true,
"created_at": "2026-07-08T01:07:29.439794Z",
"children": [
{
"conditions": [
{ "id": 44271, "rule_option": { "id": 15, "label": "fraud_risk" }, "operator": "eq", "operand": "high", "operand_type": "values" }
],
"members": [],
"ignore_next_rules": true
},
{
"conditions": [
{ "id": 44272, "rule_option": { "id": 15, "label": "fraud_risk" }, "operator": "eq", "operand": "medium", "operand_type": "values" }
],
"members": [
{
"payment_provider_id": 45,
"payment_provider_name": "cybersource",
"merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8",
"merchant_payment_provider_name": "Cybersource MX",
"sort": 1,
"capabilities": ["3ds"],
"enabled": true,
"strategy": "cascade",
"post_authorization": false,
"shadow_mode": false
}
],
"ignore_next_rules": true
}
]
}Errors: PAYROU-NOT_FOUND (404) if the rule does not exist; PAYROU-FORBIDDEN (403) if it is not accesible; PAYROU-INVALID_PATH_PARAM (400) if rule_id is not a valid integer.
List rules
GET /routing/v1/rules
Returns rules for your merchant in priority order. Results are paginated with an opaque cursor.
| Query param | Type | Description |
|---|---|---|
limit | integer | Optional. Page size. Default 50, maximum 200. |
cursor | string | Optional. Opaque cursor from a previous response's pagination.next_cursor. Omit for the first page. |
payment_provider_id | integer | Optional. Return only rules that reference this payment provider. |
The response wraps the rules in a pagination object. Keep requesting with next_cursor while has_more is true.
pagination field | Type | Description |
|---|---|---|
next_cursor | string | null | Cursor for the next page, or null on the last page. |
has_more | boolean | true if more rules are available. |
limit | integer | The page size that was applied. |
cURL
curl 'https://api.sandbox.deuna.io/routing/v1/rules?limit=50' \
-H 'X-Api-Key: {{API KEY}}'Response 200
200{
"merchant_id": "87dd2c00-1f7a-44aa-9e05-34330bab73cc",
"pagination": {
"next_cursor": "eyJwcmlvcml0eSI6M30",
"has_more": true,
"limit": 50
},
"rules": [
{
"id": 21873,
"label": "MX Mastercard — Cybersource",
"data_type": "credit_card",
"priority": 2,
"status": "enabled",
"is_default": false,
"trigger": "payment",
"conditions": [
{ "id": 44274, "rule_option": { "id": 3, "label": "branch" }, "operator": "in", "operand": "mastercard", "operand_type": "values" }
],
"members": [
{
"payment_provider_id": 45,
"payment_provider_name": "cybersource",
"merchant_payment_provider_id": "6e4c651a-3e5f-403d-af9d-6a9e9299acb8",
"merchant_payment_provider_name": "Cybersource MX",
"sort": 1,
"capabilities": ["3ds"],
"enabled": true,
"strategy": "cascade",
"post_authorization": false,
"shadow_mode": false
}
],
"ignore_next_rules": true,
"created_at": "2026-07-08T01:09:49.553614Z"
}
]
}Update a rule
PUT /routing/v1/rules/{rule_id}
Full replace of the rule. Send the complete rule body — the same shape as create — including id and created_at. Common use: flip status between enabled and disabled (this is how you "retire" a rule instead of deleting).
cURL — disabling a rule
curl -X PUT 'https://api.sandbox.deuna.io/routing/v1/rules/21866' \
-H 'X-Api-Key: {{API KEY}}' \
-H 'X-Idempotency-Key: 7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c' \
-H 'Content-Type: application/json' \
-d '{
"id": 21866,
"created_at": "2026-07-08T01:07:29.439794Z",
"label": "MX Mastercard — Cybersource",
"priority": 2,
"status": "disabled",
"trigger": "merchant_rule",
"is_default": false,
"data_type": "credit_card",
"ignore_next_rules": true,
"conditions": [
{ "id": 44264, "rule_option": { "id": 3, "label": "branch" }, "operator": "in", "operand": "mastercard", "operand_type": "values" },
{ "id": 44265, "rule_option": { "id": 5, "label": "currency" }, "operator": "in", "operand": "MXN", "operand_type": "values" }
],
"members": [
{ "sort": 1, "strategy": "cascade", "fraud_provider": "CYBERSOURCE", "capabilities": [], "post_authorization": false, "shadow_mode": false }
]
}'Response 200
200Returns the updated rule with status now disabled. Note: condition ids are re-issued on update.
Change a rule's priority (reorder)
PUT /routing/v1/rules/{rule_id}/reorder
Moves a rule to a new priority. Other rules are re-sequenced accordingly.
cURL
curl -X PUT 'https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder' \
-H 'X-Api-Key: sk_sandbox_9f8b2c1a7d4e60b3' \
-H 'Content-Type: application/json' \
-d '{ "priority": 2 }'| Field | Type | Required | Constraint |
|---|---|---|---|
priority | integer | Yes | Non-negative integer |
Response 200
200{ "priority": 2 }Errors: PAYROU-VALIDATION (400) with errors[].code = PRIORITY_OUT_OF_RANGE if priority is not a non-negative integer.
Validations
Create and update run these checks. Any failure returns HTTP 400 with top-level code PAYROU-VALIDATION and one entry per offending field in errors[] (see Errors for the envelope and the full field-level code catalog).
Rule level
statusmust be one ofenabled,disabled,draft,process-in-background.triggermust be one ofpayment,reject,merchant_rule.trigger = payment→ at least one member; no children.trigger = reject→ no members, no children,ignore_next_rulesmust be false,is_defaultmust be false.trigger = merchant_rule→ at least one member or at least one child.is_default = true→ no conditions andignore_next_rulesmust be false.is_default = false→ at least one condition.prioritymust be> 0and≤ 1000.
Conditions
rule_option.idrequired (> 0);operandrequired (non-empty).operatormust be valid for that rule option (see catalog).- A
rule_optionmay appear only once per rule (exceptmetadata). - For
metadata(id 16):metadata_field_nameandmetadata_field_typerequired; type must betextornumeric. - For
operand_type = list:operandmust be a valid list UUID that exists.
Members
- Each member must reference exactly one provider type: payment, merchant-payment, fraud, or authentication.
- Provider types cannot be mixed on one member.
- If
merchant_payment_provider_idis present,payment_provider_idmust also be present. - Authentication members require
authentication_type(one of3ds_authentication,3ds_data_only,unico_id). failoveris valid only on authentication members; itsauthentication_typemust also be a valid value.sortmust be unique within the rule.- A provider may appear only once per rule.
strategymust becascade.post_authorization = trueonly for fraud providers, and that member must be last bysort.
Service-level (checked against merchant configuration)
- Referenced rule option must exist and support the given operator.
- Referenced payment / merchant-payment / fraud / authentication provider must be enabled for the merchant.
merchant_payment_provider_idmust belong to the givenpayment_provider_id.
Errors
Error envelope
Every error response uses the same JSON shape:
{
"code": "PAYROU-VALIDATION",
"message": "One or more fields failed validation.",
"request_id": "req_01J9Z8K7QF3M2N4P5R6S7T8U9V",
"errors": [
{
"field": "members",
"code": "MEMBERS_REQUIRED",
"message": "At least one member is required when trigger is 'payment'."
}
]
}| Field | Type | Description |
|---|---|---|
code | string | Stable, machine-readable top-level code. Branch on this, never on message. |
message | string | Human-readable summary. May be reworded or localized — do not parse it. |
request_id | string | Unique per response and also returned in the X-Request-Id response header (on success and error). Quote it in any support request. |
errors | array | Present only for PAYROU-VALIDATION. One entry per offending field. |
errors[].field | string | Path to the field, using dot/bracket notation — e.g. priority, conditions[1].operand, members[0].sort. |
errors[].code | string | Stable field-level code (see validation codes). |
errors[].message | string | Human-readable detail. Do not parse. |
Always send an
X-Idempotency-Keyon writes. Replaying the same key returns the original response; reusing a key with a different body returns409 PAYROU-CONFLICT.
HTTP status codes
| Status | Meaning |
|---|---|
200 / 201 | Success. |
400 | The request is invalid — validation failed, malformed JSON, or bad path/query params. |
401 | Authentication failed — the X-Api-Key is missing or invalid. |
403 | Authorization failed — the key is valid but not allowed to access this merchant/resource. |
404 | The rule or a referenced resource does not exist. |
409 | Conflict — idempotency-key reuse with a different body, or a concurrent update. |
429 | Rate limit exceeded — retry after the Retry-After header. |
500 | Unexpected server error. |
503 | A downstream dependency is temporarily unavailable — safe to retry with backoff. |
Top-level codes
| Code | HTTP | When |
|---|---|---|
PAYROU-VALIDATION | 400 | One or more fields failed validation. Details in errors[]. |
PAYROU-MALFORMED_JSON | 400 | Request body is not valid JSON. |
PAYROU-INVALID_PATH_PARAM | 400 | A path parameter is the wrong type/format (e.g. non-integer rule_id). |
PAYROU-INVALID_QUERY_PARAM | 400 | A query parameter is invalid (e.g. limit out of range, malformed cursor). |
PAYROU-UNAUTHENTICATED | 401 | Missing or invalid API key. |
PAYROU-FORBIDDEN | 403 | Valid key, but not scoped to this merchant/resource. |
PAYROU-NOT_FOUND | 404 | Rule or referenced resource not found. |
PAYROU-CONFLICT | 409 | Idempotency-key reuse with a different payload, or concurrent modification. |
PAYROU-RATE_LIMITED | 429 | Too many requests. |
PAYROU-INTERNAL | 500 | Unexpected server error. |
PAYROU-UPSTREAM_UNAVAILABLE | 503 | A downstream dependency is temporarily unavailable. |
Validation codes
Returned inside errors[] when the top-level code is PAYROU-VALIDATION. Each is stable and safe to branch on.
Rule level
code | Typical field | Cause |
|---|---|---|
INVALID_STATUS | status | Not one of enabled, disabled, draft, process-in-background. |
INVALID_TRIGGER | trigger | Not one of payment, reject, merchant_rule. |
PRIORITY_OUT_OF_RANGE | priority | Not between 1 and 1000 (covers both ≤ 0 and > 1000). |
MEMBERS_REQUIRED | members | trigger = payment with no members. |
MEMBERS_OR_CHILDREN_REQUIRED | members | trigger = merchant_rule with neither members nor children. |
CONDITIONS_REQUIRED | conditions | Non-default rule with no conditions. |
TRIGGER_CANNOT_BE_DEFAULT | is_default | reject rule with is_default = true. |
TRIGGER_CANNOT_HAVE_MEMBERS | members | reject rule with members. |
TRIGGER_CANNOT_HAVE_CHILDREN | children | payment/reject rule with children. |
TRIGGER_CANNOT_IGNORE_NEXT_RULES | ignore_next_rules | reject rule with ignore_next_rules = true. |
DEFAULT_RULE_CANNOT_HAVE_CONDITIONS | conditions | Default rule with conditions. |
DEFAULT_RULE_CANNOT_IGNORE_NEXT_RULES | ignore_next_rules | Default rule with ignore_next_rules = true. |
Conditions
code | Typical field | Cause |
|---|---|---|
RULE_OPTION_ID_REQUIRED | conditions[i].rule_option.id | Missing or ≤ 0. |
OPERAND_REQUIRED | conditions[i].operand | Empty operand for an operator that requires one. |
OPERATOR_REQUIRED | conditions[i].operator | Operator is empty. |
INVALID_OPERATOR | conditions[i].operator | Operator not valid for that rule option. |
DUPLICATE_RULE_OPTION | conditions[i].rule_option | Same rule option used more than once (except metadata). |
METADATA_FIELDS_REQUIRED | conditions[i].metadata_field_name | metadata condition missing metadata_field_name/metadata_field_type. |
INVALID_METADATA_FIELD_TYPE | conditions[i].metadata_field_type | Not text or numeric. |
RULE_OPTION_NOT_FOUND | conditions[i].rule_option.id | Rule option does not exist. |
INVALID_LIST_REFERENCE | conditions[i].operand | operand_type = list but the list UUID is invalid or not found. |
Members
code | Typical field | Cause |
|---|---|---|
MEMBER_PROVIDER_REQUIRED | members[i] | Member references no provider (payment, fraud or authentication). |
MEMBER_MULTIPLE_PROVIDER_TYPES | members[i] | Member mixes provider types (e.g. payment + fraud, or payment + authentication). |
PAYMENT_PROVIDER_ID_REQUIRED | members[i].payment_provider_id | merchant_payment_provider_id sent without payment_provider_id. |
AUTHENTICATION_TYPE_REQUIRED | members[i].authentication_type | Authentication member without an authentication_type. |
INVALID_AUTHENTICATION_TYPE | members[i].authentication_type | Not one of 3ds_authentication, 3ds_data_only, unico_id (applies to failover.authentication_type too). |
FAILOVER_ONLY_ON_AUTHENTICATION | members[i].failover | failover set on a non-authentication member. |
DUPLICATE_MEMBER | members[i] | Same provider appears more than once in the rule. |
DUPLICATE_MEMBER_SORT | members[i].sort | Two members share a sort value. |
INVALID_STRATEGY | members[i].strategy | Not cascade. |
POST_AUTH_ONLY_FRAUD | members[i].post_authorization | post_authorization = true on a non-fraud member. |
POST_AUTH_MUST_BE_LAST | members[i].post_authorization | The post_authorization member is not last by sort. |
PROVIDER_NOT_AVAILABLE | members[i] | Provider (payment, fraud or authentication) is not enabled for this merchant. |
MERCHANT_PROVIDER_MISMATCH | members[i].merchant_payment_provider_id | Connection does not belong to the given payment_provider_id. |
A/B testing
code | Typical field | Cause |
|---|---|---|
SPLIT_WEIGHTS_MUST_SUM_TO_100 | children | Child weight values do not sum to 100. |
End-to-end flow
The /triggers endpoint supports two integration modes — pick whichever fits how much of the decision logic you want to own. Both use the same endpoint and the same rules; only the response shape and the number of calls differ. Select it with mode on the request (single is the default). Either way, you close the loop with one /feedback call.
| Mode | How it works | Best when |
|---|---|---|
| 1 · Single call (client-driven) | One /triggers call returns the complete plan — the authentication to run (with failover) and the actions that map each outcome to process or decline. You execute the plan yourself. | You want the fewest round-trips and are comfortable applying the decision locally. |
| 2 · Guided (engine-driven) | You call /triggers and the engine returns just the next step plus a status. You perform it, then call /triggers again with that step's result; the engine returns the next step. Repeat until status = completed. | You want DEUNA to own and centralize the decision logic, step by step. |
Mode 1 — single call
You make a single /triggers call. If you run a fraud provider before DEUNA, include its result in that call. The response is self-contained: it names the authentication to run (with a failover if the primary is unavailable) and the actions that map the authentication result to what to do next — process with a payment provider or decline. Your platform executes that plan locally and closes the loop with one /feedback call — there is no second /triggers round-trip.
Step by step
- (Optional) Score with a fraud provider. If your setup uses a fraud provider up front, capture its score/decision so it can be matched by rules (via the
fraud_riskoption ormetadata). - Request the recommendation (one call).
POST /routing/v1/triggerswith the transaction data (and the fraud result, if any). The response contains anauthenticationblock (primary+ optionalfailover) and anactionsblock. - Authenticate. Run the recommended authentication (e.g. 3DS Authentication, 3DS Data Only, or Unico ID). If the
primaryprovider is unavailable, use thefailover. - Decide and process. Apply
actions: each outcome maps toprocess(with the payment provider named in the action) ordecline; thedefaultaction applies when no other outcome matches. - Report the outcome.
POST /routing/v1/feedbackwith thecollectionobject and theattempts(authentication result + payment result). This closes the loop for analytics and ML labeling.
Merchants with a single payment provider will see one provider in the
processaction; merchants with several can configure a cascade in the rule'smembers, and the recommendation reflects the provider(s) to attempt.
sequenceDiagram
participant M as Your platform
participant F as Fraud provider
participant D as DEUNA Recommendation Engine
M->>F: score request (if used)
F-->>M: score + decision
M->>D: One POST /routing/v1/triggers (txn data + fraud result)
D-->>M: recommendation (auth route + failover + actions)
Note over M: Run recommended auth use failover if primary down
Note over M: Apply actions - process with payment provider, or decline
M->>D: Two POST /routing/v1/feedback (auth result + payment result)
D-->>M: 200 OK (outcome recorded, prediction ML labeled)
Mode 1 response — the full plan
The /triggers response is a single, self-contained recommendation: the authentication to run (with an optional failover) and the actions that map the authentication result to what to do. Your platform executes this locally.
{
"collection": {
"id": "c7b9e3a2-4d51-4f8a-9b21-6e0f2a1c8d34",
"prediction_id": "pred_xyz789abc"
},
"recommendation": {
"rule_id": 21866,
"rule_label": "High risk → 3DS authentication",
"authentication": {
"primary": { "authentication_type": "3ds_authentication" },
"failover": { "authentication_type": "3ds_data_only" }
},
"actions": {
"on_success_with_liability_shift": { "action": "process", "provider": "acquirer_gateway" },
"default": { "action": "decline" }
}
}
}| Field | Type | Description |
|---|---|---|
collection.id | UUID | Correlation ID for this evaluation. Echo it in /feedback. |
collection.prediction_id | string | ML handle for the recommendation. Echo it in /feedback. |
recommendation.rule_id | integer | The rule that matched (same identifier as the rule object's id). |
recommendation.rule_label | string | Human-readable rule name. |
recommendation.authentication.primary | object | The authentication to run first: { "authentication_type": "3ds_authentication" }. Uses the same authentication_type values as rule members. |
recommendation.authentication.failover | object | null | Backup authentication if the primary is unavailable. |
recommendation.actions | object | Maps the authentication result to an action. Keyed by outcome. |
recommendation.actions.<outcome>.action | enum | process or decline. |
recommendation.actions.<outcome>.provider | string | Present when action = process — the name of the payment provider to use, as configured in your rule (e.g. acquirer_gateway). |
recommendation.actions.default | object | The fallback action applied when no other outcome key matches. |
Outcome keys under actions describe the authentication result they apply to (e.g. on_success_with_liability_shift); default is the catch-all.
Mode 2 — guided (step-by-step)
Set "mode": "guided" on the request. Instead of the full plan, the engine returns the next step and a status, and you drive the flow one step at a time:
status: awaiting_authentication→next_steptells you which authentication to run.status: awaiting_fraud→next_steptells you which fraud check to run.status: completed→ the engine has decided;next_step.actionisprocess(with aprovider) ordecline.
After you perform a step, call /triggers again — echo the collection from the previous response and include that step's result (authentication_result or fraud_result). The engine advances and returns the next step. Repeat until status = completed, then close with /feedback.
sequenceDiagram
participant M as Your platform (merchant / PSP)
participant D as DEUNA Recommendation Engine
M->>D: POST /routing/v1/triggers (mode=guided, txn data)
D-->>M: status=awaiting_authentication · next_step: run 3ds_authentication
Note over M: Run 3DS authentication
M->>D: POST /routing/v1/triggers (collection + authentication_result)
D-->>M: status=completed · next_step: process with acquirer_gateway
Note over M: Process the payment
M->>D: POST /routing/v1/feedback (attempts)
D-->>M: 200 OK
First response — a step to perform
{
"collection": { "id": "c7b9e3a2-4d51-4f8a-9b21-6e0f2a1c8d34", "prediction_id": "pred_xyz789abc" },
"recommendation": {
"rule_id": 21866,
"status": "awaiting_authentication",
"next_step": {
"type": "authentication",
"authentication_type": "3ds_authentication",
"failover": { "authentication_type": "3ds_data_only" }
}
}
}Follow-up request — post the step result
{
"mode": "guided",
"collection": { "id": "c7b9e3a2-4d51-4f8a-9b21-6e0f2a1c8d34", "prediction_id": "pred_xyz789abc" },
"authentication_result": { "eci": "05", "pares_status": "Y", "liability_shift": true }
}Terminal response — status = completed
{
"collection": { "id": "c7b9e3a2-4d51-4f8a-9b21-6e0f2a1c8d34", "prediction_id": "pred_xyz789abc" },
"recommendation": {
"rule_id": 21866,
"status": "completed",
"next_step": { "type": "process", "provider": "acquirer_gateway" }
}
}| Field | Type | Description |
|---|---|---|
mode | enum | Request field: single (default) or guided. |
collection | object | Request field on follow-up guided calls: echo the collection from the previous response to continue the same evaluation. |
authentication_result / fraud_result | object | Request field: the result of the step you just performed. |
recommendation.status | enum | awaiting_authentication, awaiting_fraud, or completed. |
recommendation.next_step.type | enum | authentication, fraud, process, or decline. |
recommendation.next_step.authentication_type | enum | Present when type = authentication. |
recommendation.next_step.failover | object | null | Optional backup for an authentication step. |
recommendation.next_step.provider | string | Present when type = process — the payment provider to use. |
Both modes are backed by the same rules and produce the same decisions — guided mode simply asks DEUNA for one step at a time instead of returning the whole plan up front.
Card identification on /triggers
/triggersThe card in payment_source.card_info can be supplied one of three ways:
| Method | Fields | Notes |
|---|---|---|
| Full PAN | card_number | BIN and brand are derived server-side. |
| BIN + last four | bin (8 digits), last_four | Use when you don't transmit the full PAN. 8-digit BIN gives issuer-level routing. |
| Network token | network_token: { dpan, par, account_bin, cryptogram, eci } | For tokenized credentials. See the note below on getting BIN-level data. |
Network tokens & BIN-level routing. A network token (DPAN) has its own token BIN, which is not the funding card's BIN. To route on the real issuer BIN, send the funding account BIN (account_bin, the 8-digit BIN your tokenization platform exposes for the underlying PAN) and/or the PAR (Payment Account Reference) so DEUNA can correlate the token to the funding account. The bare DPAN alone is not enough for issuer-BIN rules.
Feedback attempts example
attempts exampleattempts reports what actually happened: the authentication result, then the payment result from your payment provider (ISO 8583 codes).
{
"collection": { "id": "c7b9e3a2-4d51-4f8a-9b21-6e0f2a1c8d34", "prediction_id": "pred_xyz789abc" },
"transaction_id": "1b3d5f7a-8c9e-40a2-b1c3-d4e5f6a7b8c9",
"attempts": [
{
"step": "authentication",
"authentication_type": "3ds_authentication",
"result": { "eci": "05", "pares_status": "Y", "liability_shift": true }
},
{
"step": "payment",
"provider": "acquirer_gateway",
"result": { "status": "declined", "error_code": "05", "error_reason": "do_not_honor" }
}
]
}
error_codeuses ISO 8583 response codes — e.g."05"= do not honor.eciandpares_statuscarry the 3DS outcome;liability_shiftindicates whether the authentication shifted liability.
Backup authentication (failover)
Rules can define a failover authentication provider. If the recommended primary provider is unavailable (e.g. the 3DS Authentication provider is down), the recommendation returns the failover (e.g. 3DS Data Only) so a provider outage never leaves a transaction un-authenticated.
The full/routing/v1/triggersand/routing/v1/feedbackrequest/response contracts are documented in their own guides; this reference covers rule management.
Questions or missing details? Contact your DEUNA integration team.
Updated about 16 hours ago