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

# Verify a Refusal Record's signature

> Verifies a Refusal Record's signature (RFC-0021). Deliberately mounted before this app's caller-auth middleware, see packages/api/src/app.ts: no caller authentication or API key is required, by design. This is the capability that makes a refusal independently third-party verifiable. It takes the record itself, not a lookup by ID, no database access, no ownership check, nothing but the artifact and Parmana's public key.




## OpenAPI

````yaml /openapi.bundled.yaml post /refusal/verify
openapi: 3.1.0
info:
  title: Parmana API
  version: 1.0.0
  description: >
    Parmana is an Execution Trust Infrastructure that ensures there is no gap
    between what humans decide and what AI systems do. The API enables creation,
    execution, verification, replay, and auditing of Business Transactions
    through cryptographically verifiable Execution Trust Records.


    **Every route requires a caller bearer key**, except the liveness/readiness
    probes and documentation/verification routes that must be reachable with no
    credential: GET /health, GET /ready, GET /openapi.yaml, GET /documentation,
    GET /reference, POST /refusal/verify, POST /audit/verify, GET /keys/{keyId},
    and GET /.well-known/jwks.json. Send `Authorization: Bearer <key>` on every
    other request. Keys are issued by `scripts/generate-api-key.ts` and
    configured server-side via `PARMANA_API_KEYS`; only a hash of each key is
    ever held by the server, verified in constant time. A missing or invalid
    credential returns 401 before a Business Transaction is even constructed,
    independent of Policy evaluation and gateway attestation, see
    `packages/api/src/middleware/caller-auth.ts` and
    [Authentication](/api-reference/authentication). Local development may set
    `PARMANA_AUTH_DISABLED=true` to skip this middleware entirely; that flag
    must never be set in a real deployment.
  contact:
    name: Parmana Systems
    email: founder@parmanasystems.com
  license:
    name: Proprietary, source-available for evaluation only, see LICENSE
    url: https://github.com/pavancharak/AgentLabsBuildathon/blob/main/LICENSE
servers:
  - url: http://localhost:3000
    description: Local (packages/api, PORT env var, default 3000)
security:
  - bearerAuth: []
tags:
  - name: Execution
    description: >-
      Executes a Business Transaction through the complete Execution Trust
      pipeline
  - name: Transactions
    description: Business Transaction creation and retrieval
  - name: Verification
    description: Deterministic verification of an Execution Trust Record
  - name: Receipts
    description: Cryptographically signed Execution Trust Receipts
  - name: Trust Records
    description: Execution Trust Record retrieval
  - name: Replay
    description: Deterministic replay of a recorded Execution Trust Record
  - name: Policies
    description: Policy existence/readability check
  - name: Policy Governance
    description: Maker-checker proposal, listing, approval, and rejection of policy changes
  - name: Refusal Records
    description: >-
      Durable, signed evidence that a policy decision rejected a transaction
      (RFC-0021)
  - name: Audit
    description: >-
      Signed caller-authentication audit events, independently
      third-party-verifiable
  - name: System
    description: Operational endpoints
paths:
  /refusal/verify:
    post:
      tags:
        - Refusal Records
      summary: Verify a Refusal Record's signature
      description: >
        Verifies a Refusal Record's signature (RFC-0021). Deliberately mounted
        before this app's caller-auth middleware, see packages/api/src/app.ts:
        no caller authentication or API key is required, by design. This is the
        capability that makes a refusal independently third-party verifiable. It
        takes the record itself, not a lookup by ID, no database access, no
        ownership check, nothing but the artifact and Parmana's public key.
      operationId: verifyRefusalRecord
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/refusal-record.schema'
      responses:
        '200':
          description: >-
            Verification completed. A 200 does not by itself mean the signature
            verified, see the valid field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/verify-result-response.schema'
              examples:
                valid:
                  summary: Real captured response
                  value:
                    valid: true
        '400':
          description: Request body is not a structurally plausible Refusal Record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error.schema'
              examples:
                malformed:
                  summary: Real captured response
                  value:
                    error: >-
                      Request body must be a Refusal Record (refusalRecordId,
                      businessTransactionId, decision, refusalRecordHash,
                      signature required).
      security: []
components:
  schemas:
    refusal-record.schema:
      title: Refusal Record
      description: >-
        Durable, signed, independently verifiable evidence that a policy
        decision rejected a transaction (RFC-0021). Scope is deliberately
        narrow: covers PolicyEngine.evaluate REJECTs and SignalIntentBinder
        binding-violation REJECTs only, not caller-authentication failures or
        webhook signature failures, which are a separate, unsigned audit-sink
        capability. At most one Refusal Record exists per businessTransactionId.
      type: object
      additionalProperties: true
      required:
        - refusalRecordId
        - businessTransactionId
        - decision
        - evaluatedIntent
        - refusalRecordHash
        - signature
        - createdAt
      properties:
        refusalRecordId:
          type: string
          description: Unique Refusal Record identifier.
        businessTransactionId:
          type: string
          description: Business Transaction this refusal is about.
        decision:
          $ref: '#/components/schemas/decision.schema'
        evaluatedIntent:
          type: object
          description: >-
            The Intent snapshot the signals were evaluated against: target and
            parameters only, present for every refusal, not only binding
            violations.
          additionalProperties: true
          properties:
            target:
              type: string
            parameters:
              type: object
              additionalProperties: true
        bindingViolations:
          type: array
          description: >-
            Present only when the rejection came from SignalIntentBinder. Absent
            (not an empty array) for an ordinary PolicyEngine.evaluate REJECT
            that never reached binding-violation logic at all.
          items:
            type: object
            additionalProperties: true
            required:
              - signalKey
              - intentPath
              - signalValue
              - intentValue
            properties:
              signalKey:
                type: string
              intentPath:
                type: string
              signalValue: {}
              intentValue: {}
        submittedBy:
          type: string
          description: >-
            Authenticated caller who submitted the rejected request. Absent when
            caller authentication is disabled.
        refusalRecordHash:
          type: string
          description: >-
            Canonical hash of this Refusal Record, same convention as
            ExecutionTrustRecord.trustRecordHash.
        signature:
          type: object
          description: >-
            Cryptographic signature over the canonical Refusal Record, signed
            with the same key as ExecutionTrustRecord, one root of trust for
            both approvals and refusals.
          additionalProperties: true
          required:
            - algorithm
            - keyId
            - value
            - signedAt
          properties:
            algorithm:
              type: string
              examples:
                - ed25519
            keyId:
              type: string
              description: Identifier of the signing key.
            value:
              type: string
              description: Base64-encoded signature value.
            signedAt:
              type: string
              format: date-time
        createdAt:
          type: string
          format: date-time
          description: UTC timestamp when this Refusal Record was created.
      examples:
        - refusalRecordId: eddc5dd1-a791-4470-9534-11b3fc573ab8
          businessTransactionId: 353c73fb-5e87-4dbe-8bfc-c15c8f18d871
          decision:
            decisionId: 3919f4b6-cbc8-44ec-a6a1-8887ee85f314
            intentId: 4ba6b14a-c593-4f5c-ab58-dcef38a29364
            policy:
              name: vendor-payment
              version: 2.0.0
              schemaVersion: 1.0.0
            signals:
              vendorVerified: false
              invoiceVerified: true
              paymentApproved: true
              sufficientFunds: true
              paymentAmount: 1000
              riskScore: 5
              vendorId: vendor://payments
            outcome: REJECTED
            reason: Vendor payment rejected because the vendor has not been verified.
            evaluatedAt: '2026-09-15T03:27:45.666Z'
          evaluatedIntent:
            target: vendor://payments
            parameters:
              amount: 1000
              currency: USD
          createdAt: '2026-09-15T03:27:45.666Z'
          refusalRecordHash: 47d8200e839f9b4a1a53dba941e318553c9b024bc64d26c5b613e5ae2aeb65ca
          signature:
            algorithm: ed25519
            keyId: default
            value: >-
              tT32/iJ0bEDxjc+/KPeAegTBSrzpRFW14f9Vyv3Ykx+H/rLHcgd7SlFH/BAXut8sEH2mpo0smg82tSoXmZkpDQ==
            signedAt: '2026-09-15T03:27:45.668Z'
    verify-result-response.schema:
      title: Verify Result Response
      description: >-
        Response shared by POST /refusal/verify and POST /audit/verify: a bare
        signature-validity result, no wrapper, no partial-failure detail. Both
        routes verify a signature over bytes with no database lookup, so there
        is nothing more specific to report than valid or not.
      type: object
      additionalProperties: false
      required:
        - valid
      properties:
        valid:
          type: boolean
          description: >-
            Whether the signature verifies against Parmana's public key for the
            stated algorithm and key ID.
      examples:
        - valid: true
        - valid: false
    error.schema:
      title: Error Response
      description: >-
        Shared error envelope produced by
        packages/api/src/middleware/error-handler.ts and by every route's inline
        validation checks. error is always a plain human-readable string (never
        a nested object). code is present only when the failure was a
        RuntimeError subclass reaching the centralized error handler
        (VerificationFailedError, ReceiptGenerationError, or an uncategorized
        RuntimeError); it is absent from every inline route-level check
        (businessTransactionId format/required checks) and from
        BusinessTransactionValidationError, PolicyValidationError,
        SignalValidationError, PolicyNotFoundError,
        DuplicateBusinessTransactionError, and the generic 500 fallback. POST
        /policies/validate does NOT use this envelope at all. See its own
        response schema. For the triggering condition and recommended caller
        action behind any specific error/code/status combination, see the Error
        catalog at /api-reference/error-catalog.
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message.
        code:
          type: string
          description: >-
            Stable machine-readable error code. Only present for errors that
            reach the handler as a RuntimeError.
          examples:
            - RUNTIME_ERROR
            - VERIFICATION_FAILED
            - RECEIPT_GENERATION_FAILED
      examples:
        - error: businessTransactionId must be a valid UUID.
        - error: >-
            Business Transaction 'eed2a972-1bf5-4166-8472-761f76fbf1b2' already
            exists.
        - error: >-
            Execution rejected: Vendor payment rejected because the assessed
            payment risk exceeds the maximum permitted threshold.
          code: RUNTIME_ERROR
    decision.schema:
      title: Decision
      description: >-
        Immutable result of evaluating an Intent against a Policy. Decision does
        not create authority, grant authorization, or modify Intent; it only
        records the outcome of deterministic Policy evaluation.
      type: object
      additionalProperties: true
      required:
        - decisionId
        - intentId
        - policy
        - signals
        - outcome
        - evaluatedAt
      properties:
        decisionId:
          type: string
          description: Unique Decision identifier.
        intentId:
          type: string
          description: Intent evaluated by this Decision.
        policy:
          $ref: '#/components/schemas/policy.schema'
        signals:
          $ref: '#/components/schemas/signals.schema'
        outcome:
          type: string
          description: >-
            Policy evaluation outcome. There are no intermediate states: a
            Decision is always exactly one of these two values.
          enum:
            - APPROVED
            - REJECTED
        reason:
          type: string
          description: >-
            Human-readable explanation, taken from the matching Policy rule's
            outcome.reason.
        evaluatedAt:
          type: string
          format: date-time
          description: UTC timestamp when policy evaluation completed.
      examples:
        - decisionId: 9d69dc0b-333a-4be3-b09f-358fece806f3
          intentId: ae5865f6-181b-409a-90ec-1b4b8b8414ba
          policy:
            name: vendor-payment
            version: 2.0.0
            schemaVersion: 1.0.0
          signals:
            vendorVerified: true
            invoiceVerified: true
            paymentApproved: true
            sufficientFunds: true
            paymentAmount: 4500
            riskScore: 10
          outcome: APPROVED
          reason: >-
            Vendor payment authorized. Vendor verification, invoice
            verification, payment approval, funding, and risk assessment
            requirements were satisfied.
          evaluatedAt: '2026-07-07T16:38:59.326Z'
    policy.schema:
      title: Policy Reference
      description: >-
        Exact Policy to evaluate. The client explicitly supplies name, version,
        and schemaVersion; Parmana does not automatically discover or select a
        policy. If no matching policy file exists at
        policies/{name}/{version}/policy.json, the request fails with a 404
        (policyId/policyVersion on POST /policies/validate) or a RUNTIME_ERROR
        (name/version elsewhere, from PolicyNotFoundError not otherwise mapped
        by the shared error handler in every route, see the error envelope
        note).
      type: object
      additionalProperties: true
      required:
        - name
        - version
        - schemaVersion
      properties:
        name:
          type: string
          description: >-
            Policy name. Required and non-empty. Parmana rejects an empty name
            with a 400.
          minLength: 1
        version:
          type: string
          description: >-
            Business policy version. Required and non-empty. Parmana rejects an
            empty version with a 400.
          minLength: 1
          examples:
            - 2.0.0
            - 1.0.0
        schemaVersion:
          type: string
          description: Signals schema version expected by the policy.
          examples:
            - 1.0.0
      examples:
        - name: vendor-payment
          version: 2.0.0
          schemaVersion: 1.0.0
    signals.schema:
      title: Signals
      description: >-
        Opaque runtime facts evaluated by the resolved Policy's rules. Parmana
        assigns no business meaning to these values and does not statically
        validate them beyond the Policy's own signalsSchema declaration (see
        policies/{name}/{version}/policy.json). Scaled integers, not floats, for
        numeric signals such as amounts (house convention across every reference
        policy).
      type: object
      additionalProperties: true
      examples:
        - vendorVerified: true
          invoiceVerified: true
          paymentApproved: true
          sufficientFunds: true
          paymentAmount: 4500
          riskScore: 10
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        Caller API key issued by scripts/generate-api-key.ts. Sent as
        Authorization: Bearer <key>. Verified against a stored SHA-256 hash in
        constant time by packages/api/src/auth/StaticKeyAuthenticator.ts.
        Required on every route not listed as exempt in this document's
        top-level description. See /api-reference/authentication.

````