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 URLs

Production: 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 examples

The 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
HeaderRequiredDescription
X-Api-KeyYesYour API key. Determines the environment and the merchant scope.
Content-TypeYesapplication/json.
X-Idempotency-KeyRecommendedA 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

MethodPathPurpose
GET/routing/v1/rulesList all rules (priority order)
POST/routing/v1/rulesCreate 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}/reorderChange a rule's priority
🚧

There is no delete endpoint

Rules are never destroyed. To take a rule out of production, update its status to disabled. This preserves the rule and its audit history and lets you re-enable it later.

👥

Roles & permissions

Rule 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 GET rules but cannot create, update, reorder or disable them. This keeps rule changes least-privilege and auditable.

Path parameters:

ParameterTypeNotes
rule_idintegerNumeric rule identifier returned by create/list.

The Rule object

A rule is the core resource returned and accepted by these endpoints.

FieldTypeRequiredDescription
idintegerResponse onlyServer-assigned rule identifier.
labelstringYesHuman-readable rule name.
data_typestringYesMethod family this rule applies to: credit_card, debit_card, prepaid_card.
priorityintegerYesEvaluation order — lower runs first, first match wins (non-negative numbers)
statusenumYesenabled, disabled, draft
is_defaultbooleanYesIf true, this is the fallback rule when no other matches. A default rule must have no conditions and must not set ignore_next_rules.
triggerenumYespayment, reject, or merchant_rule. See Triggers.
conditionsarray<Condition>ConditionalMatch criteria, AND-combined. Required unless is_default = true.
membersarray<Member>ConditionalOrdered providers to attempt. Required for payment; required (or children) for merchant_rule; forbidden for reject.
childrenarray<Child>OptionalWeighted child branches for A/B testing (only with trigger = merchant_rule).
ignore_next_rulesbooleanYesIf true, stop evaluating further rules once this one matches. Forbidden for reject and for default rules.
created_atstring (RFC3339)Response onlyCreation timestamp.

Triggers

triggerMeaningmemberschildrenignore_next_rulesis_default
paymentRoute the payment to the listed providers.Required (≥1)Not allowedAllowedAllowed
rejectBlock the transaction outright.Not allowedNot allowedNot allowedNot allowed
merchant_ruleGroup/branch: route via members or weighted children.Required if no childrenAllowedAllowedAllowed

The Condition object

Conditions define what a transaction must match. All conditions on a rule are combined with AND.

FieldTypeRequiredDescription
idintegerResponse onlyServer-assigned condition ID.
rule_optionobjectYesThe field to match: { "id": 5, "label": "currency" }. id must be > 0. See Rule Options.
operatorenumYesComparison operator. Must be valid for that rule option
operandstringConditionalThe 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_typestringNovalues (default) or list (operand references a custom list by UUID).
operand_configobject | nullConditionalUsed when operand_type = list: { "custom_list": { "field_to_evaluate": "email" } }.
metadata_field_namestring | nullConditionalRequired when rule_option.id = 16 (metadata). The metadata key to evaluate.
metadata_field_typestring | nullConditionalRequired when rule_option.id = 16. One of text, numeric.
error_codestringResponse onlySet when async processing of a condition (e.g. list import) failed.
error_messagestringResponse onlyHuman-readable detail for error_code.
⚠️

Each rule_option may appear only once per rule — except metadata, which may repeat with different metadata_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.

FieldTypeRequiredDescription
payment_provider_idintegerConditionalDEUNA payment provider ID. Required if merchant_payment_provider_id is present.
payment_provider_namestringResponse onlyProvider name (echoed back).
merchant_payment_provider_idUUIDConditionalMerchant's specific provider connection. If sent, payment_provider_id must also be sent.
merchant_payment_provider_namestringResponse onlyConnection name (echoed back).
fraud_providerstringConditionalFraud/anti-fraud provider name. Mutually exclusive with payment- and authentication-provider fields.
fraud_provider_idstringResponse onlyFraud provider ID (echoed back).
authentication_providerstringConditionalAuthentication provider name (e.g. UNICO_ID, CYBERSOURCE_3DS). Mutually exclusive with payment- and fraud-provider fields.
authentication_provider_idstringResponse onlyAuthentication provider ID (echoed back).
authentication_typeenumConditionalThe authentication method. One of 3ds_authentication, 3ds_data_only, unico_id. Required when the member is an authentication provider.
failoverobject | nullOptionalBackup authentication used when the primary provider is unavailable: { "authentication_provider": "...", "authentication_type": "..." }. Valid only on authentication-provider members.
sortintegerYesCascade order. Must be unique within the rule.
strategyenumYesCurrently only cascade.
capabilitiesarray<string>YesProvider capabilities to use, e.g. ["3ds"]. Send [] if none.
enabled3dsbooleanNoRequest 3DS authentication on this member (create payload).
post_authorizationbooleanYesOnly a fraud provider may set true, and it must be the last member by sort.
shadow_modebooleanYesEvaluate the provider without affecting the route (safe trial).
enabledbooleanResponse onlyWhether 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.

FieldTypeRequiredDescription
weightintegerYesPercentage of matching traffic sent to this route. The two routes' weights must sum to 100.
membersarray<Member>YesThe provider(s) for this route.

Rules:

  • Exactly two routes.
  • weight values 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 /triggers response.

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

{
  "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

{
  "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 paramTypeDescription
limitintegerOptional. Page size. Default 50, maximum 200.
cursorstringOptional. Opaque cursor from a previous response's pagination.next_cursor. Omit for the first page.
payment_provider_idintegerOptional. 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 fieldTypeDescription
next_cursorstring | nullCursor for the next page, or null on the last page.
has_morebooleantrue if more rules are available.
limitintegerThe 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

{
  "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

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 }'
FieldTypeRequiredConstraint
priorityintegerYesNon-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

  • status must be one of enabled, disabled, draft, process-in-background.
  • trigger must be one of payment, reject, merchant_rule.
  • trigger = payment → at least one member; no children.
  • trigger = reject → no members, no children, ignore_next_rules must be false, is_default must be false.
  • trigger = merchant_rule → at least one member or at least one child.
  • is_default = true → no conditions and ignore_next_rules must be false.
  • is_default = false → at least one condition.
  • priority must be > 0 and ≤ 1000.

Conditions

  • rule_option.id required (> 0); operand required (non-empty).
  • operator must be valid for that rule option (see catalog).
  • A rule_option may appear only once per rule (except metadata).
  • For metadata (id 16): metadata_field_name and metadata_field_type required; type must be text or numeric.
  • For operand_type = list: operand must 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_id is present, payment_provider_id must also be present.
  • Authentication members require authentication_type (one of 3ds_authentication, 3ds_data_only, unico_id).
  • failover is valid only on authentication members; its authentication_type must also be a valid value.
  • sort must be unique within the rule.
  • A provider may appear only once per rule.
  • strategy must be cascade.
  • post_authorization = true only for fraud providers, and that member must be last by sort.

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_id must belong to the given payment_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'."
    }
  ]
}
FieldTypeDescription
codestringStable, machine-readable top-level code. Branch on this, never on message.
messagestringHuman-readable summary. May be reworded or localized — do not parse it.
request_idstringUnique per response and also returned in the X-Request-Id response header (on success and error). Quote it in any support request.
errorsarrayPresent only for PAYROU-VALIDATION. One entry per offending field.
errors[].fieldstringPath to the field, using dot/bracket notation — e.g. priority, conditions[1].operand, members[0].sort.
errors[].codestringStable field-level code (see validation codes).
errors[].messagestringHuman-readable detail. Do not parse.

Always send an X-Idempotency-Key on writes. Replaying the same key returns the original response; reusing a key with a different body returns 409 PAYROU-CONFLICT.

HTTP status codes

StatusMeaning
200 / 201Success.
400The request is invalid — validation failed, malformed JSON, or bad path/query params.
401Authentication failed — the X-Api-Key is missing or invalid.
403Authorization failed — the key is valid but not allowed to access this merchant/resource.
404The rule or a referenced resource does not exist.
409Conflict — idempotency-key reuse with a different body, or a concurrent update.
429Rate limit exceeded — retry after the Retry-After header.
500Unexpected server error.
503A downstream dependency is temporarily unavailable — safe to retry with backoff.

Top-level codes

CodeHTTPWhen
PAYROU-VALIDATION400One or more fields failed validation. Details in errors[].
PAYROU-MALFORMED_JSON400Request body is not valid JSON.
PAYROU-INVALID_PATH_PARAM400A path parameter is the wrong type/format (e.g. non-integer rule_id).
PAYROU-INVALID_QUERY_PARAM400A query parameter is invalid (e.g. limit out of range, malformed cursor).
PAYROU-UNAUTHENTICATED401Missing or invalid API key.
PAYROU-FORBIDDEN403Valid key, but not scoped to this merchant/resource.
PAYROU-NOT_FOUND404Rule or referenced resource not found.
PAYROU-CONFLICT409Idempotency-key reuse with a different payload, or concurrent modification.
PAYROU-RATE_LIMITED429Too many requests.
PAYROU-INTERNAL500Unexpected server error.
PAYROU-UPSTREAM_UNAVAILABLE503A 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

codeTypical fieldCause
INVALID_STATUSstatusNot one of enabled, disabled, draft, process-in-background.
INVALID_TRIGGERtriggerNot one of payment, reject, merchant_rule.
PRIORITY_OUT_OF_RANGEpriorityNot between 1 and 1000 (covers both ≤ 0 and > 1000).
MEMBERS_REQUIREDmemberstrigger = payment with no members.
MEMBERS_OR_CHILDREN_REQUIREDmemberstrigger = merchant_rule with neither members nor children.
CONDITIONS_REQUIREDconditionsNon-default rule with no conditions.
TRIGGER_CANNOT_BE_DEFAULTis_defaultreject rule with is_default = true.
TRIGGER_CANNOT_HAVE_MEMBERSmembersreject rule with members.
TRIGGER_CANNOT_HAVE_CHILDRENchildrenpayment/reject rule with children.
TRIGGER_CANNOT_IGNORE_NEXT_RULESignore_next_rulesreject rule with ignore_next_rules = true.
DEFAULT_RULE_CANNOT_HAVE_CONDITIONSconditionsDefault rule with conditions.
DEFAULT_RULE_CANNOT_IGNORE_NEXT_RULESignore_next_rulesDefault rule with ignore_next_rules = true.

Conditions

codeTypical fieldCause
RULE_OPTION_ID_REQUIREDconditions[i].rule_option.idMissing or ≤ 0.
OPERAND_REQUIREDconditions[i].operandEmpty operand for an operator that requires one.
OPERATOR_REQUIREDconditions[i].operatorOperator is empty.
INVALID_OPERATORconditions[i].operatorOperator not valid for that rule option.
DUPLICATE_RULE_OPTIONconditions[i].rule_optionSame rule option used more than once (except metadata).
METADATA_FIELDS_REQUIREDconditions[i].metadata_field_namemetadata condition missing metadata_field_name/metadata_field_type.
INVALID_METADATA_FIELD_TYPEconditions[i].metadata_field_typeNot text or numeric.
RULE_OPTION_NOT_FOUNDconditions[i].rule_option.idRule option does not exist.
INVALID_LIST_REFERENCEconditions[i].operandoperand_type = list but the list UUID is invalid or not found.

Members

codeTypical fieldCause
MEMBER_PROVIDER_REQUIREDmembers[i]Member references no provider (payment, fraud or authentication).
MEMBER_MULTIPLE_PROVIDER_TYPESmembers[i]Member mixes provider types (e.g. payment + fraud, or payment + authentication).
PAYMENT_PROVIDER_ID_REQUIREDmembers[i].payment_provider_idmerchant_payment_provider_id sent without payment_provider_id.
AUTHENTICATION_TYPE_REQUIREDmembers[i].authentication_typeAuthentication member without an authentication_type.
INVALID_AUTHENTICATION_TYPEmembers[i].authentication_typeNot one of 3ds_authentication, 3ds_data_only, unico_id (applies to failover.authentication_type too).
FAILOVER_ONLY_ON_AUTHENTICATIONmembers[i].failoverfailover set on a non-authentication member.
DUPLICATE_MEMBERmembers[i]Same provider appears more than once in the rule.
DUPLICATE_MEMBER_SORTmembers[i].sortTwo members share a sort value.
INVALID_STRATEGYmembers[i].strategyNot cascade.
POST_AUTH_ONLY_FRAUDmembers[i].post_authorizationpost_authorization = true on a non-fraud member.
POST_AUTH_MUST_BE_LASTmembers[i].post_authorizationThe post_authorization member is not last by sort.
PROVIDER_NOT_AVAILABLEmembers[i]Provider (payment, fraud or authentication) is not enabled for this merchant.
MERCHANT_PROVIDER_MISMATCHmembers[i].merchant_payment_provider_idConnection does not belong to the given payment_provider_id.

A/B testing

codeTypical fieldCause
SPLIT_WEIGHTS_MUST_SUM_TO_100childrenChild 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.

ModeHow it worksBest 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

  1. (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_risk option or metadata).
  2. Request the recommendation (one call). POST /routing/v1/triggers with the transaction data (and the fraud result, if any). The response contains an authentication block (primary + optional failover) and an actions block.
  3. Authenticate. Run the recommended authentication (e.g. 3DS Authentication, 3DS Data Only, or Unico ID). If the primary provider is unavailable, use the failover.
  4. Decide and process. Apply actions: each outcome maps to process (with the payment provider named in the action) or decline; the default action applies when no other outcome matches.
  5. Report the outcome. POST /routing/v1/feedback with the collection object and the attempts (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 process action; merchants with several can configure a cascade in the rule's members, 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" }
    }
  }
}
FieldTypeDescription
collection.idUUIDCorrelation ID for this evaluation. Echo it in /feedback.
collection.prediction_idstringML handle for the recommendation. Echo it in /feedback.
recommendation.rule_idintegerThe rule that matched (same identifier as the rule object's id).
recommendation.rule_labelstringHuman-readable rule name.
recommendation.authentication.primaryobjectThe authentication to run first: { "authentication_type": "3ds_authentication" }. Uses the same authentication_type values as rule members.
recommendation.authentication.failoverobject | nullBackup authentication if the primary is unavailable.
recommendation.actionsobjectMaps the authentication result to an action. Keyed by outcome.
recommendation.actions.<outcome>.actionenumprocess or decline.
recommendation.actions.<outcome>.providerstringPresent when action = process — the name of the payment provider to use, as configured in your rule (e.g. acquirer_gateway).
recommendation.actions.defaultobjectThe 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_authenticationnext_step tells you which authentication to run.
  • status: awaiting_fraudnext_step tells you which fraud check to run.
  • status: completed → the engine has decided; next_step.action is process (with a provider) or decline.

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" }
  }
}
FieldTypeDescription
modeenumRequest field: single (default) or guided.
collectionobjectRequest field on follow-up guided calls: echo the collection from the previous response to continue the same evaluation.
authentication_result / fraud_resultobjectRequest field: the result of the step you just performed.
recommendation.statusenumawaiting_authentication, awaiting_fraud, or completed.
recommendation.next_step.typeenumauthentication, fraud, process, or decline.
recommendation.next_step.authentication_typeenumPresent when type = authentication.
recommendation.next_step.failoverobject | nullOptional backup for an authentication step.
recommendation.next_step.providerstringPresent 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:

MethodFieldsNotes
Full PANcard_numberBIN and brand are derived server-side.
BIN + last fourbin (8 digits), last_fourUse when you don't transmit the full PAN. 8-digit BIN gives issuer-level routing.
Network tokennetwork_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 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_code uses ISO 8583 response codes — e.g. "05" = do not honor. eci and pares_status carry the 3DS outcome; liability_shift indicates 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/triggers and /routing/v1/feedback request/response contracts are documented in their own guides; this reference covers rule management.


Questions or missing details? Contact your DEUNA integration team.



Did this page help you?