Skip to main content
A decision is the result of evaluating a payload against an active ruleset. Request one with POST /api/decisions: Specter returns an outcome and records the decision for later retrieval, inspection, and reporting.

Outcomes

Every decision resolves to one of four outcomes. Specter is designed to fail open: if a backend errors or times out and the rule’s on_error is allow, Specter skips that backend rather than blocking the customer. See the rule engine.

The decision request

Every decision request has the same envelope, whatever schema it is decided against:
string
default:"default"
Selects the active ruleset — and through it, the schema the rest of the body is validated against. Always accepted; never part of the schema itself.
Everything else is the payload the bound schema declares. Keys the schema does not declare are dropped before evaluation, values are checked against their declared type, and a failure rejects the whole request with 422 naming the path — without echoing the value of a regulated field.

The transaction payload

These are the fields of the built-in transaction schema, the payload every instance understands. A context whose ruleset binds a custom schema takes the fields of that schema here instead. Three are required; the rest are optional but enrich the evaluation. Fields that carry regulated data are marked in the API reference, and the marker — not the field’s name — governs how Specter validates, redacts, and retains the value.
object
required
Payment credential — type: "pan", "masked_pan", "sepa", or "paypal". A pan credential is only accepted on an SAQ_D or ROC instance; on the default SAQ_A level it is rejected — see Instance configuration.
object
required
Must contain id.
object
required
Must contain reference, amount (minor units), and currency (ISO 4217).
object
IP address, fingerprint, user agent, session, and language.
object
Addresses forwarded to applicable backends.
object
Addresses forwarded to applicable backends.
array
Cart line items; each needs at least name or sku.
object
Travel data (passengers, legs) forwarded to backends that consume it.
object
Flat string key-value pairs, available to rules as $.metadata.*. Forwarded to backends and stored on the decision record, so treat it as data you share with your backend providers.
See API integration for the full schema.

The decision response

string
The outcome.
object
The (name, version) the decision was evaluated against — the binding of the ruleset that served it. Version is absent for the built-in transaction schema.
string
The ruleset that served the decision.
integer
The exact version pinned to the decision. Evidence replay uses this version, not whatever is ACTIVE later.
array
Rules that fired, in evaluation order.
array
One entry per executed backend rule, including any error such as insufficient_context. Present when the ruleset includes backend rules.
string
HMAC-SHA256 identifier for the payment credential.

The decision record

Every decision is recorded. Raw credential identifiers (PAN, IBAN, PayPal email) are never stored: the record keeps only the credential fingerprint and a masked form. Retrieve a past decision with GET /api/decisions/{id}; it carries the same schema, ruleset_id, and ruleset_version attribution as the response, so you can tell which payload type and which rules produced it.

Retention

Your instance configures a retention window, in days, per classification marker (pii, pci-pan, pci-sad). Once a window has passed, every field of that scope reads as null on the record — for pii that is customer_id, device_ip, and device_fingerprint — while the rest of the record is untouched. any-scoped fields (amount, reference, masked credential, credential fingerprint) are never aged out, so an old decision can still be correlated. A scope without a window is retained without bound. The boundary is enforced on every read, not only when the background sweep runs. backend_results[].raw_response is the backend’s own shape, so no marker resolves inside it: it is set to null on the first configured window to expire. backend_reference is held outside the body and survives.

Evidence and replay

Two endpoints, both under decisions:read, answer the first question anyone asks about a block — what value made this rule fire:
  • GET /api/decisions/{id}/payload returns the request as it stood when the decision was evaluated, redacted per marker at write time: any fields keep their value, pii fields keep their path with a [REDACTED] sentinel, a pci-pan value is stored as its credential fingerprint, and pci-sad is never stored.
  • GET /api/decisions/{id}/evidence returns an evaluation trace in the same shape as a simulation: every rule of the pinned ruleset version with its outcome and, for each rule that fired, the evidence — the resolved operands of a comparison, the count / window / threshold of a velocity rule, the entry a blacklist rule matched. Evidence is replayed (the pinned ruleset re-evaluated against the stored payload, deterministically) where the input is frozen, and captured at decision time where it is not (counters, blacklist matches). Each entry says which, and whether replay reproduced it at full fidelity.
Storage is opt-in per scope and off by default: your instance sets an evidence retention window per classification (any, pii, pci-pan); the pii and pci-pan windows are bounded by the general retention window of that scope, and turning a scope on is not retroactive. A decision with nothing stored answers 200 with retained: false — a normal state, not an error. A decision made by the block-all safeguard pins no ruleset, so its trace is null even when the payload was retained. Because the regulated scopes are reduced at rest, the store holds no raw PAN and no SAD and is available on instances below SAQ_D.

Resolving a REVIEW decision

A REVIEW decision is not final — it asks for a human judgement. Once your team has reviewed the transaction, resolve it to record the accept/reject outcome and forward it to the backends that flagged it:
Requires the decisions:write scope.
  • Only decisions with decision: "REVIEW" can be resolved — others return 422.
  • Each decision can be resolved once — a second attempt returns 409.

Request

string
required
accept or reject.
string
Free-text note stored on the resolution.

Response

The resolution is stored on the decision and returned by subsequent GET /api/decisions/{id} calls. Only backends that produced a REVIEW result participate; Specter forwards the outcome to each through its Link integration, and a backend that does not support resolution is skipped.

Rule engine

How rules combine to produce an outcome.

Lifecycle events

Feed transaction outcomes back to Specter and its backends.