Recommendation engine
This guide documents the merchant-facing API for managing rules in the DEUNA Recommendation Engine. A rule combines conditions with an ordered list of payment, fraud, or authentication providers and optional weighted child rules for A/B testing. Rules are evaluated by priority until one matches.
On this page
Recommendation engine
Set the ordered provider path evaluated for a matching rule.
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. |
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 |
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, for example { "id": 5, "label": "currency" }. Use a rule option available to the merchant; id must be > 0. |
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. |
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 } ] }
]
}'const response = await fetch("https://api.sandbox.deuna.io/routing/v1/rules", {
method: "POST",
headers: {
"X-Api-Key": "{{API KEY}}",
"X-Idempotency-Key": "b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1",
"Content-Type": "application/json"
},
body: JSON.stringify({
"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
}
]
}
]
})
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"POST",
"https://api.sandbox.deuna.io/routing/v1/rules",
headers={
"X-Api-Key": "{{API KEY}}",
"X-Idempotency-Key": "b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1",
"Content-Type": "application/json"
},
json={
"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.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/routing/v1/rules",
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {{API KEY}}",
"X-Idempotency-Key: b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"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 = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/routing/v1/rules"))
.header("X-Api-Key", "{{API KEY}}")
.header("X-Idempotency-Key", "b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\n \"label\": \"MX Mastercard — Cybersource\",\n \"priority\": 2,\n \"status\": \"enabled\",\n \"trigger\": \"merchant_rule\",\n \"is_default\": false,\n \"data_type\": \"credit_card\",\n \"ignore_next_rules\": true,\n \"conditions\": [\n { \"rule_option\": { \"id\": 3, \"label\": \"branch\" }, \"operator\": \"in\", \"operand\": \"mastercard\" },\n { \"rule_option\": { \"id\": 5, \"label\": \"currency\" }, \"operator\": \"in\", \"operand\": \"MXN\" }\n ],\n \"members\": [\n { \"sort\": 1, \"strategy\": \"cascade\", \"fraud_provider\": \"CYBERSOURCE\" }\n ],\n \"children\": [\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"high\" } ], \"members\": [] },\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"medium\" } ],\n \"members\": [ { \"sort\": 1, \"strategy\": \"cascade\", \"payment_provider_id\": 45, \"merchant_payment_provider_id\": \"6e4c651a-3e5f-403d-af9d-6a9e9299acb8\", \"capabilities\": [\"3ds\"], \"enabled3ds\": false } ] },\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"low\" } ],\n \"members\": [ { \"sort\": 1, \"strategy\": \"cascade\", \"payment_provider_id\": 45, \"merchant_payment_provider_id\": \"6e4c651a-3e5f-403d-af9d-6a9e9299acb8\", \"capabilities\": [\"3ds\"], \"enabled3ds\": false } ] }\n ]\n }"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
request, err := http.NewRequest("POST", "https://api.sandbox.deuna.io/routing/v1/rules", strings.NewReader("{\n \"label\": \"MX Mastercard — Cybersource\",\n \"priority\": 2,\n \"status\": \"enabled\",\n \"trigger\": \"merchant_rule\",\n \"is_default\": false,\n \"data_type\": \"credit_card\",\n \"ignore_next_rules\": true,\n \"conditions\": [\n { \"rule_option\": { \"id\": 3, \"label\": \"branch\" }, \"operator\": \"in\", \"operand\": \"mastercard\" },\n { \"rule_option\": { \"id\": 5, \"label\": \"currency\" }, \"operator\": \"in\", \"operand\": \"MXN\" }\n ],\n \"members\": [\n { \"sort\": 1, \"strategy\": \"cascade\", \"fraud_provider\": \"CYBERSOURCE\" }\n ],\n \"children\": [\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"high\" } ], \"members\": [] },\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"medium\" } ],\n \"members\": [ { \"sort\": 1, \"strategy\": \"cascade\", \"payment_provider_id\": 45, \"merchant_payment_provider_id\": \"6e4c651a-3e5f-403d-af9d-6a9e9299acb8\", \"capabilities\": [\"3ds\"], \"enabled3ds\": false } ] },\n { \"conditions\": [ { \"rule_option\": { \"id\": 15 }, \"operator\": \"eq\", \"operand\": \"low\" } ],\n \"members\": [ { \"sort\": 1, \"strategy\": \"cascade\", \"payment_provider_id\": 45, \"merchant_payment_provider_id\": \"6e4c651a-3e5f-403d-af9d-6a9e9299acb8\", \"capabilities\": [\"3ds\"], \"enabled3ds\": false } ] }\n ]\n }"))
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "{{API KEY}}")
request.Header.Set("X-Idempotency-Key", "b3f1c2d4-9a8e-4c11-bb77-2e5f9c74a6b1")
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}Response 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}}'const response = await fetch("https://api.sandbox.deuna.io/routing/v1/rules/21866", {
method: "GET",
headers: {
"X-Api-Key": "{{API KEY}}"
}
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"GET",
"https://api.sandbox.deuna.io/routing/v1/rules/21866",
headers={
"X-Api-Key": "{{API KEY}}"
},
)
response.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/routing/v1/rules/21866",
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {{API KEY}}"
]
]);
$response = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/routing/v1/rules/21866"))
.header("X-Api-Key", "{{API KEY}}")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
)
func main() {
request, err := http.NewRequest("GET", "https://api.sandbox.deuna.io/routing/v1/rules/21866", nil)
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "{{API KEY}}")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}Response 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}}'const response = await fetch("https://api.sandbox.deuna.io/routing/v1/rules?limit=50", {
method: "GET",
headers: {
"X-Api-Key": "{{API KEY}}"
}
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"GET",
"https://api.sandbox.deuna.io/routing/v1/rules?limit=50",
headers={
"X-Api-Key": "{{API KEY}}"
},
)
response.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/routing/v1/rules?limit=50",
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {{API KEY}}"
]
]);
$response = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/routing/v1/rules?limit=50"))
.header("X-Api-Key", "{{API KEY}}")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
)
func main() {
request, err := http.NewRequest("GET", "https://api.sandbox.deuna.io/routing/v1/rules?limit=50", nil)
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "{{API KEY}}")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}Response 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 }
]
}'const response = await fetch("https://api.sandbox.deuna.io/routing/v1/rules/21866", {
method: "PUT",
headers: {
"X-Api-Key": "{{API KEY}}",
"X-Idempotency-Key": "7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c",
"Content-Type": "application/json"
},
body: JSON.stringify({
"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
}
]
})
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"PUT",
"https://api.sandbox.deuna.io/routing/v1/rules/21866",
headers={
"X-Api-Key": "{{API KEY}}",
"X-Idempotency-Key": "7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c",
"Content-Type": "application/json"
},
json={
"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.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/routing/v1/rules/21866",
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: {{API KEY}}",
"X-Idempotency-Key: 7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"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 = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/routing/v1/rules/21866"))
.header("X-Api-Key", "{{API KEY}}")
.header("X-Idempotency-Key", "7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c")
.header("Content-Type", "application/json")
.method("PUT", HttpRequest.BodyPublishers.ofString("{\n \"id\": 21866,\n \"created_at\": \"2026-07-08T01:07:29.439794Z\",\n \"label\": \"MX Mastercard — Cybersource\",\n \"priority\": 2,\n \"status\": \"disabled\",\n \"trigger\": \"merchant_rule\",\n \"is_default\": false,\n \"data_type\": \"credit_card\",\n \"ignore_next_rules\": true,\n \"conditions\": [\n { \"id\": 44264, \"rule_option\": { \"id\": 3, \"label\": \"branch\" }, \"operator\": \"in\", \"operand\": \"mastercard\", \"operand_type\": \"values\" },\n { \"id\": 44265, \"rule_option\": { \"id\": 5, \"label\": \"currency\" }, \"operator\": \"in\", \"operand\": \"MXN\", \"operand_type\": \"values\" }\n ],\n \"members\": [\n { \"sort\": 1, \"strategy\": \"cascade\", \"fraud_provider\": \"CYBERSOURCE\", \"capabilities\": [], \"post_authorization\": false, \"shadow_mode\": false }\n ]\n }"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
request, err := http.NewRequest("PUT", "https://api.sandbox.deuna.io/routing/v1/rules/21866", strings.NewReader("{\n \"id\": 21866,\n \"created_at\": \"2026-07-08T01:07:29.439794Z\",\n \"label\": \"MX Mastercard — Cybersource\",\n \"priority\": 2,\n \"status\": \"disabled\",\n \"trigger\": \"merchant_rule\",\n \"is_default\": false,\n \"data_type\": \"credit_card\",\n \"ignore_next_rules\": true,\n \"conditions\": [\n { \"id\": 44264, \"rule_option\": { \"id\": 3, \"label\": \"branch\" }, \"operator\": \"in\", \"operand\": \"mastercard\", \"operand_type\": \"values\" },\n { \"id\": 44265, \"rule_option\": { \"id\": 5, \"label\": \"currency\" }, \"operator\": \"in\", \"operand\": \"MXN\", \"operand_type\": \"values\" }\n ],\n \"members\": [\n { \"sort\": 1, \"strategy\": \"cascade\", \"fraud_provider\": \"CYBERSOURCE\", \"capabilities\": [], \"post_authorization\": false, \"shadow_mode\": false }\n ]\n }"))
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "{{API KEY}}")
request.Header.Set("X-Idempotency-Key", "7c2e5a19-4b8d-4f10-9a3c-1d2e3f4a5b6c")
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}Response 200
Returns 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 }'const response = await fetch("https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder", {
method: "PUT",
headers: {
"X-Api-Key": "sk_sandbox_9f8b2c1a7d4e60b3",
"Content-Type": "application/json"
},
body: JSON.stringify({
"priority": 2
})
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();import requests
response = requests.request(
"PUT",
"https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder",
headers={
"X-Api-Key": "sk_sandbox_9f8b2c1a7d4e60b3",
"Content-Type": "application/json"
},
json={
"priority": 2
},
)
response.raise_for_status()
data = response.json()<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder",
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Api-Key: sk_sandbox_9f8b2c1a7d4e60b3",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"priority" => 2
])
]);
$response = curl_exec($curl);
curl_close($curl);import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder"))
.header("X-Api-Key", "sk_sandbox_9f8b2c1a7d4e60b3")
.header("Content-Type", "application/json")
.method("PUT", HttpRequest.BodyPublishers.ofString("{ \"priority\": 2 }"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());
}
}package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
request, err := http.NewRequest("PUT", "https://api.sandbox.deuna.io/routing/v1/rules/21873/reorder", strings.NewReader("{ \"priority\": 2 }"))
if err != nil { panic(err) }
request.Header.Set("X-Api-Key", "sk_sandbox_9f8b2c1a7d4e60b3")
request.Header.Set("Content-Type", "application/json")
response, err := http.DefaultClient.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
body, _ := io.ReadAll(response.Body)
fmt.Println(string(body))
}| Field | Type | Required | Constraint |
|---|---|---|---|
priority | integer | Yes | Non-negative integer |
Response 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.
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.
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
The 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. |
Feedback attempts example
attempts 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.
Questions or missing details? Contact your DEUNA integration team.