> ## Documentation Index
> Fetch the complete documentation index at: https://developer.hellgate.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Simulate ruleset

> Evaluates a caller-supplied test payload against the persisted ruleset addressed by {id} — any lifecycle state (DRAFT / ACTIVE / INACTIVE) — and returns an evaluation trace. The ruleset's own (schema, version) binding validates the payload and resolves rule field paths; the request carries no schema selector and no context. Requires the dedicated admin:rulesets:simulate scope. The simulation is inert — no decision record, no velocity counter increment, no blacklist push, no webhook, no lifecycle event, no backend invocation. Velocity counters are read live and never written, so two identical simulations may differ; the trace labels counter values as live-read.



## OpenAPI

````yaml /products/specter/openapi.yaml post /api/admin/rulesets/{id}/simulate
openapi: 3.1.0
info:
  contact:
    email: support@hellgate.io
    name: Starfish GmbH & Co. KG
    url: https://hellgate.io/cpa/specter
  description: Composable decision engine and fraud prevention service.
  license:
    name: Hellgate API Terms
    url: https://hellgate.io/terms-and-conditions
  title: Specter API
  version: '1.0'
servers:
  - description: Managed instance of Specter
    url: https://{instance}.{env}.on-hellgate.cloud
    variables:
      instance:
        default: my-instance
        description: Your unique instance slug, provided during onboarding.
      env:
        default: eu1
        description: Deployment environment (currently eu1).
security: []
tags:
  - description: Evaluate transactions and retrieve decision results.
    name: Decisions
  - description: Close decisions that were flagged for manual review.
    name: Resolutions
  - description: Report payment lifecycle events for a prior decision.
    name: Lifecycle Events
  - description: Manage rulesets and their activation lifecycle.
    name: Rulesets
  - description: Register and version custom decision-payload schemas.
    name: Schemas
  - description: Configure interceptors and intercept live payment requests.
    name: Interceptors
  - description: Subscribe to decision and event notifications.
    name: Webhooks
  - description: Manage blocked field values.
    name: Blacklist
paths:
  /api/admin/rulesets/{id}/simulate:
    post:
      tags:
        - Rulesets
      summary: Simulate ruleset
      description: >-
        Evaluates a caller-supplied test payload against the persisted ruleset
        addressed by {id} — any lifecycle state (DRAFT / ACTIVE / INACTIVE) —
        and returns an evaluation trace. The ruleset's own (schema, version)
        binding validates the payload and resolves rule field paths; the request
        carries no schema selector and no context. Requires the dedicated
        admin:rulesets:simulate scope. The simulation is inert — no decision
        record, no velocity counter increment, no blacklist push, no webhook, no
        lifecycle event, no backend invocation. Velocity counters are read live
        and never written, so two identical simulations may differ; the trace
        labels counter values as live-read.
      operationId: admin_rulesets_simulate
      parameters:
        - description: Ruleset ID
          in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimulationRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimulationTrace'
          description: Evaluation trace
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Unauthorized
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: >-
            Forbidden — the token lacks admin:rulesets:simulate, or the payload
            carries a PAN the instance's test-card policy refuses (the same
            refusal the live decision path returns).
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          description: Not found
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrors'
          description: >-
            Payload validation failure — identical semantics to live decision
            ingestion against the same bound (schema, version). Same classifier
            envelope, each entry naming the offending field path and violated
            constraint; offending values of pii / pci-pan / pci-sad scoped
            fields are never echoed. The PCI / SAQ-D instance gate applies
            unchanged — the simulator is not a bypass around it.
      callbacks: {}
      security:
        - bearerAuth: []
components:
  schemas:
    SimulationRequest:
      properties:
        payload:
          description: >-
            The test decision payload. Validates exactly as the live decision
            body does against the ruleset's bound (schema, version). A "context"
            key is ignored — routing is fixed by the ruleset addressed in the
            URL.
          type: object
      required:
        - payload
      title: SimulationRequest
      type: object
    SimulationTrace:
      description: >-
        Result of simulating a ruleset against a test payload. The trace is a
        superset of the decision response's triggered_rules — same entry
        vocabulary, extended to every rule of the ruleset with its outcome and
        the derivations behind it.
      properties:
        backend_results:
          description: >-
            One entry per backend rule that was reached, in the published
            BackendResult shape. Simulation never invokes a backend — decision
            is null, error is "not_simulated" (sibling of "insufficient_context"
            — backend skipped, no call made), latency_ms is null. Where the
            instance has the Link capability disabled, the live path makes no
            call either and records "backend_capability_disabled" with
            latency_ms 0; the trace renders that entry instead, so it reads as
            the live one would.
          items:
            $ref: '#/components/schemas/BackendResult'
          type: array
        context:
          description: The simulated ruleset's context routing key.
          type: string
        ruleset_id:
          format: uuid
          type: string
        ruleset_status:
          enum:
            - DRAFT
            - ACTIVE
            - INACTIVE
          type: string
        ruleset_version:
          type: integer
        schema:
          $ref: '#/components/schemas/SchemaBinding'
        simulated_at:
          format: date-time
          type: string
        trace:
          description: >-
            One entry per rule of the ruleset, in published evaluation order
            (rules-array order).
          items:
            $ref: '#/components/schemas/TraceEntry'
          type: array
        warnings:
          description: >-
            Authoring soft-warns — advisory only, never affecting the verdict.
            Emitted e.g. when credential.pan and credential.masked_pan both
            populate the same BIN attribute with differing values (code
            bin_attribute_divergence, paths naming both field paths).
          items:
            properties:
              code:
                example: bin_attribute_divergence
                type: string
              message:
                type: string
              paths:
                items:
                  type: string
                type: array
            required:
              - code
              - message
            title: SimulationWarning
            type: object
          type: array
        verdict:
          description: >-
            Simulated verdict — same semantics as the live decision enum. BLOCK
            terminates on first match, REVIEW accumulates, ALLOW is the default
            when nothing fires. PROVISIONAL is unreachable by simulation (no
            backend is ever invoked). Null exactly when verdict_suppressed is
            non-null.
          enum:
            - ALLOW
            - REVIEW
            - BLOCK
          type:
            - string
            - 'null'
        verdict_suppressed:
          description: >-
            Present when the run cannot state the verdict a live decision would
            return. Reaching a live, synchronous backend rule suppresses it
            (`backend_reached`) — the backend is not invoked and its answer
            could change everything after it, so the remaining rules render
            `not_evaluated`. An async backend rule suppresses only a verdict
            that would otherwise be `ALLOW` (`async_backend_reached`), where the
            live path returns `PROVISIONAL`. Shadow rules (`live` false) and
            rules on an instance with the Link capability disabled never
            suppress.
          properties:
            reason:
              enum:
                - backend_reached
                - async_backend_reached
              type: string
            rule_id:
              description: The backend rule that stopped the simulated verdict.
              type: string
          required:
            - reason
            - rule_id
          type:
            - object
            - 'null'
      required:
        - backend_results
        - context
        - ruleset_id
        - ruleset_status
        - ruleset_version
        - schema
        - simulated_at
        - trace
        - verdict
        - verdict_suppressed
      title: SimulationTrace
      type: object
      example:
        backend_results: []
        context: checkout
        ruleset_id: 1f2e3d4c-5b6a-4978-8c0d-1e2f3a4b5c6d
        ruleset_status: DRAFT
        ruleset_version: 3
        schema:
          name: transaction
        simulated_at: '2026-07-25T12:00:00Z'
        trace:
          - action: REVIEW
            fields:
              - path: $.transaction.amount
                value: 150
            name: Review medium amount
            outcome: fired
            rule_id: r1
            type: condition
          - action: BLOCK
            fields:
              - path: $.transaction.amount
                value: 150
            name: Block high amount
            outcome: fired
            rule_id: r2
            type: condition
          - action: REVIEW
            name: Review non-USD
            outcome: not_evaluated
            rule_id: r3
            skip_reason: short_circuited
            type: condition
        verdict: BLOCK
        verdict_suppressed: null
    Error:
      description: Generic error response with classifier, code, and message
      example:
        classifier: NOT_FOUND
        code: 404
        message: The requested resource does not exist
      properties:
        classifier:
          type: string
        code:
          type: integer
        message:
          type: string
      required:
        - classifier
        - code
        - message
      title: Error
      type: object
    ValidationErrors:
      description: Response shape for request validation failures
      example:
        classifier: VALIDATION_ERROR
        code: 422
        validation_errors:
          - message: must be a positive integer
            path: limit
      properties:
        classifier:
          example: VALIDATION_ERROR
          type: string
        code:
          example: 422
          type: integer
        validation_errors:
          items:
            properties:
              message:
                example: must be a positive integer
                type: string
              path:
                example: limit
                type: string
            type: object
          type: array
      required:
        - classifier
        - code
        - validation_errors
      title: ValidationErrors
      type: object
    BackendResult:
      description: Outcome of a single backend rule that was executed.
      properties:
        backend:
          example: vdm
          type: string
        backend_reference:
          example: dec-xyz
          description: >-
            The backend's own reference for this evaluation, mirrored from the
            response body so it stays addressable when the raw body is not
            served — it survives the body's retention window. Null when the
            backend supplied none.
          type:
            - string
            - 'null'
        decision:
          enum:
            - ALLOW
            - BLOCK
            - REVIEW
          type:
            - string
            - 'null'
        error:
          example: timeout
          description: >-
            Error code when the backend did not produce a decision.
            `insufficient_context` — required context fields were missing and no
            call was made. `guard_not_met` — the rule's `condition` was false or
            unevaluable and the call was skipped. `backend_capability_disabled`
            — the Link backend capability is off for this instance.
            `backend_disabled` — the Link backend is disabled and rejected the
            call. `not_simulated` — simulation only, no call made.
          type:
            - string
            - 'null'
        latency_ms:
          example: 42
          description: >-
            Round-trip latency of the call. Null where no call was made and none
            was attributed — a simulated backend rule renders "not_simulated"
            with no latency.
          type:
            - integer
            - 'null'
        link_execution_id:
          example: 3f8b2d16-71c4-4d29-a5e2-6b9c0d4f7a21
          description: >-
            Execution ID from the Link integration. Present only for Link-backed
            backends.
          type:
            - string
            - 'null'
        link_timing:
          description: >-
            Server-Timing breakdown in milliseconds, parsed from the backend
            response. Present only for Link-backed backends.
          properties:
            external:
              example: 120.5
              type: number
            internal:
              example: 8.2
              type: number
            total:
              example: 128.7
              type: number
          type:
            - object
            - 'null'
        live:
          example: true
          description: Whether the backend ran live (true) or in shadow mode (false).
          type: boolean
        missing_fields:
          example:
            - device.ip
            - billing.postal_code
          description: >-
            Dot-notation paths of required context fields that were absent.
            Populated only when error is "insufficient_context".
          items:
            type: string
          type:
            - array
            - 'null'
        raw_response:
          example:
            status: ACCEPTED
            id: dec-xyz
          description: >-
            The backend's own response body, scrubbed of values recognised as
            credentials. Null once any configured retention window has passed —
            the shape is the backend's, so no classification marker resolves a
            scope inside it and the whole body goes on the strictest configured
            window.
          type:
            - object
            - 'null'
        rule_id:
          example: r1
          type: string
      title: BackendResult
      type: object
    SchemaBinding:
      description: >-
        A ruleset's declared (schema, version) pair. name references a registry
        entry; version is required for custom schemas and absent for built-ins,
        whose version is implicit and advances only via Specter releases. Set at
        ruleset creation and immutable for the life of that ruleset version —
        rebinding a context means publishing a new ruleset version with the new
        binding and activating it. Rulesets that predate the registry surface
        the launch-backfilled transaction binding.
      properties:
        name:
          example: transaction
          type: string
        version:
          description: Registry version for custom schemas. Absent for built-in schemas.
          example: 1
          type:
            - integer
            - 'null'
      required:
        - name
      title: SchemaBinding
      type: object
    TraceEntry:
      allOf:
        - $ref: '#/components/schemas/TriggeredRule'
        - properties:
            absent_fields:
              description: >-
                Only when outcome is "abstained" — the field path(s) that
                resolved absent, e.g. "$.customer.email".
              items:
                type: string
              type:
                - array
                - 'null'
            absorbed_operands:
              description: >-
                Only when outcome is "abstained" and no path resolved absent —
                the operand(s) that absorbed the predicate while resolving to a
                present value, and why. non_list_operand — a membership (in /
                not_in) operand resolved to a value that is not an array, so the
                set test is undefined and the predicate abstains without
                anything having gone absent. Together with absent_fields this
                makes every abstention self-explaining.
              items:
                properties:
                  path:
                    description: >-
                      The operand's field path, or "(const)" where the operand
                      is a rule literal.
                    example: $.label
                    type: string
                  reason:
                    enum:
                      - non_list_operand
                    type: string
                required:
                  - path
                  - reason
                type: object
              type:
                - array
                - 'null'
            derivations:
              description: >-
                Platform-internal derivations behind the outcome. Only the keys
                the rule exercises are present. This envelope is the extension
                point downstream grammar extensions render their per-construct
                detail into without changing the core entry shape. comparison
                and blacklist are rendered by the decision evidence surface (GET
                /api/decisions/{id}/evidence); the simulation trace renders
                filter_chains, hmac and velocity.
              properties:
                blacklist:
                  description: >-
                    Present on a fired blacklist rule. Captured at decision time
                    rather than replayed — a blacklist is mutable state, so
                    re-looking-up against today's entries could not reconstruct
                    the match as it stood. Later edits to the blacklist,
                    including deleting the matched entry, do not rewrite it.
                  properties:
                    entry_id:
                      description: The blacklist entry that matched.
                      format: uuid
                      type: string
                    field_path:
                      example: $.credential_fingerprint
                      type: string
                    matched_value:
                      description: >-
                        The looked-up value, rendered per the field's scope
                        marker — "[REDACTED]" for pii / pci-pan.
                  required:
                    - field_path
                    - entry_id
                  type: object
                comparison:
                  description: >-
                    Present on a fired comparison rule. Both resolved operands
                    and the operator, each operand rendered per its own scope
                    marker — so a pii operand shows its path and a sentinel
                    while an any operand on the other side of the same
                    comparison shows its value.
                  properties:
                    left:
                      $ref: '#/components/schemas/ResolvedOperand'
                    operator:
                      enum:
                        - eq
                        - neq
                        - lt
                        - lte
                        - gt
                        - gte
                        - in
                        - not_in
                      type: string
                    right:
                      $ref: '#/components/schemas/ResolvedOperand'
                  required:
                    - operator
                    - left
                    - right
                  type: object
                filter_chains:
                  items:
                    properties:
                      output:
                        description: Final chain output, redacted per inherited scope.
                      source:
                        example: $.customer.email
                        type: string
                      steps:
                        items:
                          properties:
                            filter:
                              example: downcase()
                              type: string
                            output:
                              description: Step output, redacted per inherited scope.
                          required:
                            - filter
                          type: object
                        type: array
                    required:
                      - source
                      - steps
                    type: object
                  type: array
                hmac:
                  properties:
                    inputs:
                      description: >-
                        Contributing field paths (paths only, never values); a
                        const literal renders as "(const)". The filters an input
                        was folded through render as a filter_chains entry for
                        that path.
                      items:
                        type: string
                      type: array
                    output:
                      description: >-
                        Full HMAC-SHA256 hex, without the `hmc_` prefix the
                        engine compares against. HMAC outputs are any-scoped by
                        definition and render unredacted — to pin a rule to this
                        fingerprint, compare the hmac operand against the
                        literal `hmc_` + this value.
                      pattern: ^[0-9a-f]{64}$
                      type: string
                  required:
                    - inputs
                    - output
                  type: object
                velocity:
                  properties:
                    canonical_text:
                      description: Human-readable canonical form of the counter definition.
                      type: string
                    counter_id:
                      description: >-
                        Stable canonical counter identifier — confirms
                        counter-sharing intent across rules. Value-free — the
                        counted value contributes only an instance-keyed digest,
                        so a counter keyed by a pii field neither renders that
                        value nor lets a holder of a candidate value reproduce
                        the identifier and test for it.
                      type: string
                    live_read:
                      description: >-
                        True on the simulate path — the value is read from live
                        counter state and may differ between identical runs.
                        False wherever the value is a counter reading captured
                        at decision time and is therefore stable across repeated
                        reads.
                      type: boolean
                    threshold:
                      description: The rule's configured threshold.
                      type: integer
                    value:
                      description: The counter value the rule evaluated against.
                      type: integer
                    window_seconds:
                      description: The rule's configured rolling window.
                      type: integer
                  required:
                    - canonical_text
                    - counter_id
                    - live_read
                    - threshold
                    - value
                    - window_seconds
                  type: object
              type:
                - object
                - 'null'
            fields:
              description: >-
                Field values the rule's predicate resolved, rendered per scope
                marker — any renders the value; pii / pci-pan / pci-sad render
                "[REDACTED]" with the path visible. Filtered values inherit the
                strictest scope of their chain's inputs. A path addressing an
                object or array renders each descendant per its own marker. A
                path that resolved absent carries no value at all.
              items:
                properties:
                  path:
                    example: $.transaction.amount
                    type: string
                  value:
                    description: Rendered value or "[REDACTED]".
                required:
                  - path
                type: object
              type:
                - array
                - 'null'
            outcome:
              description: >-
                fired — predicate matched, the rule's action contributes to the
                verdict. not_fired — predicate evaluated to false. abstained —
                the predicate absorbed (absent-attribute fail-skip),
                distinguished from not_fired, and always names its cause —
                absent_fields where a reference resolved to absent,
                absorbed_operands where one resolved present but unusable.
                not_simulated — backend rule reached but not invoked (see
                backend_results). not_evaluated — evaluation never reached this
                rule (see skip_reason).
              enum:
                - fired
                - not_fired
                - abstained
                - not_simulated
                - not_evaluated
              type: string
            skip_reason:
              description: >-
                Only when outcome is "not_evaluated". short_circuited — an
                earlier rule fired BLOCK. verdict_suppressed — an earlier
                backend rule was reached and stopped evaluation. Absent on a
                rule that is disabled and therefore never evaluated — nothing
                skipped it.
              enum:
                - short_circuited
                - verdict_suppressed
              type:
                - string
                - 'null'
          required:
            - outcome
          type: object
      description: >-
        Extends the published TriggeredRule entry (rule_id, name, action, type)
        with the simulation outcome and the derivations behind it.
      title: TraceEntry
    TriggeredRule:
      properties:
        action:
          enum:
            - BLOCK
            - REVIEW
          example: BLOCK
          type:
            - string
            - 'null'
        name:
          example: Block USD
          type:
            - string
            - 'null'
        rule_id:
          example: r1
          type: string
        type:
          enum:
            - condition
            - blacklist
            - backend
            - backend_group
          example: condition
          type: string
      title: TriggeredRule
      type: object
    ResolvedOperand:
      description: >-
        One side of a comparison as it resolved during evaluation. `path` is
        present for a field reference and null for a literal. `value` is
        rendered per the operand's scope marker — "[REDACTED]" for pii / pci-pan
        — so evidence names which rule fired on which path without disclosing a
        protected value. A `pci-sad` field is excluded from evidence entirely
        and can never appear as an operand.
      properties:
        path:
          example: $.transaction.amount
          type:
            - string
            - 'null'
        value:
          description: Rendered value or "[REDACTED]".
      title: ResolvedOperand
      type: object
  securitySchemes:
    bearerAuth:
      bearerFormat: JWT
      description: >-
        HS256-signed JWT bearer token, obtained via the OAuth2
        client-credentials grant (see Authentication).
      scheme: bearer
      type: http

````