Skip to main content
A validation came back DENY or REVIEW and someone is asking why. This guide takes you from the decision Tracer returned to a named rule or a named spending limit. From there it reaches the stored record and the entry you can hand an auditor months later. What changes in your operation: the answer to “why was this blocked?” stops being a log search. Each decision arrives with the identifiers of what produced it. The same record answers by id years later, and its audit event can be checked against the hash chain.
Who is this guide for? Developers integrating the validation call, support and disputes teams answering customer questions, and compliance officers preparing evidence. Steps 1 to 4 need only the response you already have; steps 5 to 8 use the query endpoints.

Before you start


  • Tracer running and reachable, with an API key — see Getting started
  • A DENY or REVIEW response to work from, or the validationId of one
  • Familiarity with what rules and limits do — see the Rules engine and Spending limits
All calls below send the API key as X-API-Key.

Step 1: Read the decision Tracer returned


POST /v1/validations answers with the full decision. A new request answers 201; repeating a requestId answers 200 with the decision Tracer already recorded for that key.
A denial produced by a rule looks like this:
For the full request and response schema, see Validate a transaction.
Store the validationId next to your own transaction record. It is the key Step 5 takes, and it is also the resourceId of the audit event in Step 7.

Step 2: Tell a rule denial from a limit denial


Read reason first. It names what produced the decision, and it tells you which of the next two steps to take. reason carries the same text on REVIEW decisions — Rule matched with REVIEW action — and Step 3 reads a review the same way it reads a denial.
On a rule denial, limitUsageDetails comes back empty because Tracer stops before the limit check — not because no limit applies to the account.

Step 3: Name the rule


matchedRuleIds lists every rule that matched, whatever action each one carries — a DENY rule and an ALLOW rule can both appear on the same denial. Retrieve each one and read its action to find the rule behind the decision:
The rule carries the name, description, expression, action, and scopes a colleague needs to see why it fired — see Retrieve a rule. Compare matchedRuleIds with evaluatedRuleIds when the question is the opposite one — “why did my rule not fire?”. A rule absent from evaluatedRuleIds was not evaluated for this transaction, so start with its status and its scope rather than its expression.
A rule deleted since the decision no longer answers on GET /v1/rules/{id}. Its history, including who deleted it, stays in the audit trail — see Audit and compliance.

Step 4: Name the spending limit


On a limit_exceeded denial, limitUsageDetails holds one entry per limit Tracer checked, and the entries marked "exceeded": true are the ones the amount would push past:
Read the exceeded entry as the arithmetic of the denial: attemptedAmount against limitAmount, with currentUsage reporting what that limit’s current period and matched scope would hold if this transaction were allowed. In the entry above, a 1500 purchase would take a 50000 daily cap to 51500. A PER_TRANSACTION limit keeps no count, so its entry reports currentUsage as 0 and attemptedAmount against limitAmount is the whole comparison. GET /v1/limits/{limitId} gives the limit’s current name and configuration — see Retrieve a limit. The decision record keeps the ceiling, period, and scope as they were when the decision was made, so a limit changed since then does not change what the record says. For how each period counts and how consumption accumulates, see Spending limits.

Step 5: Retrieve the record later


Every decision is stored under its validationId:
The record answers with the transaction context Tracer evaluated — transactionType, amount, currency, transactionTimestamp, account, and the optional segment, portfolio, merchant, and metadata — plus the same decision, reason, matchedRuleIds, evaluatedRuleIds, and limitUsageDetails the original response carried, and a createdAt. See Retrieve a validation. This is the record to read out in a dispute: it is the input and the outcome in one document, and it does not change when rules or limits change afterwards.

Step 6: Find records when you do not have the id


GET /v1/validations lists stored decisions, newest first, with cursor pagination:
A query with no dates covers the last 90 days, not the full retention period. Tracer applies that default window only when both start_date and end_date are absent. Send either one — or both — to reach an older range.
Two filters answer the questions this guide exists for:
  • matched_rule_id={ruleId} — every stored decision this rule matched
  • exceeded_limit_id={limitId} — every stored decision this limit stopped
Each result is a summary: validationId, decision, reason, amount, currency, transactionType, accountId, matchedRuleIds, exceededLimitIds, processingTimeMs, and createdAt. Take the validationId of the one you want and retrieve it with Step 5 for the full record. See List validations for every filter and the pagination fields.

Step 7: Pull the audit event behind the decision


The audit trail records the decision under the validationId as the event’s resourceId:
What the audit event adds to the validation record:
The 90-day default window applies here too: GET /v1/audit-events with neither start_date nor end_date covers the last 90 days. Send the range you want when the decision is older than that.
The same endpoint carries the lifecycle of rules and limits — who created, activated, or deleted them. See List audit events for the filters, and Audit and compliance for the event types and retention periods.

Step 8: Verify the audit event in an audit


Pass the eventId to the verification endpoint:
isValid: true establishes that every record from the first one through the one you named still matches the hash stored with it. Each also links to the hash of the record before it, so within that span no record was removed, re-ordered, or re-dated. totalChecked reports how many records the check covered. On a failed check, isValid is false, message reports tampering, and firstInvalidId carries an internal sequence number for the diverging record. That number is not an audit-event id, so it is not a value to pass to GET /v1/audit-events/{id}. See Verify an audit event.
Hand over the two together: the retrieved event from Step 7 is the content of the decision, and the verification result is the evidence that the chain holding it is intact. The verification call reports on the chain — it does not return the record.

Common pitfalls


What usually goes wrong in a review:
  • “My query for last year came back empty.” A query with no start_date and no end_date covers the last 90 days. Send the range you want.
  • matchedRuleIds has three entries and only one denied.” The array holds every rule that matched, whatever action it carries. Retrieve each rule and read its action (Step 3).
  • limitUsageDetails is empty on a denial.” The decision came from a rule, not a limit. Read reason (Step 2).
  • currentUsage is higher than what the customer actually spent.” It is the projected figure, with the attempted amount already added. On an exceeded limit the counter was not incremented, so the stored consumption does not include this transaction.
  • “The rule that fired no longer exists.” Deleted rules stop answering on GET /v1/rules/{id}. Query their lifecycle through GET /v1/audit-events?resource_type=rule&resource_id={ruleId}.

Error codes

The complete list is in the Tracer error list.

Quick reference