openapi: 3.1.0
info:
  title: Parmana API
  version: 1.0.0
  description: >
    Parmana sits between an AI agent and the systems it acts on. An agent sends
    a Business Transaction: who

    authorized it, what action it wants, and the facts that justify it. Parmana
    evaluates the policy bound to

    the action, checks any signed human approval the policy requires, releases
    the action to the connector

    only if the policy approves, and returns a signed Execution Trust Record
    anyone can verify.


    **Authentication.** Send `Authorization: Bearer <key>` on every request. The
    only routes that need no key

    are the probes, the documents and the independent verification routes: `GET
    /health`, `GET /ready`,

    `GET /openapi.yaml`, `GET /openapi.json`, `GET /api-manifest.json`, `GET
    /documentation`,

    `GET /reference`, `GET /parmana-handbook.pdf`, `GET
    /handbook/download-leads`,

    `POST /handbook/download-leads`, `POST /refusal/verify`, `POST
    /execution-intents/verify`,

    `POST /audit/verify`, `GET /keys/{keyId}` and `GET /.well-known/jwks.json`.
    A missing or unknown key

    returns `401` before anything else runs. The server holds only a hash of
    each key. See

    [Authentication](/api-reference/authentication).


    **Two kinds of key.** An agent or service key may invoke only the
    capabilities listed on it. A human key

    (`credentialHolderType: USER`) is required for governance: proposing and
    approving policies, approvers

    and external connectors, and repairing Execution Intents. Approving or
    rejecting a change also needs a

    step up authorization signed with the checker's own step up key, and the
    checker must not be the

    person who proposed it.


    **Ownership.** Every lookup by `businessTransactionId` is scoped to the key
    that submitted the

    transaction. A transaction submitted by another key returns the same `404`
    as one that does not exist.


    **Try it live.** The first server below is the public sandbox,
    `https://parmana-sandbox.vercel.app`: the same

    code as production with its own database, keys and demo approver, whose one
    action, `sandbox:receipt`,

    acts on nothing. Every "Try it" panel calls it with the published demo key
    prefilled. The demo key may

    invoke only `sandbox:receipt`, so other capabilities return `403` and
    governance routes return

    `403 NON_HUMAN_CALLER_DENIED`. Everything sent to the sandbox is visible to
    every visitor. See

    [Playground](/playground). Production needs a key issued by its operator.


    **Start here:** the [Playground](/playground) (no install), the
    [Quickstart](/quickstart), then [Integrate Parmana](/agents/integrate) for
    agents

    and [Connect any external system](/guides/connect-any-external-system) for
    your own endpoint.
  contact:
    name: Parmana Systems
    email: founder@parmanasystems.com
  license:
    name: Apache License 2.0, see LICENSE
    url: https://docs.parmanasystems.com/license
servers:
  - url: https://parmana-sandbox.vercel.app
    description: Public sandbox (the docs playground). Same code as production, demo
      data only, acts on nothing. Demo key prefilled.
  - url: https://parmana-api-real.vercel.app
    description: Production. Needs a key issued by the operator.
  - 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: Approvers
    description: Maker-checker for the keys trusted to sign approvals, without a deploy
  - name: External Connectors
    description: Maker-checker for binding a capability to an operator's HTTPS
      endpoint and its policy, without a deploy (ADR-0013)
  - name: Business Signal Sources
    description: Maker-checker for the business systems Parmana asks for the facts a
      policy needs, without a deploy (RFC-0023)
  - name: Authority Grants
    description: Maker-checker for which agent may have which action decided, within
      which limits, until when (RFC-0023)
  - name: Sandbox
    description: Routes that exist only on the public sandbox (ADR-0014), for trying
      Parmana from a browser
  - name: Kill Switch
    description: Operator kill switch (ADR-0017). Trip and reset need the operator
      capabilities, and probe results need the probe capability; status is open
      to any authenticated caller
  - name: Refusal Records
    description: Durable, signed evidence that a policy decision rejected a
      transaction (RFC-0021)
  - name: Execution Intents
    description: A signed statement, stored before an action is released, of exactly
      what is about to be released, plus the operator tools to find and repair a
      released action that has no signed Trust Record (ADR-0012)
  - name: Audit
    description: Signed caller-authentication audit events, independently
      third-party-verifiable
  - name: System
    description: Operational endpoints
paths:
  /:
    get:
      tags:
        - System
      summary: Root
      operationId: getRoot
      description: >
        Returns the service name and `UP`. Use it to check that your key is
        accepted: unlike `GET /health`,

        this route requires a key.
      responses:
        "200":
          description: The key is valid and the service is responding.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/root-response.schema"
              examples:
                root:
                  summary: Real captured response
                  value:
                    name: Parmana
                    status: UP
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-sandbox.vercel.app/ \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await fetch("https://parmana-api-real.vercel.app/",
            {
              headers: { Authorization: `Bearer ${process.env.PARMANA_API_KEY}` },
            });


            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: >-
            import os


            import requests


            response = requests.get(
                "https://parmana-api-real.vercel.app/",
                headers={"Authorization": f"Bearer {os.environ['PARMANA_API_KEY']}"},
                timeout=30,
            )


            print(response.status_code, response.json())
  /health:
    get:
      tags:
        - System
      summary: Health check
      operationId: getHealth
      description: >
        Liveness probe. Always returns `UP` while the process is serving
        requests; it checks no dependency.

        Needs no key. For a check that includes storage, use `GET /ready`.
      security: []
      responses:
        "200":
          description: The process is up.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/health-response.schema"
              examples:
                health:
                  summary: Real captured response
                  value:
                    status: UP
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/health
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const health = await client.health();

            console.log(health.status);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            health = client.health()

            print(health["status"])
  /ready:
    get:
      tags:
        - System
      summary: Readiness check
      operationId: getReady
      description: >
        Readiness probe. When storage is Postgres, it runs `SELECT 1` so an
        orchestrator can tell a process

        that is up but cannot reach its database from one that can serve
        traffic. With in memory storage

        there is nothing to check and it always returns `READY`. Needs no key.
      security: []
      responses:
        "200":
          description: Ready to serve traffic. `authDisabled` says whether caller
            authentication is off on this deployment, which must never be true
            in production.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ready-response.schema"
              examples:
                authEnabled:
                  summary: Real captured response
                  value:
                    status: READY
                    storage: not-supabase-backed
                    authDisabled: false
                authDisabled:
                  summary: Real captured response
                  value:
                    status: READY
                    storage: not-supabase-backed
                    authDisabled: true
                    warning: PARMANA_AUTH_DISABLED=true -- this deployment is accepting requests
                      with no caller authentication. Must never be set in a real
                      deployment.
        "503":
          description: Postgres storage is configured but could not be reached. Requests
            that need storage will fail until it recovers.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ready-response.schema"
              examples:
                notReady:
                  summary: Shape, not a live capture
                  value:
                    status: NOT_READY
                    reason: connect ECONNREFUSED 127.0.0.1:5432
                    authDisabled: false
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/ready
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await
            fetch("https://parmana-api-real.vercel.app/ready");


            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://parmana-api-real.vercel.app/ready",
                timeout=30,
            )

            print(response.status_code, response.json())
  /openapi.yaml:
    get:
      tags:
        - System
      summary: Get the OpenAPI spec
      operationId: getOpenApiSpec
      description: >
        This API's complete OpenAPI 3.1 document as YAML, self contained with no
        external references. Needs

        no key, so a client can discover the API before it has one. Load it into
        a code generator, Postman or

        an agent. `GET /openapi.json` serves the same document as JSON.
      security: []
      responses:
        "200":
          description: The OpenAPI document, YAML.
          content:
            application/yaml:
              schema:
                type: string
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/openapi.yaml
        - lang: typescript
          label: TypeScript
          source: |-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/openapi.yaml",
            );

            console.log(response.status, await response.text());
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://parmana-api-real.vercel.app/openapi.yaml",
                timeout=30,
            )

            print(response.status_code, response.text)
  /openapi.json:
    get:
      tags:
        - System
      summary: Get the OpenAPI spec, as JSON
      operationId: getOpenApiSpecJson
      description: >
        The same document as `GET /openapi.yaml`, as JSON, for tools and agents
        that expect a spec at a

        `.json` path. Needs no key.
      security: []
      responses:
        "200":
          description: The OpenAPI document, JSON.
          content:
            application/json:
              schema:
                type: object
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/openapi.json
        - lang: typescript
          label: TypeScript
          source: |-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/openapi.json",
            );

            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://parmana-api-real.vercel.app/openapi.json",
                timeout=30,
            )

            print(response.status_code, response.json())
  /api-manifest.json:
    get:
      tags:
        - System
      summary: Machine-readable SDK and discovery manifest
      operationId: getApiManifest
      description: >
        One index for agents and tools: where the OpenAPI document and the
        rendered references are, how to

        authenticate, and both official SDKs with their package name, registry
        and current version. Read it

        first when an agent sets itself up. Go and Java are `null` because no
        SDK exists for them; call the

        REST API directly. Needs no key.
      security: []
      responses:
        "200":
          description: The manifest.
          content:
            application/json:
              schema:
                type: object
              examples:
                liveCapture:
                  summary: Real captured response
                  value:
                    name: Parmana
                    apiVersion: v1
                    buildVersion: 0.4.0
                    openapi:
                      json: /openapi.json
                      yaml: /openapi.yaml
                    documentation:
                      site: https://docs.parmanasystems.com
                      swaggerUi: /documentation
                      redoc: /reference
                    authentication:
                      type: bearer
                      header: "Authorization: Bearer <key>"
                      docs: https://docs.parmanasystems.com/api-reference/authentication
                    sdks:
                      typescript:
                        package: "@parmana/sdk"
                        registry: npm
                        version: 1.1.2
                        install: npm install @parmana/sdk
                        docs: https://docs.parmanasystems.com/sdks/typescript
                        reference: https://docs.parmanasystems.com/sdks/reference/typescript/classes/ParmanaClient
                      python:
                        package: parmana
                        registry: pypi
                        version: 1.1.4
                        install: pip install parmana
                        docs: https://docs.parmanasystems.com/sdks/python
                        reference: https://docs.parmanasystems.com/sdks/reference/python/client
                      go: null
                      java: null
                    connectorSdks:
                      typescript:
                        package: "@parmana/connector-sdk"
                        registry: npm
                        version: 0.1.0
                        install: npm install @parmana/connector-sdk
                        docs: https://docs.parmanasystems.com/reference/connector-sdk
                      python:
                        package: parmana-connector-sdk
                        registry: pypi
                        version: 0.1.0
                        install: pip install parmana-connector-sdk
                        docs: https://docs.parmanasystems.com/reference/connector-sdk-python
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/api-manifest.json
        - lang: typescript
          label: TypeScript
          source: |-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/api-manifest.json",
            );

            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://parmana-api-real.vercel.app/api-manifest.json",
                timeout=30,
            )

            print(response.status_code, response.json())
  /version:
    get:
      tags:
        - System
      summary: Service version
      operationId: getVersion
      description: >
        Returns `name`, `version` and `api`. These are literals in the server
        code, not a build number, so do

        not use them to detect a deploy. The SDKs carry their own versions; see

        `GET /api-manifest.json`.
      responses:
        "200":
          description: The version identifiers.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/version-response.schema"
              examples:
                version:
                  summary: Real captured response
                  value:
                    name: Parmana
                    version: 0.4.0
                    api: v1
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-sandbox.vercel.app/version \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const version = await client.version();

            console.log(version);
        - lang: python
          label: Python
          source: >-
            import os


            import requests


            response = requests.get(
                "https://parmana-api-real.vercel.app/version",
                headers={"Authorization": f"Bearer {os.environ['PARMANA_API_KEY']}"},
                timeout=30,
            )


            print(response.status_code, response.json())
  /keys/{keyId}:
    get:
      tags:
        - System
      summary: Fetch a public signing key for independent, offline verification
      operationId: getKey
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. This deployment's own public signing keys; the public
            sandbox has separate keys.
      description: >
        Returns the public half of a signing key, so anyone can verify a Parmana
        signature without a Parmana

        account: "an auditor, a regulator, or an endpoint receiving a release.
        Every signed artifact names its"

        `keyId`; pass it here. Keys that were rotated out stay available as long
        as the server keeps them, so

        old records remain verifiable. Use the `pem` field with the SDK
        verifiers (`verifyParmanaRelease`,

        `verify_parmana_release`) or the offline verifier. Needs no key.
      parameters:
        - name: keyId
          description: The `keyId` named in the signature you want to verify, for example
            `default`.
          in: path
          required: true
          schema:
            type: string
            x-default: default
      responses:
        "200":
          description: The public key.
          content:
            application/json:
              schema:
                type: object
                required:
                  - keyId
                  - algorithm
                  - use
                  - pem
                properties:
                  keyId:
                    type: string
                  algorithm:
                    type: string
                    description: >
                      The key's actual algorithm, derived from the key material
                      itself (asymmetricKeyType), not a global config value.
                  use:
                    type: string
                    enum:
                      - sig
                  pem:
                    type: string
                    description: PEM-encoded SPKI public key (RFC 7468).
                  jwk:
                    type: object
                    description: >
                      Node's native JWK export for this key's algorithm, when
                      available. Ed25519 exports as kty "OKP"; ML-DSA-65 exports
                      as kty "AKP" (the IETF JOSE/COSE key type for ML-DSA, not
                      an identifier this codebase invented). Omitted, not null,
                      when unavailable.
              examples:
                defaultKey:
                  summary: Real captured response
                  value:
                    keyId: default
                    algorithm: ed25519
                    use: sig
                    pem: |
                      -----BEGIN PUBLIC KEY-----
                      MCowBQYDK2VwAyEAQKyaz9ifANjaew96i34spqhM31mBatJ6TTHaMY7mclI=
                      -----END PUBLIC KEY-----
                    jwk:
                      crv: Ed25519
                      x: QKyaz9ifANjaew96i34spqhM31mBatJ6TTHaMY7mclI
                      kty: OKP
        "404":
          description: This deployment has no key with this id. Check the `keyId` in the
            signature, then try `GET /.well-known/jwks.json` to see every key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownKey:
                  summary: Real captured response
                  value:
                    error: "Key not found: does-not-exist"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/keys/default \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const key = await client.publicKey("default");

            console.log(key.pem);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            key = client.public_key("default")

            print(key.pem)
  /.well-known/jwks.json:
    get:
      tags:
        - System
      summary: List every public signing key this deployment can currently produce
      operationId: getJwks
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists every public signing key this deployment can serve, the
        enumeration form of `GET /keys/{keyId}`.

        With local key files that is every key in the key directory. Under AWS
        KMS it is the keys this

        deployment signs with: `default` and the current
        `PARMANA_VERIFICATION_KEY_ID`. A record signed

        under an older key id is still served by `GET /keys/{keyId}`. Fetch and
        cache it when you verify

        many artifacts. Not a strict RFC 7517 JWK Set: use the `pem` of each
        entry; an entry may have no

        `jwk` field. Needs no key.
      responses:
        "200":
          description: Every public key this deployment can serve.
          content:
            application/json:
              schema:
                type: object
                required:
                  - keys
                properties:
                  keys:
                    type: array
                    items:
                      type: object
                      required:
                        - keyId
                        - algorithm
                        - use
                        - pem
                      properties:
                        keyId:
                          type: string
                        algorithm:
                          type: string
                        use:
                          type: string
                          enum:
                            - sig
                        pem:
                          type: string
                        jwk:
                          type: object
              examples:
                keys:
                  summary: Real captured response
                  value:
                    keys:
                      - keyId: default
                        algorithm: ed25519
                        use: sig
                        pem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAQKyaz9ifANjaew96i34spqhM31mBatJ6TTHaMY7mclI=
                          -----END PUBLIC KEY-----
                        jwk:
                          crv: Ed25519
                          x: QKyaz9ifANjaew96i34spqhM31mBatJ6TTHaMY7mclI
                          kty: OKP
                      - keyId: gateway
                        algorithm: ed25519
                        use: sig
                        pem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAFIS92deNnIcw9U2ZrLxYo6Uqmt31SN0OKwoFhipfObk=
                          -----END PUBLIC KEY-----
                        jwk:
                          crv: Ed25519
                          x: FIS92deNnIcw9U2ZrLxYo6Uqmt31SN0OKwoFhipfObk
                          kty: OKP
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/.well-known/jwks.json \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/.well-known/jwks.json",
              {
                headers: { Authorization: `Bearer ${process.env.PARMANA_API_KEY}` },
              },
            );


            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: >-
            import os


            import requests


            response = requests.get(
                "https://parmana-api-real.vercel.app/.well-known/jwks.json",
                headers={"Authorization": f"Bearer {os.environ['PARMANA_API_KEY']}"},
                timeout=30,
            )


            print(response.status_code, response.json())
  /callers/me:
    get:
      tags:
        - System
      summary: Get the authenticated caller's identity and resolved scope
      operationId: getCallerMe
      description: >
        Returns who your key is and exactly what it may do: the caller id, the
        principals it may act for, the

        capabilities it may invoke and whether that includes every capability
        (`*`), after defaults are

        applied. Call it

        first when you set up an agent, and show it in a security review. An
        unset `allowedPrincipalIds`

        means the caller's own id; an unset `allowedCapabilities` means none.
        Never returns key material, and

        never another caller's record.
      responses:
        "200":
          description: Your key's identity and scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/callers-me-response.schema"
              examples:
                callerMe:
                  summary: Real captured response
                  value:
                    callerId: demo
                    allowedPrincipalIds:
                      - demo
                    allowedCapabilities:
                      - test:fixture-execute
                    unrestrictedCapabilities: false
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: No caller identity on the request. Happens only when caller
            authentication is disabled, which is for local development.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                noCallerIdentity:
                  summary: Real captured response when caller-auth is disabled
                  value:
                    error: No authenticated caller identity available.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-sandbox.vercel.app/callers/me \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const me = await client.caller();

            console.log(me.callerId, me.allowedCapabilities);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            me = client.caller()

            print(me.caller_id, me.allowed_capabilities)
  /execute:
    post:
      tags:
        - Execution
      summary: Execute a Business Transaction
      operationId: executeTransaction
      description: >
        In the sandbox, each `businessTransactionId` works once: a prefilled id
        returns `409` after its first use.

        For a new id each run, use the [playground](/playground) or the cURL
        sample, which generate one.

        Sends one action for a decision and, if approved, performs it. This is
        the call an agent makes. The

        server stores the Business Transaction, evaluates the policy, checks any
        signed human approval the

        policy requires, releases the action to its connector, and returns the
        signed Execution Trust Record.

        It runs synchronously: an approved action that calls an external system
        took 24 to 32 seconds in

        production, so set your client timeout to 120 seconds.


        **Before you send:** ask `GET /policies/in-effect?capability=<action>`
        which policy and version to

        declare and which facts to carry.


        **Idempotency:** `businessTransactionId` is a UUID you choose and the
        idempotency key. Sending the

        same id again returns `409`; it never runs twice. After a timeout, read

        `GET /execution-intents/{businessTransactionId}` before sending anything
        new.


        **What the server sets:** `status`, `createdAt` and
        `metadata.submittedBy` (from your key). Top level

        fields not in the schema are dropped.


        **Outcomes:**

        1. `200`: approved and released. The body is the signed Trust Record.

        2. `403` `POLICY_DENIED`: the policy refused. Final; nothing was
        released. A Refusal Record is kept at
           `GET /refusal/{businessTransactionId}`.
        3. `403` `CAPABILITY_NOT_ALLOWED`, or no code: your key may not invoke
        this action, or may not act for
           this principal.
        4. `503`: refused before release; nothing was executed. See the codes on
        that response.

        5. `502` `EXECUTION_OUTCOME_UNKNOWN`, `executionStatus:
        EXECUTION_UNKNOWN`: released, then the
           connector call failed, so the outcome is unknown. Do not resend under a new id. Check the target
           system, then close the intent with `POST /execution-intents/{businessTransactionId}/resolve`.
        6. `409` `AUTHORIZATION_NO_LONGER_VALID`, `executionStatus:
        NOT_EXECUTED`: decided, but at release a
           business fact it rested on was past its `validUntil`, or the authority grant had expired or been
           revoked (RFC-0023 phase 4). Nothing was sent. Send a new request.
        7. `500` `EXECUTION_RECORD_INCOMPLETE`: released, but the signed record
        could not be produced. Do not
           resend. An operator rebuilds it with `POST /execution-intents/{businessTransactionId}/finalize`.

        The examples named `vendor-payment-flow` show the response shape; their
        `payments:execute` action no

        longer exists. See [Integrate Parmana](/agents/integrate) and the

        [Error catalog](/api-reference/error-catalog).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/transaction-create-request.schema"
            examples:
              sandbox-receipt:
                summary: The public sandbox's receipt action, sent without an approval. The
                  sandbox refuses it (403 POLICY_DENIED), as the playground
                  shows. Get a demo approval from POST /sandbox/approvals for
                  the same target and add it to signals to release it.
                value:
                  businessTransactionId: 8ddd9ae9-eebe-4a5a-8182-4e6187466a74
                  metadata:
                    businessTransactionId: 8ddd9ae9-eebe-4a5a-8182-4e6187466a74
                    correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                    sourceSystem: docs-example
                    submittedBy: docs-reader
                    submittedAt: 2026-07-13T17:36:00.835Z
                  authority:
                    authorityId: authority-1
                    authorityType: SERVICE
                    principalId: sandbox-visitor
                    issuedAt: 2026-07-13T17:36:00.835Z
                  authorization:
                    authorizationId: authorization-1
                    authorityId: authority-1
                    purpose: Trying the Parmana sandbox
                    issuedAt: 2026-07-13T17:36:00.835Z
                  intent:
                    intentId: intent-1
                    authorizationId: authorization-1
                    action: sandbox:receipt
                    target: order-docs-example
                    parameters:
                      note: hello
                    createdAt: 2026-07-13T17:36:00.835Z
                  policy:
                    name: sandbox-receipt
                    version: 1.0.0
                    schemaVersion: 1.0.0
                  signals:
                    note: hello
                    receiptApproved: false
              vendor-payment-flow:
                summary: Historical capture (vendor-payment 2.0.0, approved). The
                  payments:execute action no longer exists on a current server.
                  Shown for the response shape only.
                value:
                  businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                  metadata:
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                    sourceSystem: vendor-payment-service
                    submittedBy: ap-automation
                    submittedAt: 2026-07-13T17:36:00.835Z
                  authority:
                    authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                    authorityType: SERVICE
                    principalId: ap-automation-svc
                    displayName: Accounts Payable Automation
                    issuedAt: 2026-07-13T17:36:00.835Z
                  authorization:
                    authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                    authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                    purpose: Authorize vendor payment disbursement
                    issuedAt: 2026-07-13T17:36:00.835Z
                  intent:
                    intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                    authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                    action: payments:execute
                    target: vendor/V-500
                    parameters:
                      amount: 5000
                      currency: USD
                    createdAt: 2026-07-13T17:36:00.835Z
                  policy:
                    name: vendor-payment
                    version: 2.0.0
                    schemaVersion: 1.0.0
                  signals:
                    vendorVerified: true
                    invoiceVerified: true
                    paymentApproved: true
                    sufficientFunds: true
                    paymentAmount: 5000
                    riskScore: 12
      responses:
        "200":
          description: Approved and released. The body is the signed Execution Trust
            Record; `executions[0].result` holds the connector's answer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-trust-record-response.schema"
              examples:
                test-fixture-flow:
                  summary: Real captured response, currently runnable (pairs with the
                    test-fixture-flow request example above; APPROVED)
                  value:
                    trustRecordId: 7ec8745e-fa10-4506-a247-c5a8b794eaa5
                    businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                    transaction:
                      businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                      metadata:
                        businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                        correlationId: 9c039d73-7fb1-461f-908f-c646c905633c
                        sourceSystem: docs-api-reference
                        submittedBy: docs-example
                        submittedAt: 2026-09-14T17:17:50.000Z
                      authority:
                        authorityId: f10f016f-b7c0-443e-8307-d26f5fa98067
                        authorityType: SERVICE
                        principalId: docs-example-caller
                        displayName: Docs Example Caller
                        issuedAt: 2026-09-14T17:17:50.000Z
                      authorization:
                        authorizationId: 5f112f4c-a43d-41dd-bfee-66a49efe33de
                        authorityId: f10f016f-b7c0-443e-8307-d26f5fa98067
                        purpose: "API reference example: refund authorization"
                        issuedAt: 2026-09-14T17:17:50.000Z
                      intent:
                        intentId: 462c2efd-3b88-4f59-8c47-8ba01a63d37e
                        authorizationId: 5f112f4c-a43d-41dd-bfee-66a49efe33de
                        action: test:fixture-execute
                        target: vendor://payments
                        parameters:
                          amount: 1000
                          currency: USD
                        createdAt: 2026-09-14T17:17:50.000Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                        contentHash: 27b396d84540aa792d88566da38537b671195c0c0049bac3aca41d4544a12b1a
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 1000
                        riskScore: 5
                        vendorId: vendor://payments
                      status: RECEIVED
                      createdAt: 2026-09-14T17:17:55.617Z
                    authorization:
                      payload:
                        version: 1
                        authorizationId: 381ef27a-0955-4085-9e5d-e4223fef2986
                        nonce: ed25c17c-c0b2-4600-b445-3c8cb557ac12
                        decisionId: b83deff8-ef50-49d0-830f-741cba8f5668
                        businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                        policyName: vendor-payment
                        policyVersion: 2.0.0
                        policyContentHash: 27b396d84540aa792d88566da38537b671195c0c0049bac3aca41d4544a12b1a
                        signalsHash: 50d0c4e1e3b0190c2074068627086e59049b0d2d224a71cb4fc9adf7124be287
                        submittedBy: docs-example
                        authorizedAt: 2026-09-14T17:17:55.627Z
                        expiresAt: 2026-09-14T17:19:55.627Z
                        businessTransactionHash: ba5498ce2474e9ef58a9c368c70fd4cf0a497a84199218783d3b2992799a77bc
                      signature: HZaohm/v3xrJTp+VrcJmLLcIG1SMl3LyPcHQUF6UHo7J9Z/KY92brpU92xVnPhVJJMaB+fd7nVRQ96k3OP7DBQ==
                      keyId: default
                      algorithm: ed25519
                    overrides: []
                    executions:
                      - executionId: 21a57d3e-bed5-4a73-8af9-2ac2d7e8326e
                        businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                        decision:
                          decisionId: b83deff8-ef50-49d0-830f-741cba8f5668
                          intentId: 462c2efd-3b88-4f59-8c47-8ba01a63d37e
                          policy:
                            name: vendor-payment
                            version: 2.0.0
                            schemaVersion: 1.0.0
                          signals:
                            vendorVerified: true
                            invoiceVerified: true
                            paymentApproved: true
                            sufficientFunds: true
                            paymentAmount: 1000
                            riskScore: 5
                            vendorId: vendor://payments
                          outcome: APPROVED
                          reason: Vendor payment authorized. Vendor verification, invoice verification,
                            payment approval, funding, and risk assessment
                            requirements were satisfied.
                          evaluatedAt: 2026-09-14T17:17:55.626Z
                        status: COMPLETED
                        mode: SYNC
                        startedAt: 2026-09-14T17:17:55.631Z
                        metadata:
                          authorizationId: 381ef27a-0955-4085-9e5d-e4223fef2986
                        previousChainHash: null
                        chainHash: f6f885358ddb3c820b3499d45c2a90a2624032ceece7bd53ece36809783e3220
                        chainSignature:
                          algorithm: ed25519
                          keyId: default
                          value: uXnVWKHYHDf8xRsl9oYdfFCem3f0n6IBzm2uYmxDnEkIU8FyOKxxWxeTidnF3A0UH2XSNNIROeeRzET6X/67BQ==
                          signedAt: 2026-09-14T17:17:55.652Z
                        evidence:
                          businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                          action: test:fixture-execute
                          target: vendor://payments
                          parameters:
                            amount: 1000
                            currency: USD
                          success: true
                          executedAt: 2026-09-14T17:17:55.646Z
                          attributes:
                            connector:
                              connectorId: test-fixture
                              connectorVersion: 1.0.0
                              capability: test:fixture-execute
                              sanitizedEndpoint: vendor://payments
                              credentialProviderId: static
                              requestSummary:
                                businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                                action: test:fixture-execute
                                target: vendor://payments
                                parameters:
                                  amount: 1000
                                  currency: USD
                              responseSummary:
                                success: true
                                metadata: {}
                              startedAt: 2026-09-14T17:17:55.645Z
                              completedAt: 2026-09-14T17:17:55.646Z
                              connectorEvidenceHash: 887b000a355c96ca432c7a709b24b50e89d879bc50699bc5b5280a957d981f70
                        completedAt: 2026-09-14T17:17:55.650Z
                    verifications:
                      - verificationId: 0e7de678-bb48-4fa0-ad53-3664d78d954b
                        businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                        status: VERIFIED
                        message: Execution Trust Record verified successfully.
                        verifiedAt: 2026-09-14T17:17:55.661Z
                        trustRecordHash: cd261f8b33ebd435ce31bdcafc971044aae165d12695446f587068052ce52807
                    receipts:
                      - receiptId: cf5f9ef1-1e53-459a-8f58-01c9dbe50f84
                        businessTransactionId: e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a
                        trustRecordHash: cd261f8b33ebd435ce31bdcafc971044aae165d12695446f587068052ce52807
                        receiptHash: 3a028e197e0c297761fd675696a6d418ffb67a10a07954cf4ed004e4ceb9f4f0
                        issuedAt: 2026-09-14T17:17:55.662Z
                        algorithm: ed25519
                        signature: S0rbIGkdEIWtk1fVqcEq8I+d2xUZHZQW28SFDolnLcd3hYYAWz2YQ+LkvlQc9OzbPE0vF/75VUrvlvIcbaOTAw==
                    createdAt: 2026-09-14T17:17:55.652Z
                    updatedAt: 2026-09-14T17:17:55.652Z
                    trustRecordHash: cd261f8b33ebd435ce31bdcafc971044aae165d12695446f587068052ce52807
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: lPz+IbGAQwPuLEzB7fEgiKU3dZTEeT3aIFqC/6LIRMBGK7UZor2nVkmItNUXu9RBCziAUH3yj2EchtwX6GAXBQ==
                      signedAt: 2026-09-14T17:17:55.655Z
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    trustRecordId: 65fee934-532c-4feb-88d9-72c0cd912a81
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    transaction:
                      businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                      metadata:
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:00.835Z
                      authority:
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:00.835Z
                      authorization:
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:00.835Z
                      intent:
                        intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        action: payments:execute
                        target: vendor/V-500
                        parameters:
                          amount: 5000
                          currency: USD
                        createdAt: 2026-07-13T17:36:00.835Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 5000
                        riskScore: 12
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:00.965Z
                    overrides: []
                    executions:
                      - executionId: 90746492-7721-4228-b244-67bf8a45ca68
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        decision:
                          decisionId: 8ec880cb-677a-4889-96f1-5a18a4edc1b5
                          intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                          policy:
                            name: vendor-payment
                            version: 2.0.0
                            schemaVersion: 1.0.0
                          signals:
                            vendorVerified: true
                            invoiceVerified: true
                            paymentApproved: true
                            sufficientFunds: true
                            paymentAmount: 5000
                            riskScore: 12
                          outcome: APPROVED
                          reason: Vendor payment authorized. Vendor verification, invoice verification,
                            payment approval, funding, and risk assessment
                            requirements were satisfied.
                          evaluatedAt: 2026-07-13T17:36:00.967Z
                        status: COMPLETED
                        mode: SYNC
                        startedAt: 2026-07-13T17:36:00.968Z
                        metadata:
                          authorizationId: dbc09266-2078-4d4e-93d0-f355eb7eebcc
                        evidence:
                          businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                          action: payments:execute
                          target: vendor/V-500
                          parameters:
                            amount: 5000
                            currency: USD
                          success: true
                          executedAt: 2026-07-13T17:36:00.970Z
                          attributes:
                            connector:
                              connectorId: vendor-payment
                              connectorVersion: 1.0.0
                              capability: payments:execute
                              sanitizedEndpoint: vendor/V-500
                              credentialProviderId: environment
                              requestSummary:
                                businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                                action: payments:execute
                                target: vendor/V-500
                                parameters:
                                  amount: 5000
                                  currency: USD
                              responseSummary:
                                success: true
                                metadata: {}
                              startedAt: 2026-07-13T17:36:00.970Z
                              completedAt: 2026-07-13T17:36:00.970Z
                              connectorEvidenceHash: c97411026a82b608d9b0f8b137a64e26d8af24bdda09310b639e527e08a9aada
                        completedAt: 2026-07-13T17:36:00.970Z
                    verifications:
                      - verificationId: e22354e0-7f22-49b2-bd2f-09e47881d788
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        status: VERIFIED
                        message: Execution Trust Record verified successfully.
                        verifiedAt: 2026-07-13T17:36:00.973Z
                        trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    receipts:
                      - receiptId: 863947e6-9d36-492e-b9c9-78b0f3e9b6df
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                        receiptHash: c9666b08b42bb5ff77650b544c3c2183609f25e4d8dc09359b71a970c57ed3ce
                        issuedAt: 2026-07-13T17:36:00.973Z
                        algorithm: ed25519
                        signature: Pcr42EvbaUbLogvVhYiV3g7uuwvZr82HUJhVptnXRxmTtiEGiz2SCdz5Z80xRDvmvSxpOSG5OnvMAaRzJXncBg==
                    createdAt: 2026-07-13T17:36:00.971Z
                    updatedAt: 2026-07-13T17:36:00.971Z
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: gjDYmHUBKIrv7bT3uwAkt3wHeoSwz5uANXQV5MfsFgbUIh83KiufZwxht3F3Een+7K/ZW9SRn0F0J7ACJRLbAg==
                      signedAt: 2026-07-13T17:36:00.972Z
        "400":
          description: The request is malformed and nothing was stored.
            `businessTransactionId` is missing or not a UUID, or the trust chain
            does not hold together (for example `authorization.authorityId` is
            not `authority.authorityId`, or `metadata.businessTransactionId`
            differs). The message names the field.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badUuid:
                  summary: Real captured response, malformed businessTransactionId
                  value:
                    error: businessTransactionId must be a valid UUID.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "Refused. `POLICY_DENIED`: the policy refused this request. Final
            for this request; nothing was released; do not resend the same
            facts. `CAPABILITY_NOT_ALLOWED`: your key may not invoke
            `intent.action`; an operator must add it to the key. No code:
            `authority.principalId` is missing, or your key may not act for it
            (it must equal your caller id, or be in the key's
            `allowedPrincipalIds`)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                principalNotAllowed:
                  summary: Real captured response, principalId not permitted for this caller
                  value:
                    error: Caller is not permitted to assert this authority.principalId.
                capabilityNotAllowed:
                  summary: The key may not invoke this action
                  value:
                    error: Caller is not permitted to invoke this capability.
                    code: CAPABILITY_NOT_ALLOWED
                policyDenied:
                  summary: A policy rejected the transaction, a final decision
                  value:
                    error: "Execution rejected: Vendor payment rejected because the assessed payment
                      risk exceeds the maximum permitted threshold."
                    code: POLICY_DENIED
        "404":
          description: "`policy.name` and `policy.version` do not match a policy this
            server has. Ask `GET /policies/in-effect` which to declare."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                policyNotFound:
                  summary: Real captured response, unknown policy referenced by the transaction
                  value:
                    error: Policy 'does-not-exist' version '9.9.9' was not found.
        "409":
          description: "This `businessTransactionId` was already used: nothing ran again;
            read the existing result with `GET
            /trust-records/{businessTransactionId}` or `GET
            /execution-intents/{businessTransactionId}`. Or
            `AUTHORIZATION_NO_LONGER_VALID` (RFC-0023 phase 4): decided, but at
            release a business fact it rested on was past its `validUntil`, or
            the authority grant had expired or been revoked; nothing was sent
            (`executionStatus: NOT_EXECUTED`), so send a new request."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                noLongerValid:
                  summary: "The response's exact form, from AuthorizationNoLongerValidError: the
                    grant was revoked between the decision and the release"
                  value:
                    error: "Not released: what the authorization for business transaction
                      '38dcf000-99ed-43af-8666-46c63c8ada74' rests on no longer
                      holds (authority grant
                      9f3cf733-a8ed-487c-bb58-a53950388cdf is no longer the
                      active grant of \"warehouse-agent\" for
                      \"erp:release-goods\" (revoked or replaced)). Nothing was
                      executed. Send a new request to be decided again."
                    code: AUTHORIZATION_NO_LONGER_VALID
                    executionStatus: NOT_EXECUTED
                duplicate:
                  summary: Real captured response
                  value:
                    error: Business Transaction 'eed2a972-1bf5-4166-8472-761f76fbf1b2' already
                      exists.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          description: "`EXECUTION_RECORD_INCOMPLETE`: the action was released but its
            signed record could not be produced or stored. Do not resend under a
            new id; an operator rebuilds the record with `POST
            /execution-intents/{businessTransactionId}/finalize`, which never
            calls the connector again. Without a code: an unexpected failure,
            for example a body missing a required nested object."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                executionRecordIncomplete:
                  summary: The action was released but its signed record could not be produced
                  value:
                    error: "The action for business transaction
                      'e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a' was released to the
                      execution system, but its signed Execution Trust Record
                      could not be produced. Do not retry as a new transaction:
                      reconcile against the connector and the execution audit
                      events for this businessTransactionId. authorizationId:
                      381ef27a-0955-4085-9e5d-e4223fef2986."
                    code: EXECUTION_RECORD_INCOMPLETE
                malformedBody:
                  summary: Real captured response, valid UUID but missing required nested fields
                    (e.g. metadata); no code field, this is the generic 500
                    handler, not a typed RuntimeError
                  value:
                    error: Internal Server Error
        "502":
          description: "`EXECUTION_OUTCOME_UNKNOWN`: the action was released and the
            connector call failed (unreachable, timed out, or answered with an
            error), so whether it was performed is unknown. The Execution Intent
            is `ERRORED`, and the response says `executionStatus:
            EXECUTION_UNKNOWN`. Do not resend under a new id: check the target
            system, then close the intent with `POST
            /execution-intents/{businessTransactionId}/resolve`. The message
            carries the ids, never the connector's own error."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                executionOutcomeUnknown:
                  summary: The action was released and the connector call failed
                  value:
                    error: "The action for business transaction
                      '38dcf000-99ed-43af-8666-46c63c8ada74' was released to the
                      execution system, but the call failed, so whether it was
                      performed is unknown. Do not retry as a new transaction:
                      check the target system, then close the Execution Intent
                      with POST
                      /execution-intents/38dcf000-99ed-43af-8666-46c63c8ada74/r\
                      esolve. authorizationId:
                      4d291c90-9e93-4a8a-b214-8cc6526fe2e3."
                    code: EXECUTION_OUTCOME_UNKNOWN
                    executionStatus: EXECUTION_UNKNOWN
        "503":
          $ref: "#/components/responses/SigningUnavailable"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # Sandbox: a receipt for a note, released only with a signed
            approval.

            # The script makes a new businessTransactionId on each run (each id
            can be

            # used once). Without the approval (receiptApproved false) the
            request is

            # refused. To release it, first get an approval from POST
            /sandbox/approvals

            # for the same target and add it as signals.approvalArtifact with

            # receiptApproved true.

            TXN_ID=$( (uuidgen 2>/dev/null || python3 -c 'import uuid;
            print(uuid.uuid4())') | tr 'A-Z' 'a-z')

            curl -X POST https://parmana-sandbox.vercel.app/execute \
              --silent \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "$(cat <<JSON
            {
              "businessTransactionId": "$TXN_ID",
              "metadata": {
                "businessTransactionId": "$TXN_ID",
                "correlationId": "cc1ca975-4935-4e9a-a591-8897af6b56fb",
                "sourceSystem": "docs-example",
                "submittedBy": "docs-reader",
                "submittedAt": "2026-07-13T17:36:00.835Z"
              },
              "authority": {
                "authorityId": "authority-1",
                "authorityType": "SERVICE",
                "principalId": "sandbox-visitor",
                "issuedAt": "2026-07-13T17:36:00.835Z"
              },
              "authorization": {
                "authorizationId": "authorization-1",
                "authorityId": "authority-1",
                "purpose": "Trying the Parmana sandbox",
                "issuedAt": "2026-07-13T17:36:00.835Z"
              },
              "intent": {
                "intentId": "intent-1",
                "authorizationId": "authorization-1",
                "action": "sandbox:receipt",
                "target": "order-docs-example",
                "parameters": { "note": "hello" },
                "createdAt": "2026-07-13T17:36:00.835Z"
              },
              "policy": {
                "name": "sandbox-receipt",
                "version": "1.0.0",
                "schemaVersion": "1.0.0"
              },
              "signals": {
                "note": "hello",
                "receiptApproved": false
              }
            }

            JSON

            )"
        - lang: typescript
          label: TypeScript
          source: >-
            import { createBusinessTransaction, ParmanaClient } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const inEffect = await client.policyInEffect("paytm:refund");


            // The signed approval the approver sent, for this order and up to
            this amount.

            const approval = JSON.parse(readFileSync("approval.json", "utf8"));


            const transaction = createBusinessTransaction({
              principalId: (await client.caller()).callerId,
              purpose: "Refund order ORD-1042",
              action: "paytm:refund",
              target: "ORD-1042",
              parameters: { orderId: "ORD-1042", transactionId: "TXN-9", amount: 750 },
              policy: inEffect.policy,
              signals: {
                refundEligible: true,
                fraudCheckPassed: true,
                refundAmount: 750,
                managerApproved: true,
                approvalArtifact: approval,
              },
            });


            const record = await client.execute(transaction);


            console.log(
              record.businessTransactionId,
              record.executions[0]?.decision.outcome,
            );
        - lang: python
          label: Python
          source: >-
            import json

            import os

            from pathlib import Path


            from parmana import ParmanaClient, create_business_transaction


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            in_effect = client.policy_in_effect("paytm:refund")


            # The signed approval the approver sent, for this order and up to
            this amount.

            approval = json.loads(Path("approval.json").read_text())


            transaction = create_business_transaction(
                principal_id=client.caller().caller_id,
                purpose="Refund order ORD-1042",
                action="paytm:refund",
                target="ORD-1042",
                parameters={"orderId": "ORD-1042", "transactionId": "TXN-9", "amount": 750},
                policy=in_effect.policy,
                signals={
                    "refundEligible": True,
                    "fraudCheckPassed": True,
                    "refundAmount": 750,
                    "managerApproved": True,
                    "approvalArtifact": approval,
                },
            )


            record = client.execute(transaction)


            print(record.business_transaction_id,
            record.executions[0].decision.outcome)
  /transactions:
    post:
      tags:
        - Transactions
      summary: Create (execute) a Business Transaction
      operationId: createTransaction
      description: >
        In the sandbox, each `businessTransactionId` works once: a prefilled id
        returns `409` after its first use.

        For a new id each run, use the [playground](/playground) or the cURL
        sample, which generate one.

        The same as `POST /execute` in every way except the success status,
        `201` instead of `200`. Use

        whichever fits your client. Read [Execute a Business
        Transaction](/api-reference/endpoints/execute-transaction)

        for the full behavior, the outcomes and what to do after each.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/transaction-create-request.schema"
            examples:
              sandbox-receipt:
                summary: The public sandbox's receipt action, sent without an approval. The
                  sandbox refuses it (403 POLICY_DENIED), as the playground
                  shows. Get a demo approval from POST /sandbox/approvals for
                  the same target and add it to signals to release it.
                value:
                  businessTransactionId: ae94f2aa-6120-4c83-bf5d-7108e380d5a8
                  metadata:
                    businessTransactionId: ae94f2aa-6120-4c83-bf5d-7108e380d5a8
                    correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                    sourceSystem: docs-example
                    submittedBy: docs-reader
                    submittedAt: 2026-07-13T17:36:00.835Z
                  authority:
                    authorityId: authority-1
                    authorityType: SERVICE
                    principalId: sandbox-visitor
                    issuedAt: 2026-07-13T17:36:00.835Z
                  authorization:
                    authorizationId: authorization-1
                    authorityId: authority-1
                    purpose: Trying the Parmana sandbox
                    issuedAt: 2026-07-13T17:36:00.835Z
                  intent:
                    intentId: intent-1
                    authorizationId: authorization-1
                    action: sandbox:receipt
                    target: order-docs-example
                    parameters:
                      note: hello
                    createdAt: 2026-07-13T17:36:00.835Z
                  policy:
                    name: sandbox-receipt
                    version: 1.0.0
                    schemaVersion: 1.0.0
                  signals:
                    note: hello
                    receiptApproved: false
              vendor-payment-flow-transactions-endpoint:
                summary: Real captured request (vendor-payment 2.0.0, APPROVED), includes a
                  client-supplied status and an unrecognized top-level field to
                  demonstrate both are rejected/dropped, never persisted (see
                  the 201 response below)
                value:
                  businessTransactionId: ffe803db-fb24-498a-9826-47ddc7e7167d
                  metadata:
                    businessTransactionId: ffe803db-fb24-498a-9826-47ddc7e7167d
                    correlationId: 303003bf-3939-407d-855d-ffb338532d6d
                    sourceSystem: vendor-payment-service
                    submittedBy: ap-automation
                    submittedAt: 2026-07-08T03:54:34.783Z
                  authority:
                    authorityId: 05ff9e2e-e5dd-40aa-9964-02c2574396c9
                    authorityType: SERVICE
                    principalId: ap-automation-svc
                    displayName: Accounts Payable Automation
                    issuedAt: 2026-07-08T03:54:34.784Z
                  authorization:
                    authorizationId: 5fb16a24-6688-4f6f-98ee-53a117f0f2c9
                    authorityId: 05ff9e2e-e5dd-40aa-9964-02c2574396c9
                    purpose: Authorize vendor payment disbursement
                    issuedAt: 2026-07-08T03:54:34.784Z
                  intent:
                    intentId: 023fbdd4-f206-4a42-b7dd-d5f865fd2c74
                    authorizationId: 5fb16a24-6688-4f6f-98ee-53a117f0f2c9
                    action: payments:execute
                    target: vendor/V-300
                    parameters:
                      amount: 3300
                      currency: USD
                    createdAt: 2026-07-08T03:54:34.784Z
                  policy:
                    name: vendor-payment
                    version: 2.0.0
                    schemaVersion: 1.0.0
                  signals:
                    vendorVerified: true
                    invoiceVerified: true
                    paymentApproved: true
                    sufficientFunds: true
                    paymentAmount: 3300
                    riskScore: 9
                  status: APPROVED
                  unexpectedField: should-not-be-persisted
      responses:
        "201":
          description: Approved and released. The body is the signed Execution Trust Record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-trust-record-response.schema"
              examples:
                test-fixture-flow:
                  summary: Real captured response, currently runnable (pairs with the
                    test-fixture-flow request example above; APPROVED, 201)
                  value:
                    trustRecordId: 2770d2b5-9464-4c74-87aa-7a23ae4be7cd
                    businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                    transaction:
                      businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                      metadata:
                        businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                        sourceSystem: docs-api-reference
                        submittedBy: docs-example
                      authority:
                        authorityId: be8afa44-ee3c-44c2-a4a2-ba119e128948
                        authorityType: SERVICE
                        principalId: docs-example-caller
                        issuedAt: 2026-09-14T17:18:27.000Z
                      authorization:
                        authorizationId: 81a1c120-d296-458b-bbe5-824a8617f23a
                        authorityId: be8afa44-ee3c-44c2-a4a2-ba119e128948
                        purpose: "API reference example: transactions endpoint"
                        issuedAt: 2026-09-14T17:18:27.000Z
                      intent:
                        intentId: ceb89d68-13ac-4ff9-b7b9-dd0e15637baa
                        authorizationId: 81a1c120-d296-458b-bbe5-824a8617f23a
                        action: test:fixture-execute
                        target: vendor://payments
                        parameters:
                          amount: 250
                          currency: USD
                        createdAt: 2026-09-14T17:18:27.000Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                        contentHash: 27b396d84540aa792d88566da38537b671195c0c0049bac3aca41d4544a12b1a
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 250
                        riskScore: 5
                        vendorId: vendor://payments
                      status: RECEIVED
                      createdAt: 2026-09-14T17:18:27.958Z
                    authorization:
                      payload:
                        version: 1
                        authorizationId: 232afef4-b320-4596-906c-b6adf6ef83ff
                        nonce: b138d6ca-c509-4b3d-a2d1-09abd113d5a0
                        decisionId: ac059ca3-9c2c-4e32-b4ad-bb8787444b2c
                        businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                        policyName: vendor-payment
                        policyVersion: 2.0.0
                        policyContentHash: 27b396d84540aa792d88566da38537b671195c0c0049bac3aca41d4544a12b1a
                        signalsHash: 7518e06daf697fbdbacfb7f0f88d42cbe24aefae6e82b3801e60efa22c62f12c
                        submittedBy: docs-example
                        authorizedAt: 2026-09-14T17:18:27.960Z
                        expiresAt: 2026-09-14T17:20:27.960Z
                        businessTransactionHash: aa491841d05ce70f7f7157fbb8ba6fee0c4f886ea5fc16fa22de21e1bcaf25da
                      signature: 01OnI4GwrHrDeVneUHn01CnLnkwRN+pTHYQmHfEFJUIjIKIHmqe28QWe6KEcEYkzAhtJIS8ZmpvNVvW2pn5IBA==
                      keyId: default
                      algorithm: ed25519
                    overrides: []
                    executions:
                      - executionId: d5a2dfb7-c642-43bc-a8ad-132df83a4a5a
                        businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                        decision:
                          decisionId: ac059ca3-9c2c-4e32-b4ad-bb8787444b2c
                          intentId: ceb89d68-13ac-4ff9-b7b9-dd0e15637baa
                          policy:
                            name: vendor-payment
                            version: 2.0.0
                            schemaVersion: 1.0.0
                          signals:
                            vendorVerified: true
                            invoiceVerified: true
                            paymentApproved: true
                            sufficientFunds: true
                            paymentAmount: 250
                            riskScore: 5
                            vendorId: vendor://payments
                          outcome: APPROVED
                          reason: Vendor payment authorized. Vendor verification, invoice verification,
                            payment approval, funding, and risk assessment
                            requirements were satisfied.
                          evaluatedAt: 2026-09-14T17:18:27.960Z
                        status: COMPLETED
                        mode: SYNC
                        startedAt: 2026-09-14T17:18:27.963Z
                        metadata:
                          authorizationId: 232afef4-b320-4596-906c-b6adf6ef83ff
                        previousChainHash: null
                        chainHash: bb16b8a121a770e956bd2b81ca23e9f73fc69ce5213d6711b4f3aa29aed37d5f
                        chainSignature:
                          algorithm: ed25519
                          keyId: default
                          value: wgxGxFN6lFvet10b3M01GliPAnazoYFFHh498w6vc/Stm1xTpe9af/Ee+9SsngEylzqs4E7EAbfG8+ehgyOHCw==
                          signedAt: 2026-09-14T17:18:27.975Z
                        evidence:
                          businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                          action: test:fixture-execute
                          target: vendor://payments
                          parameters:
                            amount: 250
                            currency: USD
                          success: true
                          executedAt: 2026-09-14T17:18:27.971Z
                          attributes:
                            connector:
                              connectorId: test-fixture
                              connectorVersion: 1.0.0
                              capability: test:fixture-execute
                              sanitizedEndpoint: vendor://payments
                              credentialProviderId: static
                              requestSummary:
                                businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                                action: test:fixture-execute
                                target: vendor://payments
                                parameters:
                                  amount: 250
                                  currency: USD
                              responseSummary:
                                success: true
                                metadata: {}
                              startedAt: 2026-09-14T17:18:27.971Z
                              completedAt: 2026-09-14T17:18:27.971Z
                              connectorEvidenceHash: af0c1560344e9376c903ad77ae5574ae5cb7f81e968d286b9e33e66b790059ea
                        completedAt: 2026-09-14T17:18:27.973Z
                    verifications:
                      - verificationId: db0a48c7-893f-4cec-982f-f95e23608e5b
                        businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                        status: VERIFIED
                        message: Execution Trust Record verified successfully.
                        verifiedAt: 2026-09-14T17:18:27.981Z
                        trustRecordHash: 05fe72e8943fca8f451f631cd4f3ae61f8b62e0e8e92410d13166c67605d7892
                    receipts:
                      - receiptId: 0601d04d-3d3c-4b0c-9b37-e811b7eb8375
                        businessTransactionId: 4cfe5661-42a6-4db0-8372-2d24dca235f2
                        trustRecordHash: 05fe72e8943fca8f451f631cd4f3ae61f8b62e0e8e92410d13166c67605d7892
                        receiptHash: f95295cb63aa045e17835fc06b367cb5305878e8ce13b268c9b72371c0da05f6
                        issuedAt: 2026-09-14T17:18:27.981Z
                        algorithm: ed25519
                        signature: PYhfZtyR+ON2X94ik82TSlh+IgBJT3PPz9FieAwgwepHR7K+LDNOHtVwpCbOakw2z3yeF+S/a8CGRBwGLUSxCA==
                    createdAt: 2026-09-14T17:18:27.975Z
                    updatedAt: 2026-09-14T17:18:27.975Z
                    trustRecordHash: 05fe72e8943fca8f451f631cd4f3ae61f8b62e0e8e92410d13166c67605d7892
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: 7q+9s+1mzxTdnwL5ogZnVHdNP4RqrBfJICu+tYdtQw+JObBcLVi1BESqin8KpJ+uZrmo+AcEgigmyKmCnc4ADQ==
                      signedAt: 2026-09-14T17:18:27.977Z
                vendor-payment-flow-transactions-endpoint:
                  summary: "Real captured response, the client-supplied status (APPROVED) and
                    unexpectedField from the request example above are both
                    absent: status is server-set to RECEIVED and the
                    unrecognized field was silently dropped"
                  value:
                    trustRecordId: d2111a67-86bb-4d5f-952c-a5a44d5e5144
                    businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                    transaction:
                      businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                      metadata:
                        businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                        correlationId: a6ea6a80-d032-4749-9872-1843efaac04c
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:20.390Z
                      authority:
                        authorityId: a9c3938d-de22-454a-a75c-1666f3c1693d
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:20.390Z
                      authorization:
                        authorizationId: b45f2e89-6c0b-4534-8047-9a75734ccecf
                        authorityId: a9c3938d-de22-454a-a75c-1666f3c1693d
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:20.390Z
                      intent:
                        intentId: 578850fb-277a-4bb7-99dd-6761961e10e1
                        authorizationId: b45f2e89-6c0b-4534-8047-9a75734ccecf
                        action: payments:execute
                        target: vendor/V-700
                        parameters:
                          amount: 3300
                          currency: USD
                        createdAt: 2026-07-13T17:36:20.390Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 3300
                        riskScore: 9
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:20.483Z
                    overrides: []
                    executions:
                      - executionId: 028cdd7b-d04a-400f-ad1a-08eedc69e31e
                        businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                        decision:
                          decisionId: d70f66bd-2674-4c07-8c2f-f9a19f8f5fd2
                          intentId: 578850fb-277a-4bb7-99dd-6761961e10e1
                          policy:
                            name: vendor-payment
                            version: 2.0.0
                            schemaVersion: 1.0.0
                          signals:
                            vendorVerified: true
                            invoiceVerified: true
                            paymentApproved: true
                            sufficientFunds: true
                            paymentAmount: 3300
                            riskScore: 9
                          outcome: APPROVED
                          reason: Vendor payment authorized. Vendor verification, invoice verification,
                            payment approval, funding, and risk assessment
                            requirements were satisfied.
                          evaluatedAt: 2026-07-13T17:36:20.484Z
                        status: COMPLETED
                        mode: SYNC
                        startedAt: 2026-07-13T17:36:20.485Z
                        metadata:
                          authorizationId: 3a71d9ab-5f36-42b0-ba5b-214179fde2d9
                        evidence:
                          businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                          action: payments:execute
                          target: vendor/V-700
                          parameters:
                            amount: 3300
                            currency: USD
                          success: true
                          executedAt: 2026-07-13T17:36:20.486Z
                          attributes:
                            connector:
                              connectorId: vendor-payment
                              connectorVersion: 1.0.0
                              capability: payments:execute
                              sanitizedEndpoint: vendor/V-700
                              credentialProviderId: environment
                              requestSummary:
                                businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                                action: payments:execute
                                target: vendor/V-700
                                parameters:
                                  amount: 3300
                                  currency: USD
                              responseSummary:
                                success: true
                                metadata: {}
                              startedAt: 2026-07-13T17:36:20.486Z
                              completedAt: 2026-07-13T17:36:20.486Z
                              connectorEvidenceHash: d172eee9244766ced4e30678711bdb3093250cf3a93adb4b0ab41914b2da36c6
                        completedAt: 2026-07-13T17:36:20.486Z
                    verifications:
                      - verificationId: 39a20fd1-50a9-4b3a-a38b-7d261c616f41
                        businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                        status: VERIFIED
                        message: Execution Trust Record verified successfully.
                        verifiedAt: 2026-07-13T17:36:20.487Z
                        trustRecordHash: 20f53f013d8fb7a93666bdeca0fc6015b009406e4ae686e74244a626a66dd781
                    receipts:
                      - receiptId: 7bd6d010-a9c1-440f-9823-501fc116e64f
                        businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                        trustRecordHash: 20f53f013d8fb7a93666bdeca0fc6015b009406e4ae686e74244a626a66dd781
                        receiptHash: fa267b72b5fdce8e4ab5e3273f788f08ffaadb27c7af47c34b94aa872b37ca40
                        issuedAt: 2026-07-13T17:36:20.487Z
                        algorithm: ed25519
                        signature: XZ4/ADgeML5fI7iWhTECN+dA642KROMjSLkMJdHGITNBXkryK+NWKkzzw9VGorBlBynrPHPF70OhgbNtrxqgAg==
                    createdAt: 2026-07-13T17:36:20.486Z
                    updatedAt: 2026-07-13T17:36:20.486Z
                    trustRecordHash: 20f53f013d8fb7a93666bdeca0fc6015b009406e4ae686e74244a626a66dd781
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: teph6TYn3Q0s+/hjz3TQr4uNmaRrGaPsozXkbiEkf0MjfPDIEChYa2pBjrQktLFwXW8X8cnih8/z3Sd8vjasDA==
                      signedAt: 2026-07-13T17:36:20.487Z
        "400":
          description: The request is malformed and nothing was stored. The same checks as
            `POST /execute`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badUuid:
                  summary: Real captured response, malformed businessTransactionId
                  value:
                    error: businessTransactionId must be a valid UUID.
                invariantMismatch:
                  summary: Real captured response, authorization.authorityId does not match
                    authority.authorityId
                  value:
                    error: authorization.authorityId must match authority.authorityId.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Refused, with the same codes and meaning as `POST /execute`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                principalNotAllowed:
                  summary: Real captured response, principalId not permitted for this caller
                  value:
                    error: Caller is not permitted to assert this authority.principalId.
                capabilityNotAllowed:
                  summary: The key may not invoke this action
                  value:
                    error: Caller is not permitted to invoke this capability.
                    code: CAPABILITY_NOT_ALLOWED
                policyDenied:
                  summary: A policy rejected the transaction, a final decision
                  value:
                    error: "Execution rejected: Vendor payment rejected because the assessed payment
                      risk exceeds the maximum permitted threshold."
                    code: POLICY_DENIED
        "404":
          description: "`policy.name` and `policy.version` do not match a policy this
            server has."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                policyNotFound:
                  summary: Real captured response, unknown policy referenced by the transaction
                  value:
                    error: Policy 'does-not-exist' version '9.9.9' was not found.
        "409":
          description: This `businessTransactionId` was already used. Nothing ran again.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                duplicate:
                  summary: Real captured response
                  value:
                    error: Business Transaction '1a341e62-18de-408e-9b34-89aec901dae5' already
                      exists.
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          description: "`EXECUTION_RECORD_INCOMPLETE`: released, but the signed record
            could not be produced. Do not resend; an operator finalizes it. See
            `POST /execute`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                executionRecordIncomplete:
                  summary: The action was released but its signed record could not be produced
                  value:
                    error: "The action for business transaction
                      'e54187e6-431f-4e1c-9c0e-ff2cb5d6d40a' was released to the
                      execution system, but its signed Execution Trust Record
                      could not be produced. Do not retry as a new transaction:
                      reconcile against the connector and the execution audit
                      events for this businessTransactionId. authorizationId:
                      381ef27a-0955-4085-9e5d-e4223fef2986."
                    code: EXECUTION_RECORD_INCOMPLETE
        "502":
          description: "`EXECUTION_OUTCOME_UNKNOWN`: released, then the connector call
            failed. Do not resend under a new id; check the target system, then
            resolve the intent. See `POST /execute`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                executionOutcomeUnknown:
                  summary: The action was released and the connector call failed
                  value:
                    error: "The action for business transaction
                      '38dcf000-99ed-43af-8666-46c63c8ada74' was released to the
                      execution system, but the call failed, so whether it was
                      performed is unknown. Do not retry as a new transaction:
                      check the target system, then close the Execution Intent
                      with POST
                      /execution-intents/38dcf000-99ed-43af-8666-46c63c8ada74/r\
                      esolve. authorizationId:
                      4d291c90-9e93-4a8a-b214-8cc6526fe2e3."
                    code: EXECUTION_OUTCOME_UNKNOWN
        "503":
          $ref: "#/components/responses/SigningUnavailable"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X POST https://parmana-sandbox.vercel.app/transactions \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "businessTransactionId": "4cfe5661-42a6-4db0-8372-2d24dca235f2",
                "metadata": {
                  "businessTransactionId": "4cfe5661-42a6-4db0-8372-2d24dca235f2",
                  "sourceSystem": "docs-api-reference",
                  "submittedBy": "docs-example"
                },
                "authority": {
                  "authorityId": "be8afa44-ee3c-44c2-a4a2-ba119e128948",
                  "authorityType": "SERVICE",
                  "principalId": "docs-example-caller",
                  "issuedAt": "2026-09-14T17:18:27.000Z"
                },
                "authorization": {
                  "authorizationId": "81a1c120-d296-458b-bbe5-824a8617f23a",
                  "authorityId": "be8afa44-ee3c-44c2-a4a2-ba119e128948",
                  "purpose": "API reference example: transactions endpoint",
                  "issuedAt": "2026-09-14T17:18:27.000Z"
                },
                "intent": {
                  "intentId": "ceb89d68-13ac-4ff9-b7b9-dd0e15637baa",
                  "authorizationId": "81a1c120-d296-458b-bbe5-824a8617f23a",
                  "action": "test:fixture-execute",
                  "target": "vendor://payments",
                  "parameters": {
                    "amount": 250,
                    "currency": "USD"
                  },
                  "createdAt": "2026-09-14T17:18:27.000Z"
                },
                "policy": {
                  "name": "vendor-payment",
                  "version": "2.0.0",
                  "schemaVersion": "1.0.0"
                },
                "signals": {
                  "vendorVerified": true,
                  "invoiceVerified": true,
                  "paymentApproved": true,
                  "sufficientFunds": true,
                  "paymentAmount": 250,
                  "riskScore": 5,
                  "vendorId": "vendor://payments"
                }
              }'
        - lang: typescript
          label: TypeScript
          source: >-
            import { createBusinessTransaction, ParmanaClient } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const inEffect = await client.policyInEffect("paytm:refund");


            // The signed approval the approver sent, for this order and up to
            this amount.

            const approval = JSON.parse(readFileSync("approval.json", "utf8"));


            const transaction = createBusinessTransaction({
              principalId: (await client.caller()).callerId,
              purpose: "Refund order ORD-1042",
              action: "paytm:refund",
              target: "ORD-1042",
              parameters: { orderId: "ORD-1042", transactionId: "TXN-9", amount: 750 },
              policy: inEffect.policy,
              signals: {
                refundEligible: true,
                fraudCheckPassed: true,
                refundAmount: 750,
                managerApproved: true,
                approvalArtifact: approval,
              },
            });


            const record = await client.createTransaction(transaction);


            console.log(
              record.businessTransactionId,
              record.executions[0]?.decision.outcome,
            );
        - lang: python
          label: Python
          source: >-
            import json

            import os

            from pathlib import Path


            from parmana import ParmanaClient, create_business_transaction


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            in_effect = client.policy_in_effect("paytm:refund")


            # The signed approval the approver sent, for this order and up to
            this amount.

            approval = json.loads(Path("approval.json").read_text())


            transaction = create_business_transaction(
                principal_id=client.caller().caller_id,
                purpose="Refund order ORD-1042",
                action="paytm:refund",
                target="ORD-1042",
                parameters={"orderId": "ORD-1042", "transactionId": "TXN-9", "amount": 750},
                policy=in_effect.policy,
                signals={
                    "refundEligible": True,
                    "fraudCheckPassed": True,
                    "refundAmount": 750,
                    "managerApproved": True,
                    "approvalArtifact": approval,
                },
            )


            record = client.create_transaction(transaction)


            print(record.business_transaction_id,
            record.executions[0].decision.outcome)
    get:
      tags:
        - Transactions
      summary: List Business Transactions
      operationId: listTransactions
      description: >
        Lists the Business Transactions your key submitted, as a bare array with
        no paging metadata. Each

        item is the request as stored; for the signed outcome of each, use `GET
        /trust-records`. A page can hold fewer than `pageSize` items, or none,
        when other callers'

        transactions fall in that window, so keep paging until you have what you
        need.
      parameters:
        - name: page
          description: Page number, starting at 1. Default 1.
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: pageSize
          description: Items per page before filtering to your own transactions. Default
            25. Maximum 100.
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
      responses:
        "200":
          description: Your transactions on this page. May be empty.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/transactions-list-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response (three transactions accepted so far)
                  value:
                    - businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                      metadata:
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:00.835Z
                      authority:
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:00.835Z
                      authorization:
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:00.835Z
                      intent:
                        intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        action: payments:execute
                        target: vendor/V-500
                        parameters:
                          amount: 5000
                          currency: USD
                        createdAt: 2026-07-13T17:36:00.835Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 5000
                        riskScore: 12
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:00.965Z
                    - businessTransactionId: f0a91005-de49-4f50-b6b0-c89f6b0cabb8
                      metadata:
                        businessTransactionId: f0a91005-de49-4f50-b6b0-c89f6b0cabb8
                        correlationId: e24cb9aa-c5a6-475c-9a1b-4203a68d053a
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:01.524Z
                      authority:
                        authorityId: e494f79a-a93d-492d-816f-96ed0624195b
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:01.524Z
                      authorization:
                        authorizationId: 5802dbf2-13a6-4bf2-b515-0df13b5472c0
                        authorityId: e494f79a-a93d-492d-816f-96ed0624195b
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:01.524Z
                      intent:
                        intentId: 5707cd17-8b5a-455a-a6ee-50bebc619ebb
                        authorizationId: 5802dbf2-13a6-4bf2-b515-0df13b5472c0
                        action: payments:execute
                        target: vendor/V-600
                        parameters:
                          amount: 6000
                          currency: USD
                        createdAt: 2026-07-13T17:36:01.524Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 6000
                        riskScore: 15
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:01.629Z
                    - businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                      metadata:
                        businessTransactionId: a0477e25-db8d-447d-9dbe-633e4705275f
                        correlationId: a6ea6a80-d032-4749-9872-1843efaac04c
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:20.390Z
                      authority:
                        authorityId: a9c3938d-de22-454a-a75c-1666f3c1693d
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:20.390Z
                      authorization:
                        authorizationId: b45f2e89-6c0b-4534-8047-9a75734ccecf
                        authorityId: a9c3938d-de22-454a-a75c-1666f3c1693d
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:20.390Z
                      intent:
                        intentId: 578850fb-277a-4bb7-99dd-6761961e10e1
                        authorizationId: b45f2e89-6c0b-4534-8047-9a75734ccecf
                        action: payments:execute
                        target: vendor/V-700
                        parameters:
                          amount: 3300
                          currency: USD
                        createdAt: 2026-07-13T17:36:20.390Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 3300
                        riskScore: 9
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:20.483Z
        "400":
          description: page or pageSize is not a whole number in range. Nothing was read.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                pageSizeTooLarge:
                  summary: Captured from the test suite, pageSize=1000000
                  value:
                    error: pageSize must be a whole number from 1 to 100.
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl
            "https://parmana-sandbox.vercel.app/transactions?page=1&pageSize=25"
            \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const transactions = await client.transactions(1, 25);

            console.log(transactions.length);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            transactions = client.transactions.list(page=1, page_size=25)

            print(len(transactions))
  /transactions/{businessTransactionId}:
    get:
      tags:
        - Transactions
      summary: Get Business Transaction
      operationId: getTransaction
      description: >
        Returns one Business Transaction as it was stored. For the signed
        outcome, what was decided and

        executed, use `GET /trust-records/{businessTransactionId}`.
      parameters:
        - name: businessTransactionId
          in: path
          required: true
          description: The id you chose when you sent the transaction. In the sandbox, the
            value shown is a seeded transaction kept for these docs. To try your
            own, run the playground and use the `businessTransactionId` it
            prints.
          schema:
            type: string
            x-default: 6f6582a1-4e1a-4800-a73b-cb4c3aa32110
      responses:
        "200":
          description: The Business Transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/transaction-response.schema"
              examples:
                vendor-payment-flow-transactions-endpoint:
                  summary: Real captured response
                  value:
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    metadata:
                      businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                      correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                      sourceSystem: vendor-payment-service
                      submittedBy: ap-automation
                      submittedAt: 2026-07-13T17:36:00.835Z
                    authority:
                      authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                      authorityType: SERVICE
                      principalId: ap-automation-svc
                      displayName: Accounts Payable Automation
                      issuedAt: 2026-07-13T17:36:00.835Z
                    authorization:
                      authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                      authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                      purpose: Authorize vendor payment disbursement
                      issuedAt: 2026-07-13T17:36:00.835Z
                    intent:
                      intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                      authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                      action: payments:execute
                      target: vendor/V-500
                      parameters:
                        amount: 5000
                        currency: USD
                      createdAt: 2026-07-13T17:36:00.835Z
                    policy:
                      name: vendor-payment
                      version: 2.0.0
                      schemaVersion: 1.0.0
                    signals:
                      vendorVerified: true
                      invoiceVerified: true
                      paymentApproved: true
                      sufficientFunds: true
                      paymentAmount: 5000
                      riskScore: 12
                    status: RECEIVED
                    createdAt: 2026-07-13T17:36:00.965Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key submitted no transaction with this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Business Transaction not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/transactions/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const transaction = await client.transaction(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(transaction.intent.action);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            transaction =
            client.transaction("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(transaction.intent.action)
  /verify:
    post:
      tags:
        - Verification
      summary: Verify an Execution Trust Record
      operationId: verifyTransaction
      description: >
        Checks a Trust Record again on the server: recomputes its hash, verifies
        its signature and checks

        that every approved execution is bound to its authorization. All checks
        run and every failure is

        reported. The result is appended to the record as a new Verification. A
        receipt can only be

        generated after a `VERIFIED` result. To verify without trusting this
        server, use

        [Verify independently](/guides/verify-independently).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/verification-request.schema"
            examples:
              vendor-payment-flow:
                summary: Real captured request
                value:
                  businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
      responses:
        "200":
          description: "The check ran. Read `status`: `VERIFIED` or `FAILED`. A `200`
            alone does not mean the record verified."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/verification-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    verificationId: 63aac300-e0a8-47e9-9bb1-fd4b7d6cfdce
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    status: VERIFIED
                    message: Execution Trust Record verified successfully.
                    verifiedAt: 2026-07-13T17:42:11.002Z
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
        "400":
          description: "`businessTransactionId` is missing or not a UUID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badUuid:
                  summary: Real captured response
                  value:
                    error: businessTransactionId must be a valid UUID.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
                    code: VERIFICATION_FAILED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl -X POST https://parmana-sandbox.vercel.app/verify \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "$(cat <<JSON
            {
                "businessTransactionId": "$TXN_ID"
            }

            JSON

            )"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const verification = await client.verify(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(verification);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            verification = client.verify("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            print(verification)
  /verification/{businessTransactionId}:
    get:
      tags:
        - Verification
      summary: Get latest Verification
      operationId: getLatestVerification
      description: Returns the most recent Verification appended to a Trust Record by
        `POST /verify`.
      parameters:
        - name: businessTransactionId
          description: The transaction's id. In the sandbox, the value shown is a seeded
            transaction kept for these docs. To try your own, run the playground
            and use the `businessTransactionId` it prints.
          in: path
          required: true
          schema:
            type: string
            x-default: 6f6582a1-4e1a-4800-a73b-cb4c3aa32110
      responses:
        "200":
          description: The latest Verification.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/verification-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    verificationId: 63aac300-e0a8-47e9-9bb1-fd4b7d6cfdce
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    status: VERIFIED
                    message: Execution Trust Record verified successfully.
                    verifiedAt: 2026-07-13T17:42:11.002Z
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id, or the record has not
            been verified yet. Run `POST /verify` first.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                trustRecordNotFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/verification/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const verification = await client.getLatestVerification(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(verification);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            verification = client.get_latest_verification("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            print(verification)
  /refusal/verify:
    post:
      tags:
        - Refusal Records
      summary: Verify a Refusal Record's signature
      operationId: verifyRefusalRecord
      description: >
        Checks a Refusal Record's signature. Send the record itself; the server
        reads no storage and needs no

        key, so anyone holding a refusal can prove Parmana issued it and that it
        was not changed. Fetch the

        record with `GET /refusal/{businessTransactionId}`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/refusal-verify-request.schema"
      responses:
        "200":
          description: The check ran. Read `valid`. A `200` alone does not mean the
            signature verified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/verify-result-response.schema"
              examples:
                valid:
                  summary: Real captured response
                  value:
                    valid: true
        "400":
          description: The body is not shaped like a 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).
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # The Refusal Record, as returned by GET
            /refusal/<businessTransactionId>:

            curl -X POST https://parmana-sandbox.vercel.app/refusal/verify \
              -H "Content-Type: application/json" \
              --data @refusal-record.json
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const record = await client.refusalRecord(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );
            const valid = await client.verifyRefusalRecord(record);

            console.log(valid);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            record =
            client.refusal_record("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            valid = client.verify_refusal_record(record)


            print(valid)
  /parmana-handbook.pdf:
    get:
      tags:
        - Handbook
      summary: Download the Parmana Handbook (PDF)
      operationId: getHandbookPdf
      description: Downloads the Parmana Handbook as a PDF. Needs no key.
      security: []
      responses:
        "200":
          description: The handbook PDF.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl https://parmana-sandbox.vercel.app/parmana-handbook.pdf -o
            parmana-handbook.pdf
        - lang: typescript
          label: TypeScript
          source: |-
            import { writeFileSync } from "node:fs";

            const response = await fetch(
              "https://parmana-api-real.vercel.app/parmana-handbook.pdf",
            );

            writeFileSync(
              "parmana-handbook.pdf",
              Buffer.from(await response.arrayBuffer()),
            );
        - lang: python
          label: Python
          source: >-
            from pathlib import Path


            import requests


            response = requests.get(
                "https://parmana-api-real.vercel.app/parmana-handbook.pdf", timeout=30
            )


            Path("parmana-handbook.pdf").write_bytes(response.content)
  /handbook/download-leads:
    get:
      tags:
        - Handbook
      summary: Capture an email address, then redirect straight to the handbook PDF
      operationId: getHandbookDownloadLead
      description: >
        Records an email address, then redirects to the handbook PDF. Backs the
        download form on the docs

        site, which submits with a plain GET. The address is stored, not
        verified. Needs no key. For a JSON

        answer use `POST /handbook/download-leads`.
      security: []
      parameters:
        - name: email
          description: The visitor's email address.
          in: query
          required: true
          schema:
            type: string
            format: email
      responses:
        "302":
          description: The address was recorded. The `Location` header points to the PDF.
          headers:
            Location:
              schema:
                type: string
              example: https://parmana-api-real.vercel.app/parmana-handbook.pdf
        "400":
          description: The email address is missing or malformed. The body is plain text,
            for a browser.
          content:
            text/plain:
              schema:
                type: string
              example: A valid email address is required.
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: curl
            "https://parmana-sandbox.vercel.app/handbook/download-leads?email=reader%40example.com"
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/handbook/download-leads?email=reader%40example.com",
            );


            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: >-
            import requests


            response = requests.get(
                "https://parmana-api-real.vercel.app/handbook/download-leads?email=reader%40example.com",
                timeout=30,
            )


            print(response.status_code, response.json())
    post:
      tags:
        - Handbook
      summary: Capture an email address before unlocking the handbook PDF download
      operationId: createHandbookDownloadLead
      description: >
        Records an email address and returns the handbook download link as JSON.
        The address is stored, not

        verified. Needs no key.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
      responses:
        "201":
          description: The address was recorded. `downloadUrl` is the PDF.
          content:
            application/json:
              schema:
                type: object
                required:
                  - downloadUrl
                properties:
                  downloadUrl:
                    type: string
              examples:
                captured:
                  summary: Real captured response
                  value:
                    downloadUrl: https://parmana-api-real.vercel.app/parmana-handbook.pdf
        "400":
          description: The email address is missing or malformed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                malformed:
                  summary: Real captured response
                  value:
                    error: A valid email address is required.
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X POST
            https://parmana-sandbox.vercel.app/handbook/download-leads \
              -H "Content-Type: application/json" \
              -d '{
                "email": "reader@example.com"
              }'
        - lang: typescript
          label: TypeScript
          source: |-
            const response = await fetch(
              "https://parmana-api-real.vercel.app/handbook/download-leads",
              {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ email: "reader@example.com" }),
              },
            );

            console.log(response.status, await response.json());
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.post(
                "https://parmana-api-real.vercel.app/handbook/download-leads",
                json={"email": "reader@example.com"},
                timeout=30,
            )

            print(response.status_code, response.json())
  /refusal/{businessTransactionId}:
    get:
      tags:
        - Refusal Records
      summary: Get a Refusal Record by transaction ID
      operationId: getRefusalRecord
      description: >
        Returns the signed Refusal Record kept when a policy refused a
        transaction: the decision, the rule

        that refused, the facts it saw and the signature. Use it to show a
        person why an action was refused,

        or pass it to `POST /refusal/verify`, which anyone can call.
      parameters:
        - name: businessTransactionId
          description: The id of the refused transaction. In the sandbox, the value shown
            is a seeded transaction kept for these docs. To try your own, run
            the playground and use the `businessTransactionId` it prints.
          in: path
          required: true
          schema:
            type: string
            x-default: 702d0c3d-24fc-4d41-801f-b19b2a5b6118
      responses:
        "200":
          description: The Refusal Record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/refusal-record-response.schema"
              examples:
                refused:
                  summary: Real captured response, a payment refused for insufficient funds
                  value:
                    refusalRecordId: a46475ef-53ae-4cb9-b407-1a64b6af03fe
                    businessTransactionId: 511e9aaf-bffe-4834-a454-6cc2894e7ab4
                    decision:
                      decisionId: 8df80cd5-d88f-416e-9b87-ac346bd144a1
                      intentId: 88d49afb-ac0b-4cb0-8420-cd2100bf1cb0
                      policy:
                        name: vendor-payment
                        version: 2.1.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: false
                        paymentAmount: 1000
                        riskScore: 10
                        vendorId: vendor://payments
                        humanApproved: true
                        approvalArtifact:
                          payload:
                            version: 1
                            approvalId: ab8f7d00-194d-428b-a40f-4f63517de2ff
                            issuer:
                              approverId: test-approver
                              keyId: test-approver-key-1
                            issuedAt: 2026-10-01T10:14:05.085Z
                            expiresAt: 2026-10-01T10:29:05.085Z
                            capability: test:fixture-execute
                            resourceId: vendor://payments
                            scope:
                              field: value
                              comparator: lte
                              value: 1000
                            nonce: a5b7a9f3-a679-4052-9aa4-391e0e6b298c
                          signature:
                            algorithm: ed25519
                            keyId: test-approver-key-1
                            value: fBA9eUxZwTD/cbjVjpYLmmDFQkpOd54nLgLiBGWhGVLHQ/xsWL/pDJUfhrmvd3YRoJSibwn7+LaHAHLPsgJ1BA==
                            signedAt: 2026-10-01T10:14:05.085Z
                      outcome: REJECTED
                      reason: Vendor payment rejected because sufficient funds are not available.
                      matchedRuleId: reject-insufficient-funds
                      evaluatedRules: 6
                      matchedPath:
                        - approve-payment
                        - reject-high-risk-payment
                        - reject-unverified-vendor
                        - reject-unverified-invoice
                        - reject-payment-not-approved
                        - reject-insufficient-funds
                      evaluatedAt: 2026-10-01T10:14:05.097Z
                    evaluatedIntent:
                      target: vendor://payments
                      parameters:
                        paymentId: payment-001
                        amount: 1000
                    submittedBy: operator-maker
                    createdAt: 2026-10-01T10:14:05.098Z
                    refusalRecordHash: e4988c237239299438ed9fb99aa86a9fada04facd0128eb73dcdb21bfa683386
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: LE2h4I+CkhAaivg6n0GZ3FbUN5tAtKR6R2HcvweB4nb3JHhkyO6mFTbNgsoHi+EyfXcoX7L7GAXVeMYNoXP5DQ==
                      signedAt: 2026-10-01T10:14:05.099Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no refused transaction with this id. An approved
            transaction has a Trust Record instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Refusal Record not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/refusal/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const record = await client.refusalRecord(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(record);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            record =
            client.refusal_record("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(record)
  /execution-intents/verify:
    post:
      tags:
        - Execution Intents
      summary: Verify an Execution Intent's signature
      operationId: verifyExecutionIntent
      description: >
        Checks an Execution Intent's hash and signature. Send the intent itself;
        the server reads no storage

        and needs no key. A valid intent proves Parmana signed it before release
        and that it was not changed.

        It does not prove the action ran: an intent is written before release.
        For the outcome, read the

        Trust Record for the same transaction.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/execution-intent-verify-request.schema"
      responses:
        "200":
          description: The check ran. Read `valid`. A `200` alone does not mean the
            signature verified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/verify-result-response.schema"
              examples:
                valid:
                  summary: Real captured response
                  value:
                    valid: true
                altered:
                  summary: The same intent with its target changed after signing (real captured
                    response)
                  value:
                    valid: false
        "400":
          description: The body is not shaped like an Execution Intent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                malformed:
                  summary: Message text from the source
                  value:
                    error: Request body must be an Execution Intent (intentId,
                      businessTransactionId, authorizationId, intentHash,
                      signature required).
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # The intent, as returned in .intent by GET
            /execution-intents/<businessTransactionId>:

            curl -X POST
            https://parmana-sandbox.vercel.app/execution-intents/verify \
              -H "Content-Type: application/json" \
              --data @execution-intent.json
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const view = await client.executionIntent(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );
            const valid = await client.verifyExecutionIntent(view.intent);

            console.log(valid);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            view =
            client.execution_intent("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            valid = client.verify_execution_intent(view.intent)


            print(valid)
  /execution-intents/unfinalized:
    get:
      tags:
        - Execution Intents
      summary: List Execution Intents that never reached a signed Trust Record
      operationId: listUnfinalizedExecutionIntents
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        The operator's work queue: Execution Intents that never reached a signed
        Trust Record and were not

        closed by hand, oldest first. Each one is an action that may have been
        performed without a record.

        For an intent in state `RELEASED`, rebuild the record with `POST
        .../finalize`. For `PREPARED` or

        `ERRORED`, check the target system, then close it with `POST
        .../resolve`. Needs a human key, because

        the list spans every caller. See [Execution
        Intents](/concepts/execution-intents).
      parameters:
        - name: limit
          in: query
          required: false
          description: How many intents to return. Default 50, at most 200.
          schema:
            type: integer
            minimum: 1
            maximum: 200
      responses:
        "200":
          description: The intents, oldest first. Empty when nothing needs attention.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-intents-unfinalized-response.schema"
              examples:
                errored:
                  summary: Real captured response, one intent whose connector call failed
                  value:
                    intents:
                      - intent:
                          intentId: 6fbdabeb-65c5-4264-976d-cc0db7c75dfe
                          businessTransactionId: 40dd71a7-d627-4e8a-8935-2ba35d5060f7
                          decisionId: 05521896-0a5c-4ad8-9963-d27f5d10988f
                          authorizationId: 36057e16-2ea6-4a59-b496-59bb13506e0c
                          policyName: vendor-payment
                          policyVersion: 2.1.0
                          policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                          signalsHash: edaa331fb10564b8562dbf0c29212554b8a5d0e917ca1595c66dee1a67064ac0
                          businessTransactionHash: c85fefd375e9d224d0e5a931465fab5595cb8cee8d5fdd704dd2fe3d60cc485e
                          action: test:fixture-execute
                          target: vendor://payments
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                          createdAt: 2026-10-01T10:14:05.140Z
                          intentHash: 6bf3d216b31f8ca056743ee847f9259acc78b5014669411b6936bffa40ea39ff
                          signature:
                            algorithm: ed25519
                            keyId: default
                            value: Bjt1kU6s297FI88N+bMxHjh9Pb1ZgxA5cqMKpW5KECyZ3RG5ifsNKs3cLCiJ3nqxw/qIHLCAjwSRpAXJ+MgBBg==
                            signedAt: 2026-10-01T10:14:05.140Z
                        status:
                          state: ERRORED
                          failureReason: connector timed out
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                nonHumanCaller:
                  summary: Real captured response
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/execution-intents/unfinalized?limit=50" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const { intents } = await client.unfinalizedExecutionIntents(50);

            console.log(intents.length);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            result = client.unfinalized_execution_intents(50)

            print(len(result.intents))
  /execution-intents/{businessTransactionId}:
    get:
      tags:
        - Execution Intents
      summary: Get an Execution Intent and its status
      operationId: getExecutionIntent
      description: >
        Returns the signed Execution Intent for a transaction and where it got
        to. Read it after a timeout or

        a `5xx` before sending anything new. The `status.state` is one of:


        * `PREPARED`: signed and stored; the action may or may not have been
        released.

        * `RELEASED`: released and its result saved.

        * `FINALIZED`: a signed Trust Record exists.

        * `ERRORED`: the release failed; the action may still have run.

        * `RESOLVED`: a person checked the target system and closed it
        (`resolution`, `resolutionNote`,
          `resolvedBy`, `resolvedAt`; an unsigned statement).
      parameters:
        - name: businessTransactionId
          description: "The transaction's id: the `businessTransactionId` of a request you
            sent. In the sandbox, the value shown is a seeded transaction kept
            for these docs. To try your own, run the playground and use the
            `businessTransactionId` it prints."
          in: path
          required: true
          schema:
            type: string
            x-default: 6f6582a1-4e1a-4800-a73b-cb4c3aa32110
      responses:
        "200":
          description: The intent and its status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-intent-response.schema"
              examples:
                finalized:
                  summary: Real captured response, an intent finalized inline
                  value:
                    intent:
                      intentId: eb477e41-97d5-4f06-b288-a82b285d43c8
                      businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                      decisionId: abc800f7-31f7-4200-a0b8-40ddee4531d9
                      authorizationId: c2adcd88-46ea-48c6-998e-4b1104d78dc4
                      policyName: vendor-payment
                      policyVersion: 2.1.0
                      policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                      signalsHash: 57fba49e69315b5edc10cae142b6f635d3ad37456004e4fd0bba3d56f6516beb
                      businessTransactionHash: 6f5d7263b1769bbb3fe63c590348fe0e5e3e47d24521da6b3473c990b475144a
                      action: test:fixture-execute
                      target: vendor://payments
                      submittedBy: operator-maker
                      grantedCapability: test:fixture-execute
                      createdAt: 2026-10-01T10:14:05.108Z
                      intentHash: c0b44a20cad33538471c5f13e6c8b659e9a91849428d9c27718e57fa7b41ddd9
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: Zkj+SVWnnLH+CTStZaOMsAOmq4Ea8nlI30StVdTqTNKd925QArXbabNf6xIMH+CQU4BYBlX8LlHTvFZXCFrjBw==
                        signedAt: 2026-10-01T10:14:05.109Z
                    status:
                      state: FINALIZED
                      releasedAt: 2026-10-01T10:14:05.115Z
                      trustRecordId: 3b911989-c708-4ad8-8a30-a407bd118982
                      finalizationMode: INLINE
                      finalizedAt: 2026-10-01T10:14:05.117Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no intent with this id. Transactions sent before
            Execution Intents existed have none.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Message text from the source
                  value:
                    error: Execution Intent not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/execution-intents/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const view = await client.executionIntent(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(view.status.state);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            view =
            client.execution_intent("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(view.status.state)
  /execution-intents/{businessTransactionId}/finalize:
    post:
      tags:
        - Execution Intents
      summary: Rebuild the signed Trust Record for a released action
      operationId: finalizeExecutionIntent
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rebuilds the signed Trust Record for an action that was released but
        whose record was never

        produced, the repair for a `500` `EXECUTION_RECORD_INCOMPLETE`. It never
        calls the connector: it uses

        the result saved right after release, then verifies the record and
        generates its receipt. Safe to

        repeat: "if a record exists it returns it as `ALREADY_FINALIZED` and
        builds nothing. Needs a human key."
      parameters:
        - name: businessTransactionId
          description: The transaction's id.
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The Trust Record exists. `outcome` is `FINALIZED` when this call
            built it, `ALREADY_FINALIZED` when it already existed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-intent-finalize-response.schema"
              examples:
                finalized:
                  summary: Real captured response, this call rebuilt the record
                  value:
                    outcome: FINALIZED
                    businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                    trustRecordId: e90c619d-d84d-4ac5-8ccd-1533e19620b7
                    trustRecord:
                      trustRecordId: e90c619d-d84d-4ac5-8ccd-1533e19620b7
                      businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                      transaction:
                        businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                        metadata:
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          correlationId: 76fd653b-0b4d-42a3-a520-1d54aa0cff39
                          createdBy: integration-test
                          createdAt: 2026-10-01T10:14:05.158Z
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                        authority:
                          authorityId: 3ab4a04e-f032-4a2d-bdec-45a216e347e2
                          authorityType: USER
                          principalId: integration-test
                          displayName: Integration Test
                          issuedAt: 2026-10-01T10:14:05.158Z
                        authorization:
                          authorizationId: 6b9aa3b5-ea9c-4ffd-8471-a62d127846a8
                          authorityId: 3ab4a04e-f032-4a2d-bdec-45a216e347e2
                          purpose: Integration Test
                          issuedAt: 2026-10-01T10:14:05.158Z
                        intent:
                          intentId: 0ed0f62f-9cb9-4e89-a88e-e7eb0cd733d8
                          authorizationId: 6b9aa3b5-ea9c-4ffd-8471-a62d127846a8
                          action: test:fixture-execute
                          target: vendor://payments
                          parameters:
                            paymentId: payment-001
                            amount: 1000
                          createdAt: 2026-10-01T10:14:05.158Z
                        policy:
                          name: vendor-payment
                          version: 2.1.0
                          schemaVersion: 1.0.0
                          contentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        signals:
                          vendorVerified: true
                          invoiceVerified: true
                          paymentApproved: true
                          sufficientFunds: true
                          paymentAmount: 1000
                          riskScore: 10
                          vendorId: vendor://payments
                          humanApproved: true
                          approvalArtifact:
                            payload:
                              version: 1
                              approvalId: 5a8f523b-8bbc-4e85-b802-4e2beed9f423
                              issuer:
                                approverId: test-approver
                                keyId: test-approver-key-1
                              issuedAt: 2026-10-01T10:14:05.158Z
                              expiresAt: 2026-10-01T10:29:05.158Z
                              capability: test:fixture-execute
                              resourceId: vendor://payments
                              scope:
                                field: value
                                comparator: lte
                                value: 1000
                              nonce: d95d37c2-5214-4f05-b25b-b6b9cfccc3f1
                            signature:
                              algorithm: ed25519
                              keyId: test-approver-key-1
                              value: rr8nX28mY3mw4NqI39HFkevWyYIyuR+YfIpROngbwCPnaOIvFzP/xtIiV4TfxwjJzid2v0jw2SkU9na2tm+pAw==
                              signedAt: 2026-10-01T10:14:05.158Z
                        status: RECEIVED
                        createdAt: 2026-10-01T10:14:05.161Z
                      authorization:
                        payload:
                          version: 1
                          authorizationId: 0124ee41-6b81-4a34-9dca-4e7e6903a605
                          nonce: 9600d763-0309-4b44-a5c2-d1079e67e112
                          decisionId: 0086ad8e-8dc6-4dfb-895f-3f6bf93756c3
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          policyName: vendor-payment
                          policyVersion: 2.1.0
                          policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                          signalsHash: 22740e756e579df6102d6408aa403cdc42292f8d8ced9b1e23b189ddc655b3f8
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                          authorizedAt: 2026-10-01T10:14:05.162Z
                          expiresAt: 2026-10-01T10:16:05.162Z
                          businessTransactionHash: d1ccf6f901a96576c004a53a197b8e6a32eea95713b9dd00499b07a4b89da4e4
                        signature: TDZgeHSqXhdacOLBUZzEMIFFTP1BkF68hTrvtQrI3SWJo7MApe6JqPTou7xoqzKK7tBwvDrG25mTyRA3TUA8AQ==
                        keyId: default
                        algorithm: ed25519
                      overrides: []
                      executions:
                        - executionId: ad87bb66-5811-404b-bdbf-f8196bfec100
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          decision:
                            decisionId: 0086ad8e-8dc6-4dfb-895f-3f6bf93756c3
                            intentId: 0ed0f62f-9cb9-4e89-a88e-e7eb0cd733d8
                            policy:
                              name: vendor-payment
                              version: 2.1.0
                              schemaVersion: 1.0.0
                            signals:
                              vendorVerified: true
                              invoiceVerified: true
                              paymentApproved: true
                              sufficientFunds: true
                              paymentAmount: 1000
                              riskScore: 10
                              vendorId: vendor://payments
                              humanApproved: true
                              approvalArtifact:
                                payload:
                                  version: 1
                                  approvalId: 5a8f523b-8bbc-4e85-b802-4e2beed9f423
                                  issuer:
                                    approverId: test-approver
                                    keyId: test-approver-key-1
                                  issuedAt: 2026-10-01T10:14:05.158Z
                                  expiresAt: 2026-10-01T10:29:05.158Z
                                  capability: test:fixture-execute
                                  resourceId: vendor://payments
                                  scope:
                                    field: value
                                    comparator: lte
                                    value: 1000
                                  nonce: d95d37c2-5214-4f05-b25b-b6b9cfccc3f1
                                signature:
                                  algorithm: ed25519
                                  keyId: test-approver-key-1
                                  value: rr8nX28mY3mw4NqI39HFkevWyYIyuR+YfIpROngbwCPnaOIvFzP/xtIiV4TfxwjJzid2v0jw2SkU9na2tm+pAw==
                                  signedAt: 2026-10-01T10:14:05.158Z
                            outcome: APPROVED
                            reason: Vendor payment authorized. Vendor verification, invoice verification,
                              payment approval, funding, and risk assessment
                              requirements were satisfied, with a verified human
                              approval.
                            matchedRuleId: approve-payment
                            evaluatedRules: 1
                            matchedPath:
                              - approve-payment
                            evaluatedAt: 2026-10-01T10:14:05.162Z
                          status: COMPLETED
                          mode: SYNC
                          startedAt: 2026-10-01T10:14:05.164Z
                          metadata:
                            authorizationId: 0124ee41-6b81-4a34-9dca-4e7e6903a605
                          previousChainHash: null
                          chainHash: 94a3d02990b2e74a76bbaf742854ef6478855bc219cfe2c0761aa88a5222e40d
                          chainSignature:
                            algorithm: ed25519
                            keyId: default
                            value: 4aKQdzrhPjdIEphO85x0am3wAesHiFfhDg45gQvtkCAmAO7sUMkgHdMMm9qmuhZMFPz4rPqY/2QiMVVT9ZpSAA==
                            signedAt: 2026-10-01T10:14:05.167Z
                          evidence:
                            businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                            action: test:fixture-execute
                            target: vendor://payments
                            parameters:
                              paymentId: payment-001
                              amount: 1000
                            success: true
                            executedAt: 2026-10-01T10:14:05.165Z
                            attributes: {}
                          completedAt: 2026-10-01T10:14:05.166Z
                      verifications:
                        - verificationId: ed256da8-bab4-429c-b161-8da81e4c794a
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          status: VERIFIED
                          message: Execution Trust Record verified successfully.
                          verifiedAt: 2026-10-01T10:14:05.174Z
                          trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                      receipts:
                        - receiptId: 85a5bc98-a5e3-4b58-b90c-ae46ee3226f6
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                          receiptHash: 1896eea5226afcca692bc7163a446b2699b63b26255b1902549e38d1cafd093f
                          issuedAt: 2026-10-01T10:14:05.174Z
                          algorithm: ed25519
                          signature: XxsHhBDY9ZKD3VnjRS7e/ad5mdBoaJFsvETgrdmUEaA4WGT8C4TE+ANj97snSMS/WEmp4d+MjRR3inidJSu9Dg==
                      evidenceAnchor:
                        policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        anchorHash: d9c24439faa28d86e5702a10b6cceace41ea1feac5e90af659c306f5ec3cd46e
                      createdAt: 2026-10-01T10:14:05.171Z
                      updatedAt: 2026-10-01T10:14:05.171Z
                      trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: XK2qjtDkiHNCA12TNIvb5XnoBLqats3CiLdHN7xYxgYul+hCPrp78CPqNXGWDlzsItk8HMYPjQdkCjsgVBcFCw==
                        signedAt: 2026-10-01T10:14:05.172Z
                alreadyFinalized:
                  summary: Real captured response, the same call repeated
                  value:
                    outcome: ALREADY_FINALIZED
                    businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                    trustRecordId: e90c619d-d84d-4ac5-8ccd-1533e19620b7
                    trustRecord:
                      trustRecordId: e90c619d-d84d-4ac5-8ccd-1533e19620b7
                      businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                      transaction:
                        businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                        metadata:
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          correlationId: 76fd653b-0b4d-42a3-a520-1d54aa0cff39
                          createdBy: integration-test
                          createdAt: 2026-10-01T10:14:05.158Z
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                        authority:
                          authorityId: 3ab4a04e-f032-4a2d-bdec-45a216e347e2
                          authorityType: USER
                          principalId: integration-test
                          displayName: Integration Test
                          issuedAt: 2026-10-01T10:14:05.158Z
                        authorization:
                          authorizationId: 6b9aa3b5-ea9c-4ffd-8471-a62d127846a8
                          authorityId: 3ab4a04e-f032-4a2d-bdec-45a216e347e2
                          purpose: Integration Test
                          issuedAt: 2026-10-01T10:14:05.158Z
                        intent:
                          intentId: 0ed0f62f-9cb9-4e89-a88e-e7eb0cd733d8
                          authorizationId: 6b9aa3b5-ea9c-4ffd-8471-a62d127846a8
                          action: test:fixture-execute
                          target: vendor://payments
                          parameters:
                            paymentId: payment-001
                            amount: 1000
                          createdAt: 2026-10-01T10:14:05.158Z
                        policy:
                          name: vendor-payment
                          version: 2.1.0
                          schemaVersion: 1.0.0
                          contentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        signals:
                          vendorVerified: true
                          invoiceVerified: true
                          paymentApproved: true
                          sufficientFunds: true
                          paymentAmount: 1000
                          riskScore: 10
                          vendorId: vendor://payments
                          humanApproved: true
                          approvalArtifact:
                            payload:
                              version: 1
                              approvalId: 5a8f523b-8bbc-4e85-b802-4e2beed9f423
                              issuer:
                                approverId: test-approver
                                keyId: test-approver-key-1
                              issuedAt: 2026-10-01T10:14:05.158Z
                              expiresAt: 2026-10-01T10:29:05.158Z
                              capability: test:fixture-execute
                              resourceId: vendor://payments
                              scope:
                                field: value
                                comparator: lte
                                value: 1000
                              nonce: d95d37c2-5214-4f05-b25b-b6b9cfccc3f1
                            signature:
                              algorithm: ed25519
                              keyId: test-approver-key-1
                              value: rr8nX28mY3mw4NqI39HFkevWyYIyuR+YfIpROngbwCPnaOIvFzP/xtIiV4TfxwjJzid2v0jw2SkU9na2tm+pAw==
                              signedAt: 2026-10-01T10:14:05.158Z
                        status: RECEIVED
                        createdAt: 2026-10-01T10:14:05.161Z
                      authorization:
                        payload:
                          version: 1
                          authorizationId: 0124ee41-6b81-4a34-9dca-4e7e6903a605
                          nonce: 9600d763-0309-4b44-a5c2-d1079e67e112
                          decisionId: 0086ad8e-8dc6-4dfb-895f-3f6bf93756c3
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          policyName: vendor-payment
                          policyVersion: 2.1.0
                          policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                          signalsHash: 22740e756e579df6102d6408aa403cdc42292f8d8ced9b1e23b189ddc655b3f8
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                          authorizedAt: 2026-10-01T10:14:05.162Z
                          expiresAt: 2026-10-01T10:16:05.162Z
                          businessTransactionHash: d1ccf6f901a96576c004a53a197b8e6a32eea95713b9dd00499b07a4b89da4e4
                        signature: TDZgeHSqXhdacOLBUZzEMIFFTP1BkF68hTrvtQrI3SWJo7MApe6JqPTou7xoqzKK7tBwvDrG25mTyRA3TUA8AQ==
                        keyId: default
                        algorithm: ed25519
                      overrides: []
                      executions:
                        - executionId: ad87bb66-5811-404b-bdbf-f8196bfec100
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          decision:
                            decisionId: 0086ad8e-8dc6-4dfb-895f-3f6bf93756c3
                            intentId: 0ed0f62f-9cb9-4e89-a88e-e7eb0cd733d8
                            policy:
                              name: vendor-payment
                              version: 2.1.0
                              schemaVersion: 1.0.0
                            signals:
                              vendorVerified: true
                              invoiceVerified: true
                              paymentApproved: true
                              sufficientFunds: true
                              paymentAmount: 1000
                              riskScore: 10
                              vendorId: vendor://payments
                              humanApproved: true
                              approvalArtifact:
                                payload:
                                  version: 1
                                  approvalId: 5a8f523b-8bbc-4e85-b802-4e2beed9f423
                                  issuer:
                                    approverId: test-approver
                                    keyId: test-approver-key-1
                                  issuedAt: 2026-10-01T10:14:05.158Z
                                  expiresAt: 2026-10-01T10:29:05.158Z
                                  capability: test:fixture-execute
                                  resourceId: vendor://payments
                                  scope:
                                    field: value
                                    comparator: lte
                                    value: 1000
                                  nonce: d95d37c2-5214-4f05-b25b-b6b9cfccc3f1
                                signature:
                                  algorithm: ed25519
                                  keyId: test-approver-key-1
                                  value: rr8nX28mY3mw4NqI39HFkevWyYIyuR+YfIpROngbwCPnaOIvFzP/xtIiV4TfxwjJzid2v0jw2SkU9na2tm+pAw==
                                  signedAt: 2026-10-01T10:14:05.158Z
                            outcome: APPROVED
                            reason: Vendor payment authorized. Vendor verification, invoice verification,
                              payment approval, funding, and risk assessment
                              requirements were satisfied, with a verified human
                              approval.
                            matchedRuleId: approve-payment
                            evaluatedRules: 1
                            matchedPath:
                              - approve-payment
                            evaluatedAt: 2026-10-01T10:14:05.162Z
                          status: COMPLETED
                          mode: SYNC
                          startedAt: 2026-10-01T10:14:05.164Z
                          metadata:
                            authorizationId: 0124ee41-6b81-4a34-9dca-4e7e6903a605
                          previousChainHash: null
                          chainHash: 94a3d02990b2e74a76bbaf742854ef6478855bc219cfe2c0761aa88a5222e40d
                          chainSignature:
                            algorithm: ed25519
                            keyId: default
                            value: 4aKQdzrhPjdIEphO85x0am3wAesHiFfhDg45gQvtkCAmAO7sUMkgHdMMm9qmuhZMFPz4rPqY/2QiMVVT9ZpSAA==
                            signedAt: 2026-10-01T10:14:05.167Z
                          evidence:
                            businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                            action: test:fixture-execute
                            target: vendor://payments
                            parameters:
                              paymentId: payment-001
                              amount: 1000
                            success: true
                            executedAt: 2026-10-01T10:14:05.165Z
                            attributes: {}
                          completedAt: 2026-10-01T10:14:05.166Z
                      verifications:
                        - verificationId: ed256da8-bab4-429c-b161-8da81e4c794a
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          status: VERIFIED
                          message: Execution Trust Record verified successfully.
                          verifiedAt: 2026-10-01T10:14:05.174Z
                          trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                      receipts:
                        - receiptId: 85a5bc98-a5e3-4b58-b90c-ae46ee3226f6
                          businessTransactionId: 0815b845-20b0-4c45-9582-31fce04eaf58
                          trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                          receiptHash: 1896eea5226afcca692bc7163a446b2699b63b26255b1902549e38d1cafd093f
                          issuedAt: 2026-10-01T10:14:05.174Z
                          algorithm: ed25519
                          signature: XxsHhBDY9ZKD3VnjRS7e/ad5mdBoaJFsvETgrdmUEaA4WGT8C4TE+ANj97snSMS/WEmp4d+MjRR3inidJSu9Dg==
                      evidenceAnchor:
                        policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        anchorHash: d9c24439faa28d86e5702a10b6cceace41ea1feac5e90af659c306f5ec3cd46e
                      createdAt: 2026-10-01T10:14:05.171Z
                      updatedAt: 2026-10-01T10:14:05.171Z
                      trustRecordHash: 59d9a80090980ef5adf536a08897b66072dd86f98550ff45310502fe22b38612
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: XK2qjtDkiHNCA12TNIvb5XnoBLqats3CiLdHN7xYxgYul+hCPrp78CPqNXGWDlzsItk8HMYPjQdkCjsgVBcFCw==
                        signedAt: 2026-10-01T10:14:05.172Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                nonHumanCaller:
                  summary: Real captured response
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "404":
          description: No intent exists for this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Message text from the source
                  value:
                    error: No Execution Intent exists for business transaction
                      'unknown-transaction'. Transactions created before
                      Execution Intents were introduced have none.
                    code: EXECUTION_INTENT_NOT_FOUND
        "409":
          description: No execution result was saved, so there is nothing to rebuild from.
            Check the target system, then close the intent with `POST
            .../resolve`. Nothing was called or changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                resultNotRecorded:
                  summary: Message text from the source
                  value:
                    error: The Execution Intent for business transaction
                      'b1237ce3-305a-4004-8179-749352fc1ade' is in state
                      PREPARED and has no saved execution result, so its signed
                      Execution Trust Record cannot be rebuilt. The action may
                      or may not have been released. Establish the outcome from
                      the connector and reconcile it by hand. Nothing was
                      called.
                    code: EXECUTION_INTENT_RESULT_NOT_RECORDED
        "501":
          description: Execution Intents are not enabled on this deployment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notEnabled:
                  summary: Message text from the source
                  value:
                    error: Execution Intents are not enabled on this deployment.
                    code: EXECUTION_INTENTS_NOT_ENABLED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # TXN_ID: the businessTransactionId of the transaction. In the sandbox, use one from a playground run;
            # sandbox data is deleted hourly, so an older id returns 404.
            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"
            curl -X POST https://parmana-api-real.vercel.app/execution-intents/$TXN_ID/finalize \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const result = await client.finalizeExecutionIntent(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(result.outcome);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            result = client.finalize_execution_intent("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            print(result.outcome)
  /execution-intents/{businessTransactionId}/resolve:
    post:
      tags:
        - Execution Intents
      summary: Close an intent you reconciled by hand
      operationId: resolveExecutionIntent
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Closes an intent in state `PREPARED` or `ERRORED` after a person checked
        the target system, the

        repair for a `502` `EXECUTION_OUTCOME_UNKNOWN`. Record what you found,
        `NOT_EXECUTED` or `EXECUTED`,

        with a note. The intent becomes `RESOLVED` and leaves the unfinalized
        list. It never calls the

        connector. Safe to repeat: a second call returns `ALREADY_RESOLVED` with
        the first answer.

        **The resolution is an attributed, timestamped statement in the intent's
        unsigned status. It is not

        tamper evident and it is not a Trust Record.** Needs a human key.
      parameters:
        - name: businessTransactionId
          description: The transaction's id.
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/execution-intent-resolve-request.schema"
      responses:
        "200":
          description: The intent is closed. `outcome` is `RESOLVED` when this call closed
            it, `ALREADY_RESOLVED` when it was already closed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-intent-resolve-response.schema"
              examples:
                resolved:
                  summary: Real captured response, this call closed the intent
                  value:
                    outcome: RESOLVED
                    businessTransactionId: 40dd71a7-d627-4e8a-8935-2ba35d5060f7
                    intent:
                      intentId: 6fbdabeb-65c5-4264-976d-cc0db7c75dfe
                      businessTransactionId: 40dd71a7-d627-4e8a-8935-2ba35d5060f7
                      decisionId: 05521896-0a5c-4ad8-9963-d27f5d10988f
                      authorizationId: 36057e16-2ea6-4a59-b496-59bb13506e0c
                      policyName: vendor-payment
                      policyVersion: 2.1.0
                      policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                      signalsHash: edaa331fb10564b8562dbf0c29212554b8a5d0e917ca1595c66dee1a67064ac0
                      businessTransactionHash: c85fefd375e9d224d0e5a931465fab5595cb8cee8d5fdd704dd2fe3d60cc485e
                      action: test:fixture-execute
                      target: vendor://payments
                      submittedBy: operator-maker
                      grantedCapability: test:fixture-execute
                      createdAt: 2026-10-01T10:14:05.140Z
                      intentHash: 6bf3d216b31f8ca056743ee847f9259acc78b5014669411b6936bffa40ea39ff
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: Bjt1kU6s297FI88N+bMxHjh9Pb1ZgxA5cqMKpW5KECyZ3RG5ifsNKs3cLCiJ3nqxw/qIHLCAjwSRpAXJ+MgBBg==
                        signedAt: 2026-10-01T10:14:05.140Z
                    status:
                      state: RESOLVED
                      failureReason: connector timed out
                      resolution: NOT_EXECUTED
                      resolutionNote: Checked the connector. Nothing was created for this order.
                      resolvedBy: operator-maker
                      resolvedAt: 2026-10-01T10:14:05.149Z
                alreadyResolved:
                  summary: Real captured response, the same call repeated
                  value:
                    outcome: ALREADY_RESOLVED
                    businessTransactionId: 40dd71a7-d627-4e8a-8935-2ba35d5060f7
                    intent:
                      intentId: 6fbdabeb-65c5-4264-976d-cc0db7c75dfe
                      businessTransactionId: 40dd71a7-d627-4e8a-8935-2ba35d5060f7
                      decisionId: 05521896-0a5c-4ad8-9963-d27f5d10988f
                      authorizationId: 36057e16-2ea6-4a59-b496-59bb13506e0c
                      policyName: vendor-payment
                      policyVersion: 2.1.0
                      policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                      signalsHash: edaa331fb10564b8562dbf0c29212554b8a5d0e917ca1595c66dee1a67064ac0
                      businessTransactionHash: c85fefd375e9d224d0e5a931465fab5595cb8cee8d5fdd704dd2fe3d60cc485e
                      action: test:fixture-execute
                      target: vendor://payments
                      submittedBy: operator-maker
                      grantedCapability: test:fixture-execute
                      createdAt: 2026-10-01T10:14:05.140Z
                      intentHash: 6bf3d216b31f8ca056743ee847f9259acc78b5014669411b6936bffa40ea39ff
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: Bjt1kU6s297FI88N+bMxHjh9Pb1ZgxA5cqMKpW5KECyZ3RG5ifsNKs3cLCiJ3nqxw/qIHLCAjwSRpAXJ+MgBBg==
                        signedAt: 2026-10-01T10:14:05.140Z
                    status:
                      state: RESOLVED
                      failureReason: connector timed out
                      resolution: NOT_EXECUTED
                      resolutionNote: Checked the connector. Nothing was created for this order.
                      resolvedBy: operator-maker
                      resolvedAt: 2026-10-01T10:14:05.149Z
        "400":
          description: "`resolution` must be `NOT_EXECUTED` or `EXECUTED`, and `note` is
            required, at most 2000 characters. Nothing was changed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                noteRequired:
                  summary: Message text from the source
                  value:
                    error: "The resolution is invalid: note is required. Record what you found at
                      the connector."
                    code: EXECUTION_INTENT_RESOLUTION_INVALID
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                nonHumanCaller:
                  summary: Real captured response
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "404":
          description: No intent exists for this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Message text from the source
                  value:
                    error: No Execution Intent exists for business transaction
                      'unknown-transaction'. Transactions created before
                      Execution Intents were introduced have none.
                    code: EXECUTION_INTENT_NOT_FOUND
        "409":
          description: "This intent cannot be closed by hand. `RELEASED`: its result is
            saved, use `POST .../finalize`. `FINALIZED`, or a signed Trust
            Record exists: it is already complete. Nothing was changed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                released:
                  summary: Message text from the source
                  value:
                    error: The Execution Intent for business transaction
                      'b1237ce3-305a-4004-8179-749352fc1ade' is in state
                      RELEASED and cannot be resolved by hand. Its execution
                      result was saved, so rebuild the signed Trust Record with
                      finalize instead. Nothing was changed.
                    code: EXECUTION_INTENT_NOT_RESOLVABLE
        "501":
          description: Execution Intents are not enabled on this deployment.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notEnabled:
                  summary: Message text from the source
                  value:
                    error: Execution Intents are not enabled on this deployment.
                    code: EXECUTION_INTENTS_NOT_ENABLED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # TXN_ID: the businessTransactionId of the transaction. In the sandbox, use one from a playground run;
            # sandbox data is deleted hourly, so an older id returns 404.
            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"
            curl -X POST https://parmana-api-real.vercel.app/execution-intents/$TXN_ID/resolve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "$(cat <<JSON
            {
                "resolution": "NOT_EXECUTED",
                "note": "Checked the Paytm dashboard for order ORD-1042. No refund exists."
            }
            JSON
            )"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const result = await client.resolveExecutionIntent(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
              {
                resolution: "NOT_EXECUTED",
                note: "Checked the Paytm dashboard for order ORD-1042. No refund exists.",
              },
            );


            console.log(result.outcome);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            result = client.resolve_execution_intent(
                "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
                resolution="NOT_EXECUTED",
                note="Checked the Paytm dashboard for order ORD-1042. No refund exists.",
            )


            print(result.outcome)
  /audit/verify:
    post:
      tags:
        - Audit
      summary: Verify a signed caller-authentication audit event
      operationId: verifyAuditEvent
      description: >
        Checks the signature of a caller authentication audit event, the record
        the server keeps of every

        authentication, denial and governance action. Send the event and its
        signature; the server reads no

        storage and needs no key. Only production audit trails sign events. See

        [Caller audit trail](/concepts/caller-audit-trail).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/audit-verify-request.schema"
      responses:
        "200":
          description: The check ran. Read `valid`. A `200` alone does not mean the
            signature verified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/verify-result-response.schema"
              examples:
                invalid:
                  summary: Real captured response (this environment's in-memory audit sink never
                    signs, so no genuinely valid signature is capturable here)
                  value:
                    valid: false
        "400":
          description: The body is not an `{ event, signature }` pair.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                malformed:
                  summary: Real captured response
                  value:
                    error: "Request body must be { event, signature } (event: type, occurredAt,
                      route; signature: algorithm, keyId, value required)."
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # A signed audit event and its signature, as exported from
            caller_audit_events:

            curl -X POST https://parmana-sandbox.vercel.app/audit/verify \
              -H "Content-Type: application/json" \
              --data @audit-event.json
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            // A signed audit event and its signature, as exported from
            caller_audit_events.

            const { event, signature } = JSON.parse(
              readFileSync("audit-event.json", "utf8"),
            );


            const valid = await client.verifyAuditEvent(event, signature);


            console.log(valid);
        - lang: python
          label: Python
          source: >-
            import json

            import os

            from datetime import datetime

            from pathlib import Path


            from parmana import ParmanaClient

            from parmana.models import Signature, SignatureAlgorithm


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            # A signed audit event and its signature, as exported from
            caller_audit_events.

            exported = json.loads(Path("audit-event.json").read_text())


            signature = Signature(
                algorithm=SignatureAlgorithm(exported["signature"]["algorithm"]),
                key_id=exported["signature"]["keyId"],
                value=exported["signature"]["value"],
                signed_at=datetime.fromisoformat(exported["signature"]["signedAt"]),
            )


            valid = client.verify_audit_event(exported["event"], signature)


            print(valid)
  /receipt:
    post:
      tags:
        - Receipts
      summary: Generate a Receipt
      operationId: generateReceipt
      description: >
        Generates a signed Receipt for a Trust Record: a compact proof of what
        was decided and executed,

        to hand to a person or another system. The record must have a `VERIFIED`
        Verification first; run

        `POST /verify`.


        In the sandbox, the ID must come from a playground run that was approved
        and released: each run's responses

        include its `businessTransactionId`. Sandbox data is deleted hourly, so
        an older ID returns `404`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/receipt-request.schema"
      responses:
        "200":
          description: The signed Receipt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/receipt-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    receiptId: ec0e71e6-e5af-4e14-8f65-13949fca9c6d
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    receiptHash: d443df31a6ad94e8d9758d5143afd0859984b935f3209c35a7d93aaf8cd9f0d0
                    issuedAt: 2026-07-13T17:42:11.094Z
                    algorithm: ed25519
                    signature: uchgXRFJnByGu3F+NZSoq1Hwt6bBr3rRJjQGpLOULKJ7jR4NfE/P4g7loD4L5aADdYxUqLh6Yn7ngfnz2peCCA==
        "400":
          description: "`businessTransactionId` is missing or not a UUID."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badUuid:
                  summary: Real captured response
                  value:
                    error: businessTransactionId must be a valid UUID.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
                    code: VERIFICATION_FAILED
        "409":
          description: The record's latest Verification is not `VERIFIED`. Run `POST
            /verify`, then try again.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notYetVerified:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record must be successfully verified before a Receipt can
                      be generated.
                    code: RECEIPT_GENERATION_FAILED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl -X POST https://parmana-sandbox.vercel.app/receipt \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "$(cat <<JSON
            {
                "businessTransactionId": "$TXN_ID"
            }

            JSON

            )"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const receipt = await
            client.receipt("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90");


            console.log(receipt);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            receipt =
            client.receipt.generate("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(receipt)
  /receipt/latest/{businessTransactionId}:
    get:
      tags:
        - Receipts
      summary: Get latest Receipt
      operationId: getLatestReceipt
      description: >
        Returns the most recent Receipt for a Trust Record. `POST /execute`
        generates one for every executed

        action.
      parameters:
        - name: businessTransactionId
          description: The transaction's id. In the sandbox, the value shown is a seeded
            transaction kept for these docs. To try your own, run the playground
            and use the `businessTransactionId` it prints.
          in: path
          required: true
          schema:
            type: string
            x-default: 6f6582a1-4e1a-4800-a73b-cb4c3aa32110
      responses:
        "200":
          description: The latest Receipt.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/receipt-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    receiptId: ec0e71e6-e5af-4e14-8f65-13949fca9c6d
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    receiptHash: d443df31a6ad94e8d9758d5143afd0859984b935f3209c35a7d93aaf8cd9f0d0
                    issuedAt: 2026-07-13T17:42:11.094Z
                    algorithm: ed25519
                    signature: uchgXRFJnByGu3F+NZSoq1Hwt6bBr3rRJjQGpLOULKJ7jR4NfE/P4g7loD4L5aADdYxUqLh6Yn7ngfnz2peCCA==
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id, or it has no Receipt yet.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                trustRecordNotFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/receipt/latest/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const receipt = await client.latestReceipt(
              "5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90",
            );

            console.log(receipt);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            receipt =
            client.latest_receipt("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(receipt)
  /trust-records:
    get:
      tags:
        - Trust Records
      summary: Bulk-export Execution Trust Records (compliance/audit)
      operationId: listTrustRecords
      description: >
        Exports your full, signed Trust Records (transaction, decision,
        executions, verifications, receipts)

        a page at a time, for audit and compliance. Filter by date with `since`
        and `until`. Paging works as

        on `GET /transactions`: a bare array, scoped to your key, and a page can
        hold fewer than `pageSize`

        items.
      parameters:
        - name: page
          description: Page number, starting at 1. Default 1.
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: pageSize
          description: Items per page before filtering. Default 25. Maximum 100.
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: since
          in: query
          required: false
          description: Only include records whose transaction.createdAt is on or after
            this ISO 8601 timestamp.
          schema:
            type: string
            format: date-time
        - name: until
          in: query
          required: false
          description: Only include records whose transaction.createdAt is on or before
            this ISO 8601 timestamp.
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Your Trust Records on this page. May be empty.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/trust-records-list-response.schema"
              examples:
                onePage:
                  summary: Real captured response, limit=1
                  value:
                    - trustRecordId: 3b911989-c708-4ad8-8a30-a407bd118982
                      businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                      transaction:
                        businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                        metadata:
                          businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                          correlationId: ee634901-6073-42dc-8faa-0e45f282af64
                          createdBy: integration-test
                          createdAt: 2026-10-01T10:14:05.103Z
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                        authority:
                          authorityId: 1ae40760-1bd1-42b7-adbe-4489e7cf58c8
                          authorityType: USER
                          principalId: integration-test
                          displayName: Integration Test
                          issuedAt: 2026-10-01T10:14:05.103Z
                        authorization:
                          authorizationId: 7a761cc1-77b3-45eb-8485-3dcb41c640ff
                          authorityId: 1ae40760-1bd1-42b7-adbe-4489e7cf58c8
                          purpose: Integration Test
                          issuedAt: 2026-10-01T10:14:05.103Z
                        intent:
                          intentId: 65026d03-6b60-4c06-bf72-c4035ddcf58e
                          authorizationId: 7a761cc1-77b3-45eb-8485-3dcb41c640ff
                          action: test:fixture-execute
                          target: vendor://payments
                          parameters:
                            paymentId: payment-001
                            amount: 1000
                          createdAt: 2026-10-01T10:14:05.103Z
                        policy:
                          name: vendor-payment
                          version: 2.1.0
                          schemaVersion: 1.0.0
                          contentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        signals:
                          vendorVerified: true
                          invoiceVerified: true
                          paymentApproved: true
                          sufficientFunds: true
                          paymentAmount: 1000
                          riskScore: 10
                          vendorId: vendor://payments
                          humanApproved: true
                          approvalArtifact:
                            payload:
                              version: 1
                              approvalId: e0b1d343-220f-46fc-915b-ed356b3cd57b
                              issuer:
                                approverId: test-approver
                                keyId: test-approver-key-1
                              issuedAt: 2026-10-01T10:14:05.103Z
                              expiresAt: 2026-10-01T10:29:05.103Z
                              capability: test:fixture-execute
                              resourceId: vendor://payments
                              scope:
                                field: value
                                comparator: lte
                                value: 1000
                              nonce: 162faeee-644b-4a94-8abe-a5515149296f
                            signature:
                              algorithm: ed25519
                              keyId: test-approver-key-1
                              value: l/WpaJqRgoqbh/BuZ1VOfKar+wiYd6C3DUU9ypU8GNx8QCZjRCu79D//Ghq75HWSftiAGfcKN6WzrOwSzfP/CQ==
                              signedAt: 2026-10-01T10:14:05.103Z
                        status: RECEIVED
                        createdAt: 2026-10-01T10:14:05.105Z
                      authorization:
                        payload:
                          version: 1
                          authorizationId: c2adcd88-46ea-48c6-998e-4b1104d78dc4
                          nonce: 09408113-67d5-4447-8cd7-9c0203767b8d
                          decisionId: abc800f7-31f7-4200-a0b8-40ddee4531d9
                          businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                          policyName: vendor-payment
                          policyVersion: 2.1.0
                          policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                          signalsHash: 57fba49e69315b5edc10cae142b6f635d3ad37456004e4fd0bba3d56f6516beb
                          submittedBy: operator-maker
                          grantedCapability: test:fixture-execute
                          authorizedAt: 2026-10-01T10:14:05.107Z
                          expiresAt: 2026-10-01T10:16:05.107Z
                          businessTransactionHash: 6f5d7263b1769bbb3fe63c590348fe0e5e3e47d24521da6b3473c990b475144a
                        signature: 2mCcxfvZ1LIGiFnfHH8ug+Azw7W8AiXMyWmx98z2bDVK9cUZyutsOQD/pLxJ4YBwcjl003acuqkU1iB2OX3sAA==
                        keyId: default
                        algorithm: ed25519
                      overrides: []
                      executions:
                        - executionId: d405986b-899c-40d6-bba4-71f885e5a6a9
                          businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                          decision:
                            decisionId: abc800f7-31f7-4200-a0b8-40ddee4531d9
                            intentId: 65026d03-6b60-4c06-bf72-c4035ddcf58e
                            policy:
                              name: vendor-payment
                              version: 2.1.0
                              schemaVersion: 1.0.0
                            signals:
                              vendorVerified: true
                              invoiceVerified: true
                              paymentApproved: true
                              sufficientFunds: true
                              paymentAmount: 1000
                              riskScore: 10
                              vendorId: vendor://payments
                              humanApproved: true
                              approvalArtifact:
                                payload:
                                  version: 1
                                  approvalId: e0b1d343-220f-46fc-915b-ed356b3cd57b
                                  issuer:
                                    approverId: test-approver
                                    keyId: test-approver-key-1
                                  issuedAt: 2026-10-01T10:14:05.103Z
                                  expiresAt: 2026-10-01T10:29:05.103Z
                                  capability: test:fixture-execute
                                  resourceId: vendor://payments
                                  scope:
                                    field: value
                                    comparator: lte
                                    value: 1000
                                  nonce: 162faeee-644b-4a94-8abe-a5515149296f
                                signature:
                                  algorithm: ed25519
                                  keyId: test-approver-key-1
                                  value: l/WpaJqRgoqbh/BuZ1VOfKar+wiYd6C3DUU9ypU8GNx8QCZjRCu79D//Ghq75HWSftiAGfcKN6WzrOwSzfP/CQ==
                                  signedAt: 2026-10-01T10:14:05.103Z
                            outcome: APPROVED
                            reason: Vendor payment authorized. Vendor verification, invoice verification,
                              payment approval, funding, and risk assessment
                              requirements were satisfied, with a verified human
                              approval.
                            matchedRuleId: approve-payment
                            evaluatedRules: 1
                            matchedPath:
                              - approve-payment
                            evaluatedAt: 2026-10-01T10:14:05.107Z
                          status: COMPLETED
                          mode: SYNC
                          startedAt: 2026-10-01T10:14:05.109Z
                          metadata:
                            authorizationId: c2adcd88-46ea-48c6-998e-4b1104d78dc4
                          previousChainHash: null
                          chainHash: 28596091c4c7e7f1038d05fbaac939c8a2efa6eda2ca4673fc2fd2a40ec8bf49
                          chainSignature:
                            algorithm: ed25519
                            keyId: default
                            value: L4JVGQx1XqG5G/4cgbtWpkWlGUvIdrLpPHKEuq8AG1kH6ospPHdaPT1ZMRgqdWh8VOv/+UY2cAGL82DSbjoDBg==
                            signedAt: 2026-10-01T10:14:05.115Z
                          evidence:
                            businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                            action: test:fixture-execute
                            target: vendor://payments
                            parameters:
                              paymentId: payment-001
                              amount: 1000
                            success: true
                            executedAt: 2026-10-01T10:14:05.113Z
                            attributes: {}
                          completedAt: 2026-10-01T10:14:05.114Z
                      verifications:
                        - verificationId: e01892bd-40f8-4b15-b3fd-83f78d45593f
                          businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                          status: VERIFIED
                          message: Execution Trust Record verified successfully.
                          verifiedAt: 2026-10-01T10:14:05.119Z
                          trustRecordHash: 4a6e9f2f131bd648bd4fa5d026d34a54988d0fb664ab368f378d1897b9683a30
                      receipts:
                        - receiptId: 4dc3a959-5cde-4204-a7b2-11f3a55e29b2
                          businessTransactionId: b09eb4f2-30f8-4783-bd23-4092eabc8378
                          trustRecordHash: 4a6e9f2f131bd648bd4fa5d026d34a54988d0fb664ab368f378d1897b9683a30
                          receiptHash: 37422e2d6254c26bb41db226e6595baeefa03bff5c62a8f726a95c195d1b4ff4
                          issuedAt: 2026-10-01T10:14:05.120Z
                          algorithm: ed25519
                          signature: j3Vh5Drv63BTm+N0litbKYY/NZwgjFW4DWp3B7njPlHQrp+ZifuHO8ZiGcrinTt6gqHu7nhWMYY/UoZBU4LuAA==
                      evidenceAnchor:
                        policyContentHash: 18f4acca151fef1c45c867f622bdd4edd5d640aa38b95e026430aba10756ee2d
                        anchorHash: d9c24439faa28d86e5702a10b6cceace41ea1feac5e90af659c306f5ec3cd46e
                      createdAt: 2026-10-01T10:14:05.115Z
                      updatedAt: 2026-10-01T10:14:05.115Z
                      trustRecordHash: 4a6e9f2f131bd648bd4fa5d026d34a54988d0fb664ab368f378d1897b9683a30
                      signature:
                        algorithm: ed25519
                        keyId: default
                        value: QOHBaXP4Qplx66sH2q1wkhfM+u1Geswh+DdPUPc6URt179sibLMts035o5D42HIudgz20NwL9UYiGBmVNjxCBg==
                        signedAt: 2026-10-01T10:14:05.117Z
        "400":
          description: page or pageSize is not a whole number in range. Nothing was read.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                pageSizeTooLarge:
                  summary: Captured from the test suite, pageSize=1000000
                  value:
                    error: pageSize must be a whole number from 1 to 100.
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-sandbox.vercel.app/trust-records?page=1&pageSize=25&since=2026-10-01T00%3A00%3A00Z" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const records = await client.trustRecords(1, 25, {
              since: "2026-10-01T00:00:00Z",
            });

            console.log(records.length);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            records = client.trust_records.list(page=1, page_size=25,
            since="2026-10-01T00:00:00Z")


            print(len(records))
  /trust-records/{businessTransactionId}:
    get:
      tags:
        - Trust Records
      summary: Get Execution Trust Record
      operationId: getTrustRecord
      description: >
        Returns the complete signed Execution Trust Record for a transaction:
        what was asked, what the

        policy decided, what was executed and the connector's answer, with
        signatures. Look it up by the

        `businessTransactionId` you chose, not by `trustRecordId`.
      parameters:
        - name: businessTransactionId
          in: path
          required: true
          description: The transaction's id, not the record's `trustRecordId`. In the
            sandbox, the value shown is a seeded transaction kept for these
            docs. To try your own, run the playground and use the
            `businessTransactionId` it prints.
          schema:
            type: string
            x-default: 6f6582a1-4e1a-4800-a73b-cb4c3aa32110
      responses:
        "200":
          description: The Trust Record.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/execution-trust-record-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    trustRecordId: 65fee934-532c-4feb-88d9-72c0cd912a81
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    transaction:
                      businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                      metadata:
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        correlationId: cc1ca975-4935-4e9a-a591-8897af6b56fb
                        sourceSystem: vendor-payment-service
                        submittedBy: ap-automation
                        submittedAt: 2026-07-13T17:36:00.835Z
                      authority:
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        authorityType: SERVICE
                        principalId: ap-automation-svc
                        displayName: Accounts Payable Automation
                        issuedAt: 2026-07-13T17:36:00.835Z
                      authorization:
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        authorityId: db5c5260-bb4c-40a2-a081-2928b5fe7720
                        purpose: Authorize vendor payment disbursement
                        issuedAt: 2026-07-13T17:36:00.835Z
                      intent:
                        intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                        authorizationId: 63bdc429-14e4-4ef4-8007-f3c0c9564261
                        action: payments:execute
                        target: vendor/V-500
                        parameters:
                          amount: 5000
                          currency: USD
                        createdAt: 2026-07-13T17:36:00.835Z
                      policy:
                        name: vendor-payment
                        version: 2.0.0
                        schemaVersion: 1.0.0
                      signals:
                        vendorVerified: true
                        invoiceVerified: true
                        paymentApproved: true
                        sufficientFunds: true
                        paymentAmount: 5000
                        riskScore: 12
                      status: RECEIVED
                      createdAt: 2026-07-13T17:36:00.965Z
                    overrides: []
                    executions:
                      - executionId: 90746492-7721-4228-b244-67bf8a45ca68
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        decision:
                          decisionId: 8ec880cb-677a-4889-96f1-5a18a4edc1b5
                          intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
                          policy:
                            name: vendor-payment
                            version: 2.0.0
                            schemaVersion: 1.0.0
                          signals:
                            vendorVerified: true
                            invoiceVerified: true
                            paymentApproved: true
                            sufficientFunds: true
                            paymentAmount: 5000
                            riskScore: 12
                          outcome: APPROVED
                          reason: Vendor payment authorized. Vendor verification, invoice verification,
                            payment approval, funding, and risk assessment
                            requirements were satisfied.
                          evaluatedAt: 2026-07-13T17:36:00.967Z
                        status: COMPLETED
                        mode: SYNC
                        startedAt: 2026-07-13T17:36:00.968Z
                        metadata:
                          authorizationId: dbc09266-2078-4d4e-93d0-f355eb7eebcc
                        evidence:
                          businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                          action: payments:execute
                          target: vendor/V-500
                          parameters:
                            amount: 5000
                            currency: USD
                          success: true
                          executedAt: 2026-07-13T17:36:00.970Z
                          attributes:
                            connector:
                              connectorId: vendor-payment
                              connectorVersion: 1.0.0
                              capability: payments:execute
                              sanitizedEndpoint: vendor/V-500
                              credentialProviderId: environment
                              requestSummary:
                                businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                                action: payments:execute
                                target: vendor/V-500
                                parameters:
                                  amount: 5000
                                  currency: USD
                              responseSummary:
                                success: true
                                metadata: {}
                              startedAt: 2026-07-13T17:36:00.970Z
                              completedAt: 2026-07-13T17:36:00.970Z
                              connectorEvidenceHash: c97411026a82b608d9b0f8b137a64e26d8af24bdda09310b639e527e08a9aada
                        completedAt: 2026-07-13T17:36:00.970Z
                    verifications:
                      - verificationId: e22354e0-7f22-49b2-bd2f-09e47881d788
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        status: VERIFIED
                        message: Execution Trust Record verified successfully.
                        verifiedAt: 2026-07-13T17:36:00.973Z
                        trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    receipts:
                      - receiptId: 863947e6-9d36-492e-b9c9-78b0f3e9b6df
                        businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                        trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                        receiptHash: c9666b08b42bb5ff77650b544c3c2183609f25e4d8dc09359b71a970c57ed3ce
                        issuedAt: 2026-07-13T17:36:00.973Z
                        algorithm: ed25519
                        signature: Pcr42EvbaUbLogvVhYiV3g7uuwvZr82HUJhVptnXRxmTtiEGiz2SCdz5Z80xRDvmvSxpOSG5OnvMAaRzJXncBg==
                    createdAt: 2026-07-13T17:36:00.971Z
                    updatedAt: 2026-07-13T17:36:00.971Z
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    signature:
                      algorithm: ed25519
                      keyId: default
                      value: gjDYmHUBKIrv7bT3uwAkt3wHeoSwz5uANXQV5MfsFgbUIh83KiufZwxht3F3Een+7K/ZW9SRn0F0J7ACJRLbAg==
                      signedAt: 2026-07-13T17:36:00.972Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id. A refused transaction
            has a Refusal Record instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl https://parmana-sandbox.vercel.app/trust-records/$TXN_ID \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const record = await
            client.trustRecord("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90");


            console.log(record.executions[0]?.decision.outcome);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            record = client.trust_record("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")

            print(record.executions[0].decision.outcome)
  /replay:
    post:
      tags:
        - Replay
      summary: Replay an Execution Trust Record
      operationId: replayTransaction
      description: >
        Verifies the stored signature on a Trust Record and returns its hash
        with the result. It does not

        evaluate the policy again and does not execute anything. Nothing is
        appended to the record; for a

        recorded check use `POST /verify`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/replay-request.schema"
            examples:
              vendor-payment-flow:
                summary: Real captured request
                value:
                  businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
      responses:
        "200":
          description: The check ran. Read `verified`; a `200` alone does not mean the
            signature verified.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/replay-response.schema"
              examples:
                vendor-payment-flow:
                  summary: Real captured response
                  value:
                    businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                    trustRecordHash: e20a8861864f1a0a6c5fe00e64abca04de18a7d165a82ef9d7beb458c2096b3f
                    verified: true
        "400":
          description: "`businessTransactionId` is missing."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                missingField:
                  summary: Real captured response
                  value:
                    error: businessTransactionId is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Your key has no Trust Record with this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Execution Trust Record not found.
                    code: VERIFICATION_FAILED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # TXN_ID: the businessTransactionId of the transaction. In the
            sandbox, use one from a playground run;

            # sandbox data is deleted hourly, so an older id returns 404.

            TXN_ID="${TXN_ID:?set TXN_ID to a businessTransactionId}"

            curl -X POST https://parmana-sandbox.vercel.app/replay \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d "$(cat <<JSON
            {
                "businessTransactionId": "$TXN_ID"
            }

            JSON

            )"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const replay = await
            client.replay("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90");


            console.log(replay);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            replay =
            client.replay.replay("5f0c2a7e-3d7b-4d0e-9a55-2f6d8b1c4e90")


            print(replay)
  /policies/in-effect:
    get:
      tags:
        - Policies
      summary: Get the policy a capability's requests must declare right now
      operationId: getPolicyInEffect
      description: >
        Tells an agent what a request for a capability must declare and carry
        right now: the policy name and

        version, the policy's description, and its signals (every fact the rules
        read with its type, the

        facts that must equal a value of the intent, and the facts that need a
        signed human approval with

        what that approval must name). Call it before every `POST /execute`
        instead of hard coding a version:

        approving a new version changes the answer with no deploy. The rule
        conditions are never returned.

        Allowed for a key that may invoke the capability, and for human keys.
      parameters:
        - name: capability
          in: query
          required: true
          description: The action, as in `intent.action`. The sandbox's only action is
            `sandbox:receipt`; production answers for its own capabilities, such
            as `paytm:refund`.
          schema:
            type: string
            x-default: sandbox:receipt
          example: sandbox:receipt
      responses:
        "200":
          description: What to declare and carry.
          content:
            application/json:
              schema:
                type: object
                required:
                  - capability
                  - policy
                  - description
                  - signals
                properties:
                  capability:
                    type: string
                  policy:
                    type: object
                    required:
                      - name
                      - version
                      - schemaVersion
                    properties:
                      name:
                        type: string
                      version:
                        type: string
                      schemaVersion:
                        type: string
                  description:
                    type:
                      - string
                      - "null"
                    description: The policy author's description for people, or null.
                  authorityGrantRequired:
                    type: boolean
                    const: true
                    description: >
                      Present only when the policy requires it. Your key needs a
                      current authority grant for this capability, made through
                      maker checker (/authority-grants), and each request must
                      be within the grant's limits.
                  signals:
                    type: object
                    description: What a request under this policy must carry.
                    required:
                      - facts
                      - schema
                      - bound
                      - approval
                    properties:
                      facts:
                        type: array
                        items:
                          type: string
                        description: Every fact the policy's rules read, sorted. Send each in the
                          request's signals.
                      schema:
                        type: object
                        additionalProperties:
                          type: string
                        description: The declared type of each signal (boolean, number, string).
                      bound:
                        type: object
                        additionalProperties:
                          type: string
                        description: >
                          Signals that must equal a value of the Intent, as
                          signal name to path (for example refundAmount to
                          parameters.amount). A mismatch is refused.
                      approval:
                        type: object
                        additionalProperties:
                          type: object
                          required:
                            - resourceId
                          properties:
                            resourceId:
                              type: string
                              description: Where the resource the approval names is in the request, target or
                                a parameters path.
                            value:
                              type: string
                              description: Where the amount the approval must cover is, when there is one.
                        description: >
                          Signals that count as true only with a signed approval
                          in signals.approvalArtifact, and what that approval
                          must name.
                      sourced:
                        type: object
                        additionalProperties:
                          type: object
                          required:
                            - source
                            - claim
                            - subject
                          properties:
                            source:
                              type: string
                              description: The registered business system the server asks.
                            claim:
                              type: string
                              description: What the server asks it.
                            subject:
                              type: string
                              description: Where the business object is in the request, target or a parameters
                                path.
                            maxAgeSeconds:
                              type: integer
                              description: How old the source's answer may be.
                        description: >
                          Present only when the policy has any. Facts the server
                          asks a business system for itself; you need not send
                          them, and a value you send that differs from the
                          source's is refused.
              examples:
                refund:
                  summary: The refund capability under customer-refund 1.2.0
                  value:
                    capability: paytm:refund
                    policy:
                      name: customer-refund
                      version: 1.2.0
                      schemaVersion: 1.0.0
                    description: Authorizes customer refunds. Every refund above 0 and up to 100000
                      needs a signed manager approval for that order, covering
                      that amount.
                    signals:
                      facts:
                        - fraudCheckPassed
                        - managerApproved
                        - refundAmount
                        - refundEligible
                      schema:
                        refundEligible: boolean
                        managerApproved: boolean
                        fraudCheckPassed: boolean
                        refundAmount: number
                      bound:
                        refundAmount: parameters.amount
                      approval:
                        managerApproved:
                          resourceId: parameters.orderId
                          value: parameters.amount
        "400":
          description: The `capability` query parameter is missing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                missingCapability:
                  summary: Real captured response
                  value:
                    error: capability is required, for example ?capability=paytm:refund.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`CAPABILITY_NOT_ALLOWED`: your key may not invoke this capability."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                capabilityNotAllowed:
                  summary: Real captured response, a key allowed only slack:post-message
                  value:
                    error: Caller is not permitted to invoke this capability.
                    code: CAPABILITY_NOT_ALLOWED
        "404":
          description: "`CAPABILITY_NOT_BOUND`: no policy is bound to this capability, so
            no request for it can succeed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                capabilityNotBound:
                  summary: Real captured response
                  value:
                    error: No policy is bound to capability "test:fixture-execute".
                    code: CAPABILITY_NOT_BOUND
        "409":
          description: "`NO_APPROVED_POLICY_VERSION`: no version of the bound policy has
            been approved yet, so every request is refused. A person must
            propose and approve one."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                noApprovedVersion:
                  summary: Real captured response
                  value:
                    error: 'Every request for "paytm:refund" is refused right now: no version of
                      policy "customer-refund", which capability "paytm:refund"
                      requires, has been approved through policy governance.'
                    code: NO_APPROVED_POLICY_VERSION
        "503":
          description: "`POLICY_VERSION_UNAVAILABLE`: the version in effect could not be
            read, so every request is refused meanwhile. Retry later."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                lookupFailed:
                  summary: Real captured response, the policy version store was unreachable
                  value:
                    error: 'Every request for "paytm:refund" is refused right now: the version of
                      policy "customer-refund" in effect for capability
                      "paytm:refund" could not be looked up: database
                      unreachable.'
                    code: POLICY_VERSION_UNAVAILABLE
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-sandbox.vercel.app/policies/in-effect?capability=sandbox%3Areceipt" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const inEffect = await client.policyInEffect("paytm:refund");

            console.log(inEffect.policy, inEffect.signals.approval);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            in_effect = client.policy_in_effect("paytm:refund")

            print(in_effect.policy, in_effect.signals.approval)
  /policies/validate:
    post:
      tags:
        - Policies
      summary: Validate that a Policy exists and is readable
      operationId: validatePolicy
      description: >
        Checks that this server can load a policy at a name and version, the
        same lookup a request uses.

        Unlike other routes, every answer, success or failure, has the shape `{
        valid, errors }`; only a

        `401` uses the usual error body.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/policy-validate-request.schema"
            examples:
              vendor-payment:
                summary: Real captured request
                value:
                  policyId: vendor-payment
                  policyVersion: 2.0.0
              notFound:
                summary: Real captured request, unknown policy
                value:
                  policyId: does-not-exist
                  policyVersion: 9.9.9
      responses:
        "200":
          description: The policy exists and loads.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/policy-validate-response.schema"
              examples:
                vendor-payment:
                  summary: Real captured response
                  value:
                    valid: true
                    errors: []
        "400":
          description: "`policyId` or `policyVersion` is missing or empty. Checked in that
            order."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/policy-validate-response.schema"
              examples:
                missingField:
                  summary: Real captured response
                  value:
                    valid: false
                    errors:
                      - policyVersion is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: No policy exists at this name and version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/policy-validate-response.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    valid: false
                    errors:
                      - Policy 'does-not-exist' version '9.9.9' was not found.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST https://parmana-sandbox.vercel.app/policies/validate \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "policyId": "customer-refund",
                "policyVersion": "1.2.0"
              }'
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const result = await client.validatePolicy("customer-refund",
            "1.2.0");


            console.log(result);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            result = client.validate_policy("customer-refund", "1.2.0")

            print(result)
  /policies/{name}/{version}/pending-changes:
    post:
      tags:
        - Policy Governance
      summary: Propose a policy change (maker)
      operationId: proposePolicyChange
      security:
        - operatorBearer: []
      description: >
        Proposes new content for a policy, the maker's step in maker checker
        governance. Nothing changes

        until a different person approves it. `{version}` is the version this
        proposal changes;

        `proposedContent.policyVersion` may be the same (a correction) or new (a
        new version). The content is

        checked as `POST /policies/validate` would, and
        `proposedContent.policyId` must equal `{name}`. Needs

        a human key. The public sandbox's demo key is not one, so this route
        cannot be tried there as a reader: it

        answers `401` with no key and needs an operator-issued human key
        (`credentialHolderType: USER`). See

        [Policy lifecycle and
        approvals](/guides/policy-lifecycle-and-approvals).
      parameters:
        - name: name
          description: The policy name, for example `customer-refund`. Not `.` or `..`.
            Prefilled with the sandbox's policy.
          in: path
          required: true
          schema:
            type: string
            pattern: ^[A-Za-z0-9._-]+$
            x-default: sandbox-receipt
        - name: version
          description: The version this proposal changes, for example `1.2.0`. Not `.` or
            `..`. Prefilled with the sandbox's version.
          in: path
          required: true
          schema:
            type: string
            pattern: ^[A-Za-z0-9._-]+$
            x-default: 1.0.0
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/propose-policy-change-request.schema"
      responses:
        "201":
          description: Proposed and waiting for a checker. Keep the `pendingPolicyChangeId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/pending-policy-change-response.schema"
              examples:
                proposed:
                  summary: Real captured response
                  value:
                    pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
                    policyName: openapi-capture-demo
                    policyVersion: 1.0.0
                    proposedContent:
                      policyId: openapi-capture-demo
                      policyVersion: 1.0.0
                      schemaVersion: 1.0.0
                      rules:
                        - id: always-approve
                          condition:
                            always: true
                          outcome:
                            action: approve
                            reason: OpenAPI spec capture fixture
                    proposedBy: human-maker
                    proposedAt: 2026-09-15T03:47:13.920Z
                    status: PENDING_APPROVAL
                    reason: Capture a real Pending Policy Change for the OpenAPI spec
        "400":
          description: The name or version is malformed, `reason` is missing, or the
            content is not a valid policy. The message says which.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                missingReason:
                  summary: Real captured response
                  value:
                    error: reason is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key. The attempt is
            recorded in the audit trail."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                nonHuman:
                  summary: Shape, derived from NonHumanCallerDeniedError source (not independently
                    captured in this run)
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # proposal.json: {"reason": "...", "proposedContent": <the policy file, unchanged>}
            curl -X POST https://parmana-sandbox.vercel.app/policies/customer-refund/1.3.0/pending-changes \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data @proposal.json
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const change = await client.proposePolicyChange("customer-refund",
            "1.3.0", {
              reason: "Raise the maximum refund to 150000.",
              proposedContent: JSON.parse(
                readFileSync("policies/customer-refund/1.3.0/policy.json", "utf8"),
              ),
            });


            console.log(change.pendingPolicyChangeId);
        - lang: python
          label: Python
          source: >-
            import json

            import os

            from pathlib import Path


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            change = client.propose_policy_change(
                "customer-refund",
                "1.3.0",
                proposed_content=json.loads(
                    Path("policies/customer-refund/1.3.0/policy.json").read_text()
                ),
                reason="Raise the maximum refund to 150000.",
            )


            print(change.pending_policy_change_id)
  /policies/pending-changes:
    get:
      tags:
        - Policy Governance
      summary: List pending policy changes (maker-checker)
      operationId: listPendingPolicyChanges
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists policy changes with a diff: the content in effect today
        (`current`, or `null` for a new version)

        next to the proposed content. A checker uses `status=PENDING_APPROVAL`
        to find what waits for review.

        Needs a human key.
      parameters:
        - name: status
          description: "`PENDING_APPROVAL`, `APPROVED` or `REJECTED`. Omit for all."
          in: query
          required: false
          schema:
            type: string
            enum:
              - PENDING_APPROVAL
              - APPROVED
              - REJECTED
      responses:
        "200":
          description: The changes with their diffs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/pending-changes-list-response.schema"
              examples:
                oneOpen:
                  summary: Real captured response
                  value:
                    changes:
                      - pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
                        policyName: openapi-capture-demo
                        policyVersion: 1.0.0
                        proposedContent:
                          policyId: openapi-capture-demo
                          policyVersion: 1.0.0
                          schemaVersion: 1.0.0
                          rules:
                            - id: always-approve
                              condition:
                                always: true
                              outcome:
                                action: approve
                                reason: OpenAPI spec capture fixture
                        proposedBy: human-maker
                        proposedAt: 2026-09-15T03:47:13.920Z
                        status: PENDING_APPROVAL
                        reason: Capture a real Pending Policy Change for the OpenAPI spec
                        diff:
                          current: null
                          proposed:
                            policyId: openapi-capture-demo
                            policyVersion: 1.0.0
                            schemaVersion: 1.0.0
                            rules:
                              - id: always-approve
                                condition:
                                  always: true
                                outcome:
                                  action: approve
                                  reason: OpenAPI spec capture fixture
        "400":
          description: "`status` is not `PENDING_APPROVAL`, `APPROVED` or `REJECTED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badStatus:
                  summary: Real captured response, status=OPEN
                  value:
                    error: status must be one of PENDING_APPROVAL, APPROVED, REJECTED.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/policies/pending-changes?status=PENDING_APPROVAL" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const changes = await client.policyChanges("PENDING_APPROVAL");

            console.log(changes.length);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            changes = client.policy_changes("PENDING_APPROVAL")

            print(len(changes))
  /kill-switch/trip:
    post:
      tags:
        - Kill Switch
      summary: Trip the operator kill switch
      operationId: tripKillSwitch
      description: >
        Stops new authorization and approval requests until an operator resets
        it. Needs the

        `kill-switch:trip` capability on the key. Trip, reset and status stay
        reachable while the switch is on.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - reason
              properties:
                reason:
                  type: string
                  minLength: 1
            example:
              reason: suspicious refunds
      responses:
        "200":
          description: Tripped. Returns the state.
          content:
            application/json:
              schema:
                type: object
              example:
                active: true
                version: 1
                trippedBy: operator-a
                trippedAt: 2026-10-20T14:00:41.256Z
                tripReason: suspicious refunds
        "400":
          description: The reason is missing.
          content:
            application/json:
              schema:
                type: object
              example:
                error: reason is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The key lacks the kill-switch:trip capability.
          content:
            application/json:
              schema:
                type: object
              example:
                error: CAPABILITY_NOT_ALLOWED
                message: This key does not hold the kill-switch:trip capability.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST https://your-parmana-server/kill-switch/trip \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"reason": "suspicious refunds"}'
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await
            fetch("https://your-parmana-server/kill-switch/trip", {
              method: "POST",
              headers: {
                Authorization: `Bearer ${process.env.PARMANA_API_KEY ?? ""}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ reason: "suspicious refunds" }),
            });


            console.log(await response.json());
        - lang: python
          label: Python
          source: >-
            import json

            import os

            import urllib.request


            request = urllib.request.Request(
                "https://your-parmana-server/kill-switch/trip",
                data=json.dumps({"reason": "suspicious refunds"}).encode(),
                headers={
                    "Authorization": f"Bearer {os.environ.get('PARMANA_API_KEY', '')}",
                    "Content-Type": "application/json",
                },
                method="POST",
            )


            with urllib.request.urlopen(request) as response:
                print(json.loads(response.read()))
  /kill-switch/reset:
    post:
      tags:
        - Kill Switch
      summary: Reset the operator kill switch
      operationId: resetKillSwitch
      description: >
        Lets authorization and approval requests through again. Needs the
        `kill-switch:reset` capability on the key.

        In production a reset takes two different operators (ADR-0017 decision
        1). The first request answers 202 and

        holds the reset as pending. A second operator's request completes it and
        answers 200. The same operator

        cannot confirm their own request (409). A trip cancels a pending reset.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - reason
              properties:
                reason:
                  type: string
                  minLength: 1
            example:
              reason: reviewed, safe
      responses:
        "200":
          description: Reset complete. Returns the outcome and the state.
          content:
            application/json:
              schema:
                type: object
              example:
                outcome: reset
                state:
                  active: false
                  version: 3
                  trippedBy: operator-a
                  trippedAt: 2026-10-20T14:00:41.256Z
                  tripReason: suspicious refunds
                  resetRequestedBy: operator-a
                  resetBy: operator-b
                  resetAt: 2026-10-20T14:00:41.276Z
                  resetReason: confirmed
        "202":
          description: The reset is pending. A second, different operator must confirm it.
          content:
            application/json:
              schema:
                type: object
              example:
                outcome: pending
                state:
                  active: true
                  version: 2
                  trippedBy: operator-a
                  trippedAt: 2026-10-20T14:00:41.256Z
                  tripReason: suspicious refunds
                  pendingResetBy: operator-a
                  pendingResetAt: 2026-10-20T14:00:41.271Z
                  pendingResetReason: reviewed, safe
        "400":
          description: The reason is missing.
          content:
            application/json:
              schema:
                type: object
              example:
                error: reason is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The key lacks the kill-switch:reset capability.
          content:
            application/json:
              schema:
                type: object
              example:
                error: CAPABILITY_NOT_ALLOWED
                message: This key does not hold the kill-switch:reset capability.
        "409":
          description: The same operator asked for this reset before. A second operator
            must confirm it.
          content:
            application/json:
              schema:
                type: object
              example:
                error: SAME_OPERATOR_CANNOT_CONFIRM_RESET
                message: A second operator must confirm this reset.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST https://your-parmana-server/kill-switch/reset \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{"reason": "reviewed, safe"}'
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await
            fetch("https://your-parmana-server/kill-switch/reset", {
              method: "POST",
              headers: {
                Authorization: `Bearer ${process.env.PARMANA_API_KEY ?? ""}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ reason: "reviewed, safe" }),
            });


            console.log(await response.json());
        - lang: python
          label: Python
          source: >-
            import json

            import os

            import urllib.request


            request = urllib.request.Request(
                "https://your-parmana-server/kill-switch/reset",
                data=json.dumps({"reason": "reviewed, safe"}).encode(),
                headers={
                    "Authorization": f"Bearer {os.environ.get('PARMANA_API_KEY', '')}",
                    "Content-Type": "application/json",
                },
                method="POST",
            )


            with urllib.request.urlopen(request) as response:
                print(json.loads(response.read()))
  /kill-switch/status:
    get:
      tags:
        - Kill Switch
      summary: Read the kill switch state
      operationId: getKillSwitchStatus
      description: >
        Returns the current state with the nonce you sent echoed back. The
        response is not signed in this release (see ADR-0017).
      parameters:
        - name: nonce
          description: A value you choose. It is echoed back, so an old response cannot
            pass as a current one.
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: The state, with the nonce echoed.
          content:
            application/json:
              schema:
                type: object
              example:
                active: true
                version: 1
                trippedBy: operator-a
                trippedAt: 2026-10-20T14:00:41.256Z
                tripReason: suspicious refunds
                nonce: check-1
                signed: false
        "401":
          $ref: "#/components/responses/Unauthorized"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl "https://your-parmana-server/kill-switch/status?nonce=check-1"
            \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await fetch(
              "https://your-parmana-server/kill-switch/status?nonce=check-1",
              {
                headers: { Authorization: `Bearer ${process.env.PARMANA_API_KEY ?? ""}` },
              },
            );


            console.log(await response.json());
        - lang: python
          label: Python
          source: >-
            import json

            import os

            import urllib.request


            request = urllib.request.Request(
                "https://your-parmana-server/kill-switch/status?nonce=check-1",
                headers={"Authorization": f"Bearer {os.environ.get('PARMANA_API_KEY', '')}"},
            )


            with urllib.request.urlopen(request) as response:
                print(json.loads(response.read()))
  /kill-switch/probes:
    post:
      tags:
        - Kill Switch
      summary: Record a kill switch probe result
      operationId: recordKillSwitchProbe
      description: >
        Records what a scheduler saw from a harmless authorization request. The
        scheduler sends the request to

        `/execute` and passes the response status and error code here. The
        result is `refused` when the response is

        503 with `KILL_SWITCH_ACTIVE`. It `passed` when the refusal matches the
        switch state at the time of recording.

        Needs the `kill-switch:probe` capability on the key. Results are kept in
        the database. The one-year retention

        job is not built yet (ADR-0017 decision 3).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - status
              properties:
                status:
                  type: integer
                  description: The HTTP status the probe request received.
                errorCode:
                  type: string
                  description: The error code in the response body, when there is one.
            example:
              status: 503
              errorCode: KILL_SWITCH_ACTIVE
      responses:
        "201":
          description: The probe result, as recorded.
          content:
            application/json:
              schema:
                type: object
              example:
                probedAt: 2026-10-20T14:00:41.264Z
                recordedBy: scheduler
                switchActive: true
                switchVersion: 1
                observedStatus: 503
                observedErrorCode: KILL_SWITCH_ACTIVE
                refused: true
                passed: true
        "400":
          description: The status is missing or not an integer.
          content:
            application/json:
              schema:
                type: object
              example:
                error: status is required and must be an integer.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: The key lacks the kill-switch:probe capability.
          content:
            application/json:
              schema:
                type: object
              example:
                error: CAPABILITY_NOT_ALLOWED
                message: This key does not hold the kill-switch:probe capability.
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST https://your-parmana-server/kill-switch/probes \
              -H "Authorization: Bearer $PARMANA_PROBE_KEY" \
              -H "Content-Type: application/json" \
              -d '{"status": 503, "errorCode": "KILL_SWITCH_ACTIVE"}'
        - lang: typescript
          label: TypeScript
          source: >-
            const response = await
            fetch("https://your-parmana-server/kill-switch/probes", {
              method: "POST",
              headers: {
                Authorization: `Bearer ${process.env.PARMANA_PROBE_KEY ?? ""}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ status: 503, errorCode: "KILL_SWITCH_ACTIVE" }),
            });


            console.log(await response.json());
        - lang: python
          label: Python
          source: >-
            import json

            import os

            import urllib.request


            request = urllib.request.Request(
                "https://your-parmana-server/kill-switch/probes",
                data=json.dumps({"status": 503, "errorCode": "KILL_SWITCH_ACTIVE"}).encode(),
                headers={
                    "Authorization": f"Bearer {os.environ.get('PARMANA_PROBE_KEY', '')}",
                    "Content-Type": "application/json",
                },
                method="POST",
            )


            with urllib.request.urlopen(request) as response:
                print(json.loads(response.read()))
  /policies/pending-changes/{id}/approve:
    post:
      tags:
        - Policy Governance
      summary: Approve a pending policy change (checker)
      operationId: approvePolicyChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Approves a policy change, the checker's step, and puts it into effect.
        Checked in order: a human key,

        not the person who proposed it, and a step up authorization signed with
        your step up key for this

        change id and action `approve`, valid once. The policy is written and a
        signed approval record is

        kept before the change is marked approved, so a failure never leaves a
        change approved without

        effect. The next request for the bound capability must declare the
        approved version. See

        [Policy governance](/guides/policy-governance-maker-checker).
      parameters:
        - name: id
          description: The `pendingPolicyChangeId` from the proposal.
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/approve-policy-change-request.schema"
      responses:
        "200":
          description: Approved and in effect.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/pending-policy-change-response.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
                    policyName: openapi-capture-demo
                    policyVersion: 1.0.0
                    proposedContent:
                      policyId: openapi-capture-demo
                      policyVersion: 1.0.0
                      schemaVersion: 1.0.0
                      rules:
                        - id: always-approve
                          condition:
                            always: true
                          outcome:
                            action: approve
                            reason: OpenAPI spec capture fixture
                    proposedBy: human-maker
                    proposedAt: 2026-09-15T03:47:13.920Z
                    status: APPROVED
                    reason: Capture a real Pending Policy Change for the OpenAPI spec
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-15T03:48:03.586Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED` (needs a human key),
            `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE` (the proposer cannot
            approve), or `STEP_UP_AUTHORIZATION_INVALID` (missing, expired,
            reused, or signed for another change, action or key; sign a new
            one)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response
                  value:
                    error: Pending Policy Change '307d8265-1171-49a4-b13f-fc3d3ae323e3' was proposed
                      by this same caller — the proposer (maker) may not also
                      approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
                stepUpInvalid:
                  summary: Real captured response
                  value:
                    error: The step-up authorization envelope is missing, invalid, expired,
                      replayed, or does not match this request.
                    code: STEP_UP_AUTHORIZATION_INVALID
        "404":
          description: No policy change has this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Pending Policy Change '00000000-0000-0000-0000-000000000000' not found.
                    code: PENDING_POLICY_CHANGE_NOT_FOUND
        "500":
          description: The server is misconfigured and cannot apply policy changes. The
            change stays pending; nothing was written.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                misconfigured:
                  summary: Real captured response, a deployment with no
                    PolicyChangeApprovalService
                  value:
                    error: Internal Server Error
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action approve > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/policies/pending-changes/ba7c5827-5844-4069-94fc-9b438ef08f78/approve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "approve",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.approvePolicyChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: >-
            import os

            from pathlib import Path


            from parmana import ParmanaClient

            from parmana.crypto import sign_policy_change_step_up


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="approve",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )


            change =
            client.approve_policy_change("ba7c5827-5844-4069-94fc-9b438ef08f78",
            step_up)


            print(change.status)
  /policies/pending-changes/{id}/reject:
    post:
      tags:
        - Policy Governance
      summary: Reject a pending policy change (checker)
      operationId: rejectPolicyChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rejects a policy change. The policy in effect does not change. The same
        checks as approve, with a

        step up authorization for action `reject` and a `rejectionReason`.
      parameters:
        - name: id
          description: The `pendingPolicyChangeId` from the proposal.
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/reject-policy-change-request.schema"
      responses:
        "200":
          description: Rejected. The policy in effect did not change.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/pending-policy-change-response.schema"
              examples:
                rejected:
                  summary: Real captured response
                  value:
                    pendingPolicyChangeId: 5617ba3c-fb07-437d-9131-232b84bbb863
                    policyName: openapi-capture-demo-reject
                    policyVersion: 1.0.0
                    proposedContent:
                      policyId: openapi-capture-demo-reject
                      policyVersion: 1.0.0
                      schemaVersion: 1.0.0
                      rules:
                        - id: always-approve
                          condition:
                            always: true
                          outcome:
                            action: approve
                            reason: OpenAPI spec capture fixture
                    proposedBy: human-maker
                    proposedAt: 2026-09-15T03:48:09.512Z
                    status: REJECTED
                    reason: Capture a real rejected Pending Policy Change for the OpenAPI spec
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-15T03:48:21.422Z
                    rejectionReason: Does not meet the bar for this fixture
        "400":
          description: "`rejectionReason` is missing or empty."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                emptyReason:
                  summary: Real captured response
                  value:
                    error: rejectionReason is required.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`, as on approve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response, the maker rejecting their own change
                  value:
                    error: Pending Policy Change '011c1620-89d9-4bf5-a0b0-356edd566d3f' was proposed
                      by this same caller — the proposer (maker) may not also
                      approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
        "404":
          description: No policy change has this id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownChange:
                  summary: Real captured response
                  value:
                    error: Pending Policy Change '7d5f8a52-0000-4000-8000-000000000000' not found.
                    code: PENDING_POLICY_CHANGE_NOT_FOUND
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action reject > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/policies/pending-changes/ba7c5827-5844-4069-94fc-9b438ef08f78/reject \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"rejectionReason\": \"The maximum stays at 100000 this quarter.\", \"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "reject",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.rejectPolicyChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              "The maximum stays at 100000 this quarter.",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="reject",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.reject_policy_change(
                "ba7c5827-5844-4069-94fc-9b438ef08f78",
                "The maximum stays at 100000 this quarter.",
                step_up,
            )

            print(change.status)
  /approval-issuers:
    get:
      tags:
        - Approvers
      summary: List approver keys
      operationId: listApprovalIssuers
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists every approver key the server trusts, or once trusted, to sign
        human approvals for policies

        that require one. Keys in the server code come first (`source: code`),
        then keys added through

        approver changes (`source: governed`). Revoked keys stay listed and
        marked, so every approval ever

        accepted can be explained. Needs a human key. See [Manage
        approvers](/guides/manage-approvers).
      responses:
        "200":
          description: The approver keys.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/approval-issuers-list-response.schema"
              examples:
                codeAndGoverned:
                  summary: Real captured response
                  value:
                    issuers:
                      - approverId: refund-manager
                        keyId: refund-manager-key-1
                        revoked: false
                        source: code
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAVMs/E6N2XEQfEEWlwMg0wRS0L4svbZ0W785aAxUP78M=
                          -----END PUBLIC KEY-----
                      - approverId: approver-example
                        keyId: approver-example-key-1
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
                          -----END PUBLIC KEY-----
                        revoked: false
                        addedByChangeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                        addedAt: 2026-09-28T19:05:59.738Z
                        source: governed
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/approval-issuers \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const approvers = await client.approvers();

            console.log(approvers);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            approvers = client.list_approvers()

            print(approvers)
  /approval-issuers/changes:
    post:
      tags:
        - Approvers
      summary: Propose adding or revoking an approver key
      operationId: proposeApprovalIssuerChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Proposes trusting a new approver key (`add`, with its Ed25519 public
        key) or revoking one added this

        way (`revoke`). Nothing changes until a different person approves it.
        Keys in the server code change

        only through a pull request. A key id can be added once: to rotate, add
        a new key id, then revoke the

        old one. Needs a human key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/propose-approval-issuer-change-request.schema"
      responses:
        "201":
          description: Proposed and waiting for a checker. Keep the `changeId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/approval-issuer-change-response.schema"
              examples:
                add:
                  summary: Real captured response
                  value:
                    changeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                    action: add
                    approverId: approver-example
                    keyId: approver-example-key-1
                    publicKeyPem: |
                      -----BEGIN PUBLIC KEY-----
                      MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
                      -----END PUBLIC KEY-----
                    reason: The approver approves refunds for the West region from October.
                    proposedBy: human-maker
                    proposedAt: 2026-09-28T19:05:59.726Z
                    status: PENDING_APPROVAL
                revoke:
                  summary: Real captured response
                  value:
                    changeId: 348e9b4c-61dc-4bef-8815-6d4488750111
                    action: revoke
                    approverId: approver-example
                    keyId: approver-example-key-1
                    reason: The approver moved to another team.
                    proposedBy: human-maker
                    proposedAt: 2026-09-28T19:05:59.750Z
                    status: PENDING_APPROVAL
        "400":
          description: "`action` is not `add` or `revoke`, `approverId` or `keyId` is not
            1 to 128 letters, digits, dots, underscores or hyphens, `reason` is
            missing or over 2000 characters, or `publicKeyPem` is not an Ed25519
            public key (on add) or is present (on revoke)."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badKey:
                  summary: Real captured response
                  value:
                    error: publicKeyPem must be an Ed25519 public key in PEM, as made by
                      scripts/generate-approver-key.ts.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "409":
          description: "`CONFLICT`: the key is in the server code, the key to add already
            exists, the key to revoke is not an active governed key, or another
            change for this key is pending."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyExists:
                  summary: Real captured response
                  value:
                    error: Approver 'approver-example' key 'approver-example-key-1' already exists.
                      Use a new key id to add a new key.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # Run in the folder that holds the new approver's public key file.

            curl -X POST
            https://parmana-api-real.vercel.app/approval-issuers/changes \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "$(jq -n --rawfile pem approver-example__approver-example-key-1.public.pem '{action: "add", approverId: "approver-example", keyId: "approver-example-key-1", publicKeyPem: $pem, reason: "The approver approves refunds for the West region from October."}')"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const change = await client.proposeApproverChange({
              action: "add",
              approverId: "approver-example",
              keyId: "approver-example-key-1",
              publicKeyPem: readFileSync(
                "approver-example__approver-example-key-1.public.pem",
                "utf8",
              ),
              reason: "The approver approves refunds for the West region from October.",
            });


            console.log(change.changeId);
        - lang: python
          label: Python
          source: >-
            import os

            from pathlib import Path


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            change = client.approvers.propose_add(
                approver_id="approver-example",
                key_id="approver-example-key-1",
                public_key_pem=Path(
                    "approver-example__approver-example-key-1.public.pem"
                ).read_text(),
                reason="The example approver approves refunds for the West region from October.",
            )


            print(change.change_id)
    get:
      tags:
        - Approvers
      summary: List approver changes
      operationId: listApprovalIssuerChanges
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists approver changes, newest first. A checker uses
        `status=PENDING_APPROVAL` to find what waits for

        review. Needs a human key.
      parameters:
        - name: status
          description: "`PENDING_APPROVAL`, `APPROVED` or `REJECTED`. Omit for all."
          in: query
          required: false
          schema:
            type: string
            enum:
              - PENDING_APPROVAL
              - APPROVED
              - REJECTED
      responses:
        "200":
          description: The changes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/approval-issuer-changes-list-response.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    changes:
                      - changeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                        action: add
                        approverId: approver-example
                        keyId: approver-example-key-1
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
                          -----END PUBLIC KEY-----
                        reason: The approver approves refunds for the West region from October.
                        proposedBy: human-maker
                        proposedAt: 2026-09-28T19:05:59.726Z
                        status: PENDING_APPROVAL
        "400":
          description: "`status` is not `PENDING_APPROVAL`, `APPROVED` or `REJECTED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badStatus:
                  summary: Real captured response, status=OPEN
                  value:
                    error: status must be one of PENDING_APPROVAL, APPROVED, REJECTED.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/approval-issuers/changes?status=PENDING_APPROVAL" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const changes = await client.approverChanges("PENDING_APPROVAL");

            console.log(changes.length);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            changes = client.approver_changes("PENDING_APPROVAL")

            print(len(changes))
  /approval-issuers/changes/{id}/approve:
    post:
      tags:
        - Approvers
      summary: Approve an approver change
      operationId: approveApprovalIssuerChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Approves an approver change and applies it at once: an added key is
        trusted from the next approval

        checked; a revoked key's approvals are refused from the next request on,
        including ones it already

        signed. Checked in order: a human key, not the proposer, and a step up
        authorization for this change

        id and action `approve`, valid once. The step up authorization is the
        same one policy changes use,

        with the change id in `payload.pendingPolicyChangeId`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/approve-policy-change-request.schema"
            examples:
              stepUp:
                summary: Real captured request
                value:
                  stepUpAuthorization:
                    payload:
                      version: 1
                      nonce: 0b6081da-cd57-4519-ae36-71eb1bb62432
                      pendingPolicyChangeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                      action: approve
                      authorizedAt: 2026-09-28T19:05:59.733Z
                      expiresAt: 2026-09-28T19:07:59.733Z
                    signature: Nv94Q62/g4IQ0zFxDqF621Stz+JBy/4KOvLJ9+RfJO8xMw6hw06X4gzdDNEh2DxJt9xmL7VTF9TxDd9s9qmkCg==
                    keyId: human-checker-step-up-key
                    algorithm: ed25519
      responses:
        "200":
          description: Approved and applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/approval-issuer-change-response.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    changeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                    action: add
                    approverId: approver-example
                    keyId: approver-example-key-1
                    publicKeyPem: |
                      -----BEGIN PUBLIC KEY-----
                      MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
                      -----END PUBLIC KEY-----
                    reason: The approver approves refunds for the West region from October.
                    proposedBy: human-maker
                    proposedAt: 2026-09-28T19:05:59.726Z
                    status: APPROVED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-28T19:05:59.738Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`. Sign a new step up authorization
            for this change and action."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response
                  value:
                    error: Approver change 'ba7c5827-5844-4069-94fc-9b438ef08f78' was proposed by
                      this same caller — the proposer (maker) may not also
                      approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
                stepUpReused:
                  summary: Real captured response, the same step up sent twice
                  value:
                    error: The step-up authorization envelope is missing, invalid, expired,
                      replayed, or does not match this request.
                    code: STEP_UP_AUTHORIZATION_INVALID
        "404":
          description: "`APPROVAL_ISSUER_CHANGE_NOT_FOUND`: no approver change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Approver change '00000000-0000-0000-0000-000000000000' not found.
                    code: APPROVAL_ISSUER_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved, the key to add now
            exists, or the key to revoke is no longer active. Nothing was
            written."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyApproved:
                  summary: Real captured response
                  value:
                    error: Approver change 'e0292192-c75b-45eb-89e2-c14bae0f8751' is already
                      APPROVED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action approve > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/approval-issuers/changes/ba7c5827-5844-4069-94fc-9b438ef08f78/approve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "approve",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.approveApproverChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="approve",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.approve_approver_change("ba7c5827-5844-4069-94fc-9b438ef08f78", step_up)

            print(change.status)
  /approval-issuers/changes/{id}/reject:
    post:
      tags:
        - Approvers
      summary: Reject an approver change
      operationId: rejectApprovalIssuerChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rejects an approver change. No key changes. The same checks as approve,
        with a step up authorization

        for action `reject` and a `rejectionReason`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/reject-policy-change-request.schema"
      responses:
        "200":
          description: Rejected. No key changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/approval-issuer-change-response.schema"
              examples:
                rejected:
                  summary: Real captured response
                  value:
                    changeId: 348e9b4c-61dc-4bef-8815-6d4488750111
                    action: revoke
                    approverId: approver-example
                    keyId: approver-example-key-1
                    reason: The approver moved to another team.
                    proposedBy: human-maker
                    proposedAt: 2026-09-28T19:05:59.750Z
                    status: REJECTED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-28T19:05:59.753Z
                    rejectionReason: The approver stays on refunds until 31 October.
        "400":
          description: "`rejectionReason` is missing, empty or over 2000 characters."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                emptyReason:
                  summary: Real captured response
                  value:
                    error: rejectionReason is required, at most 2000 characters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`, as on approve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response, the maker rejecting their own change
                  value:
                    error: Approver change 'e0292192-c75b-45eb-89e2-c14bae0f8751' was proposed by
                      this same caller — the proposer (maker) may not also
                      approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
        "404":
          description: "`APPROVAL_ISSUER_CHANGE_NOT_FOUND`: no approver change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownChange:
                  summary: Real captured response
                  value:
                    error: Approver change '7d5f8a52-0000-4000-8000-000000000000' not found.
                    code: APPROVAL_ISSUER_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response
                  value:
                    error: Approver change 'e0292192-c75b-45eb-89e2-c14bae0f8751' is already
                      APPROVED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action reject > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/approval-issuers/changes/ba7c5827-5844-4069-94fc-9b438ef08f78/reject \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"rejectionReason\": \"The approver stays on refunds until 31 October.\", \"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "reject",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.rejectApproverChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              "The approver stays on refunds until 31 October.",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="reject",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.reject_approver_change(
                "ba7c5827-5844-4069-94fc-9b438ef08f78",
                "The approver stays on refunds until 31 October.",
                step_up,
            )

            print(change.status)
  /external-connectors:
    get:
      tags:
        - External Connectors
      summary: List external connectors
      operationId: listExternalConnectors
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists every external connector registration, active and revoked: the
        capability, the HTTPS endpoint

        Parmana releases it to, the policy that governs it and the parameters
        forwarded. Revoked

        registrations stay listed, so every release ever made can be explained.
        Needs a human key. See

        [Connect any external system](/guides/connect-any-external-system).
      responses:
        "200":
          description: The registrations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/external-connectors-list-response.schema"
              examples:
                active:
                  summary: Real captured response
                  value:
                    connectors:
                      - registrationId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
                        capability: erp:create-invoice
                        endpointUrl: https://erp.example.com/parmana/release
                        policy: erp-invoice
                        allowedParameters:
                          - amount
                          - currency
                          - customerId
                        timeoutMs: 10000
                        status: active
                        registeredAt: 2026-09-30T17:25:46.385Z
                withRevoked:
                  summary: "Real captured response, after a revoke: the registration stays listed"
                  value:
                    connectors:
                      - registrationId: f3774dad-3182-4206-9aaf-49aefe76b557
                        capability: erp:create-invoice
                        endpointUrl: https://erp.example.com/parmana/release
                        policy: erp-invoice
                        allowedParameters:
                          - amount
                          - currency
                          - customerId
                        timeoutMs: 10000
                        status: revoked
                        registeredAt: 2026-09-30T19:03:21.535Z
                        revokedByChangeId: d1164799-36e9-4281-878a-93d666befd42
                        revokedAt: 2026-09-30T19:03:21.554Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/external-connectors \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            for (const connector of await client.externalConnectors()) {
              console.log(connector.capability, connector.status, connector.endpointUrl);
            }
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            for connector in client.list_external_connectors():
                print(connector.capability, connector.status, connector.endpoint_url)
  /external-connectors/changes:
    post:
      tags:
        - External Connectors
      summary: Propose registering or revoking an external connector
      operationId: proposeExternalConnectorChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Proposes connecting your own system to Parmana with no Parmana code
        change: `register` names a

        capability, the HTTPS endpoint to release it to, the policy that governs
        it, the parameters to

        forward and optionally a timeout; `revoke` ends the active registration
        for a capability. Nothing

        changes until a different person approves it. The endpoint must be
        `https`, name a domain (not an IP

        address or localhost), carry no user name, password or fragment, and
        resolve only to public

        addresses; the address is checked again on approval and at every
        release. Capabilities in a built in

        namespace (`paytm`, `hubspot`, `github`, `slack`, `test`) are refused.
        To move a capability, revoke

        it, then register the new endpoint. Needs a human key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/propose-external-connector-change-request.schema"
            examples:
              register:
                summary: Real captured request
                value:
                  action: register
                  capability: erp:create-invoice
                  endpointUrl: https://erp.example.com/parmana/release
                  policy: erp-invoice
                  allowedParameters:
                    - amount
                    - currency
                    - customerId
                  timeoutMs: 10000
                  reason: Finance creates invoices in the ERP through Parmana from October.
              revoke:
                summary: Real captured request
                value:
                  action: revoke
                  capability: erp:create-invoice
                  reason: The ERP endpoint moves to a new host.
      responses:
        "201":
          description: Proposed and waiting for a checker. Keep the `changeId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/external-connector-change-response.schema"
              examples:
                register:
                  summary: Real captured response
                  value:
                    changeId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
                    action: register
                    capability: erp:create-invoice
                    endpointUrl: https://erp.example.com/parmana/release
                    policy: erp-invoice
                    allowedParameters:
                      - amount
                      - currency
                      - customerId
                    timeoutMs: 10000
                    reason: Finance creates invoices in the ERP through Parmana from October.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T17:25:46.316Z
                    status: PENDING_APPROVAL
                revoke:
                  summary: Real captured response
                  value:
                    changeId: 5bdbfd6a-babf-4977-b090-d561ddcee0d7
                    action: revoke
                    capability: erp:create-invoice
                    reason: The ERP endpoint moves to a new host.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T17:25:46.418Z
                    status: PENDING_APPROVAL
        "400":
          description: "`INVALID_EXTERNAL_CONNECTOR`: a field is missing or malformed, the
            namespace is built in, or a revoke carries registration fields.
            `EXTERNAL_ENDPOINT_ADDRESS_REFUSED`: the endpoint is not https,
            names an IP address or localhost, resolves to an address that is not
            public, or does not resolve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                builtInNamespace:
                  summary: Real captured response
                  value:
                    error: The namespace 'paytm' belongs to a built in connector. Use another
                      namespace.
                    code: INVALID_EXTERNAL_CONNECTOR
                privateAddress:
                  summary: Real captured response
                  value:
                    error: The endpoint host 'erp-internal.example.com' resolves to an address that
                      is not public (10.0.0.7).
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
                badCapability:
                  summary: Real captured response
                  value:
                    error: capability must be namespace:verb, at most 128 characters, matching
                      ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$.
                    code: INVALID_EXTERNAL_CONNECTOR
                badTimeout:
                  summary: Real captured response
                  value:
                    error: timeoutMs must be an integer from 1000 to 30000; it defaults to 10000.
                    code: INVALID_EXTERNAL_CONNECTOR
                duplicateParameter:
                  summary: Real captured response
                  value:
                    error: allowedParameters must be an array of at most 64 distinct names, each
                      matching ^[A-Za-z_][A-Za-z0-9_]{0,63}$.
                    code: INVALID_EXTERNAL_CONNECTOR
                revokeWithRegistrationFields:
                  summary: Real captured response
                  value:
                    error: A revoke takes only action, capability and reason.
                    code: INVALID_EXTERNAL_CONNECTOR
                plainHttp:
                  summary: Real captured response
                  value:
                    error: endpointUrl must use https.
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "409":
          description: "`CONFLICT`: the capability already has an active registration (to
            register) or has none (to revoke), or another change for it is
            pending."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    error: A change for capability 'erp:create-invoice' is already pending. It must
                      be approved or rejected first.
                    code: CONFLICT
                alreadyActive:
                  summary: Real captured response
                  value:
                    error: Capability 'erp:create-invoice' already has an active external connector.
                      Revoke it first.
                    code: CONFLICT
                revokeUnregistered:
                  summary: Real captured response
                  value:
                    error: Capability 'erp:create-invoice' has no active external connector.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X POST
            https://parmana-api-real.vercel.app/external-connectors/changes \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "action": "register",
                "capability": "erp:create-invoice",
                "endpointUrl": "https://erp.example.com/parmana/release",
                "policy": "erp-invoice",
                "allowedParameters": [
                  "amount",
                  "currency"
                ],
                "timeoutMs": 10000,
                "reason": "Finance creates invoices in the ERP through Parmana."
              }'
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            const change = await client.proposeExternalConnectorChange({
              action: "register",
              capability: "erp:create-invoice",
              endpointUrl: "https://erp.example.com/parmana/release",
              policy: "erp-invoice",
              allowedParameters: ["amount", "currency"],
              timeoutMs: 10000,
              reason: "Finance creates invoices in the ERP through Parmana.",
            });

            console.log(change.changeId);
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            change = client.external_connectors.propose_register(
                capability="erp:create-invoice",
                endpoint_url="https://erp.example.com/parmana/release",
                policy="erp-invoice",
                allowed_parameters=["amount", "currency"],
                timeout_ms=10000,
                reason="Finance creates invoices in the ERP through Parmana.",
            )

            print(change.change_id)
    get:
      tags:
        - External Connectors
      summary: List external connector changes
      operationId: listExternalConnectorChanges
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists external connector changes, newest first. A checker uses
        `status=PENDING_APPROVAL` to find what

        waits for review. Needs a human key.
      parameters:
        - name: status
          description: "`PENDING_APPROVAL`, `APPROVED` or `REJECTED`. Omit for all."
          in: query
          required: false
          schema:
            type: string
            enum:
              - PENDING_APPROVAL
              - APPROVED
              - REJECTED
      responses:
        "200":
          description: The changes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/external-connector-changes-list-response.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    changes:
                      - changeId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
                        action: register
                        capability: erp:create-invoice
                        endpointUrl: https://erp.example.com/parmana/release
                        policy: erp-invoice
                        allowedParameters:
                          - amount
                          - currency
                          - customerId
                        timeoutMs: 10000
                        reason: Finance creates invoices in the ERP through Parmana from October.
                        proposedBy: human-maker
                        proposedAt: 2026-09-30T17:25:46.316Z
                        status: PENDING_APPROVAL
                approved:
                  summary: "Real captured response, status=APPROVED: a register and the revoke
                    that ended it"
                  value:
                    changes:
                      - changeId: d1164799-36e9-4281-878a-93d666befd42
                        action: revoke
                        capability: erp:create-invoice
                        reason: The ERP endpoint moves to a new host.
                        proposedBy: human-maker
                        proposedAt: 2026-09-30T19:03:21.551Z
                        status: APPROVED
                        resolvedBy: human-checker
                        resolvedAt: 2026-09-30T19:03:21.554Z
                      - changeId: f3774dad-3182-4206-9aaf-49aefe76b557
                        action: register
                        capability: erp:create-invoice
                        endpointUrl: https://erp.example.com/parmana/release
                        policy: erp-invoice
                        allowedParameters:
                          - amount
                          - currency
                          - customerId
                        timeoutMs: 10000
                        reason: Finance creates invoices in the ERP through Parmana from October.
                        proposedBy: human-maker
                        proposedAt: 2026-09-30T19:03:21.530Z
                        status: APPROVED
                        resolvedBy: human-checker
                        resolvedAt: 2026-09-30T19:03:21.535Z
        "400":
          description: "`status` is not `PENDING_APPROVAL`, `APPROVED` or `REJECTED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badStatus:
                  summary: Real captured response
                  value:
                    error: status must be one of PENDING_APPROVAL, APPROVED, REJECTED.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/external-connectors/changes?status=PENDING_APPROVAL" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            for (const change of await client.externalConnectorChanges(
              "PENDING_APPROVAL",
            )) {
              console.log(change.changeId, change.action, change.capability);
            }
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            for change in client.external_connector_changes("PENDING_APPROVAL"):
                print(change.change_id, change.action, change.capability)
  /external-connectors/changes/{id}/approve:
    post:
      tags:
        - External Connectors
      summary: Approve an external connector change
      operationId: approveExternalConnectorChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Approves an external connector change and applies it at once: `register`
        makes the registration

        active, `revoke` ends it. For a register, the endpoint's address is
        checked again first, so a DNS

        change since the proposal cannot point it at a private address. Checked
        in order: a human key, not

        the proposer, and a step up authorization for this change id and action
        `approve`, valid once, with

        the change id in `payload.pendingPolicyChangeId`. Requests for the
        capability are refused until a

        version of its policy is approved.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/approve-policy-change-request.schema"
            examples:
              stepUp:
                summary: Real captured request
                value:
                  stepUpAuthorization:
                    payload:
                      version: 1
                      nonce: a11a41c1-651e-431e-82ef-5199791a6428
                      pendingPolicyChangeId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
                      action: approve
                      authorizedAt: 2026-09-30T17:25:46.377Z
                      expiresAt: 2026-09-30T17:27:46.377Z
                    signature: f7L+V1mwISUMWPZ85KqGJ2s1TaPfBiPHhwfsWjwhu2/1cSCJjP06MvgJ6eM6F01uR1T6/qT2FcHPpD/ETa+bDA==
                    keyId: human-checker-step-up-key
                    algorithm: ed25519
      responses:
        "200":
          description: Approved and applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/external-connector-change-response.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    changeId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
                    action: register
                    capability: erp:create-invoice
                    endpointUrl: https://erp.example.com/parmana/release
                    policy: erp-invoice
                    allowedParameters:
                      - amount
                      - currency
                      - customerId
                    timeoutMs: 10000
                    reason: Finance creates invoices in the ERP through Parmana from October.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T17:25:46.316Z
                    status: APPROVED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-30T17:25:46.385Z
                revokeApproved:
                  summary: Real captured response, a revoke
                  value:
                    changeId: d1164799-36e9-4281-878a-93d666befd42
                    action: revoke
                    capability: erp:create-invoice
                    reason: The ERP endpoint moves to a new host.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T19:03:21.551Z
                    status: APPROVED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-30T19:03:21.554Z
        "400":
          description: "`EXTERNAL_ENDPOINT_ADDRESS_REFUSED`: the endpoint now resolves to
            an address that is not public, or does not resolve. The change stays
            pending; fix the DNS and approve with a new step up authorization,
            or reject it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                addressChanged:
                  summary: Real captured response, the host moved to a private address after the
                    proposal
                  value:
                    error: The endpoint host 'crm.example.com' resolves to an address that is not
                      public (10.0.0.7).
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`. Sign a new step up authorization
            for this change and action."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response
                  value:
                    error: External connector change '1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37' was
                      proposed by this same caller — the proposer (maker) may
                      not also approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
        "404":
          description: "`EXTERNAL_CONNECTOR_CHANGE_NOT_FOUND`: no external connector
            change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: External connector change '00000000-0000-0000-0000-000000000000' not
                      found.
                    code: EXTERNAL_CONNECTOR_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved, the capability now has
            an active registration (register) or no longer has one (revoke).
            Nothing was written."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response, the same change approved twice
                  value:
                    error: External connector change 'd1164799-36e9-4281-878a-93d666befd42' is
                      already APPROVED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action approve > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/external-connectors/changes/ba7c5827-5844-4069-94fc-9b438ef08f78/approve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "approve",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.approveExternalConnectorChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="approve",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.approve_external_connector_change(
                "ba7c5827-5844-4069-94fc-9b438ef08f78", step_up
            )

            print(change.status)
  /external-connectors/changes/{id}/reject:
    post:
      tags:
        - External Connectors
      summary: Reject an external connector change
      operationId: rejectExternalConnectorChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rejects an external connector change. No registration changes. The same
        checks as approve, with a

        step up authorization for action `reject` and a `rejectionReason`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/reject-policy-change-request.schema"
      responses:
        "200":
          description: Rejected. No registration changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/external-connector-change-response.schema"
              examples:
                rejected:
                  summary: Real captured response
                  value:
                    changeId: 5bdbfd6a-babf-4977-b090-d561ddcee0d7
                    action: revoke
                    capability: erp:create-invoice
                    reason: The ERP endpoint moves to a new host.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T17:25:46.418Z
                    status: REJECTED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-30T17:25:46.439Z
                    rejectionReason: Keep the current endpoint until the new host is live.
                rejectedRegister:
                  summary: Real captured response, a register
                  value:
                    changeId: 1b618590-af7a-4196-8f8b-a4344873e2d6
                    action: register
                    capability: crm:create-lead
                    endpointUrl: https://crm.example.com/parmana/release
                    policy: crm-lead
                    allowedParameters:
                      - email
                      - company
                    timeoutMs: 10000
                    reason: Sales creates leads in the CRM through Parmana.
                    proposedBy: human-maker
                    proposedAt: 2026-09-30T19:03:21.542Z
                    status: REJECTED
                    resolvedBy: human-checker
                    resolvedAt: 2026-09-30T19:03:21.549Z
                    rejectionReason: The CRM host moved inside the office network.
        "400":
          description: "`rejectionReason` is missing, empty or over 2000 characters."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                emptyReason:
                  summary: Real captured response
                  value:
                    error: rejectionReason is required, at most 2000 characters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`, as on approve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response, the maker rejecting their own change
                  value:
                    error: External connector change 'f25d72fd-c6da-4223-b614-3658af8d02d2' was
                      proposed by this same caller — the proposer (maker) may
                      not also approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
        "404":
          description: "`EXTERNAL_CONNECTOR_CHANGE_NOT_FOUND`: no external connector
            change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownChange:
                  summary: Real captured response
                  value:
                    error: External connector change '7d5f8a52-0000-4000-8000-000000000000' not
                      found.
                    code: EXTERNAL_CONNECTOR_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response
                  value:
                    error: External connector change 'f25d72fd-c6da-4223-b614-3658af8d02d2' is
                      already REJECTED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id ba7c5827-5844-4069-94fc-9b438ef08f78 --action reject > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/external-connectors/changes/ba7c5827-5844-4069-94fc-9b438ef08f78/reject \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"rejectionReason\": \"The ERP team has not reviewed the endpoint yet.\", \"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "ba7c5827-5844-4069-94fc-9b438ef08f78",
              action: "reject",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.rejectExternalConnectorChange(
              "ba7c5827-5844-4069-94fc-9b438ef08f78",
              "Use the finance team's own endpoint.",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="ba7c5827-5844-4069-94fc-9b438ef08f78",
                action="reject",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.reject_external_connector_change(
                "ba7c5827-5844-4069-94fc-9b438ef08f78",
                "Use the finance team's own endpoint.",
                step_up,
            )

            print(change.status)
  /business-signal-sources:
    get:
      tags:
        - Business Signal Sources
      summary: List business signal sources
      operationId: listBusinessSignalSources
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists every business signal source registration, active and revoked: the
        name a policy's

        `signalSources` uses, the HTTPS endpoint Parmana asks, and the Ed25519
        key its answers must be

        signed with, if one is registered. Revoked registrations stay listed, so
        every Trusted Signal ever

        recorded can be traced to the registration that produced it. Needs a
        human key. See

        [Connect a business system as a signal
        source](/guides/connect-a-business-signal-source).
      responses:
        "200":
          description: The registrations.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/business-signal-sources-list-response.schema"
              examples:
                active:
                  summary: Real captured response
                  value:
                    sources:
                      - registrationId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                        name: erp
                        endpointUrl: https://orders.example.com/parmana/signals
                        timeoutMs: 5000
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                          -----END PUBLIC KEY-----
                        keyId: erp-2026-10
                        status: active
                        registeredAt: 2026-10-11T02:58:24.745Z
                withRevoked:
                  summary: "Real captured response, after a revoke: the registration stays listed"
                  value:
                    sources:
                      - registrationId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                        name: erp
                        endpointUrl: https://orders.example.com/parmana/signals
                        timeoutMs: 5000
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                          -----END PUBLIC KEY-----
                        keyId: erp-2026-10
                        status: revoked
                        registeredAt: 2026-10-11T02:58:24.745Z
                        revokedByChangeId: 92ba551c-02bb-4089-9762-6d2d3a89392e
                        revokedAt: 2026-10-11T02:58:24.800Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/business-signal-sources \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            for (const source of await client.businessSignalSources()) {
              console.log(source.name, source.status, source.endpointUrl);
            }
        - lang: python
          label: Python
          source: |-
            import os

            from parmana import ParmanaClient

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            for source in client.list_business_signal_sources():
                print(source.name, source.status, source.endpoint_url)
  /business-signal-sources/changes:
    post:
      tags:
        - Business Signal Sources
      summary: Propose registering or revoking a business signal source
      operationId: proposeBusinessSignalSourceChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Proposes connecting a business system, any system that owns facts a
        policy needs (an ERP, an

        order system, a CRM, a ledger), so Parmana can ask it instead of
        trusting the agent's claim.

        `register` names the source, the HTTPS endpoint Parmana asks, optionally
        a timeout, and optionally

        the Ed25519 public key the source signs its answers with; `revoke` ends
        the active registration

        for a name. Nothing changes until a different person approves it. The
        endpoint must be `https`,

        name a domain (not an IP address or localhost), carry no user name,
        password or fragment, and

        resolve only to public addresses; the address is checked again on
        approval and at every query.

        To move a source, revoke it, then register the new endpoint. Needs a
        human key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/propose-business-signal-source-change-request.schem\
                a"
            examples:
              register:
                summary: Real captured request
                value:
                  action: register
                  name: erp
                  endpointUrl: https://orders.example.com/parmana/signals
                  timeoutMs: 5000
                  publicKeyPem: |
                    -----BEGIN PUBLIC KEY-----
                    MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                    -----END PUBLIC KEY-----
                  keyId: erp-2026-10
                  reason: Release goods only against invoices the ERP says are paid.
              revoke:
                summary: Real captured request
                value:
                  action: revoke
                  name: erp
                  reason: The ERP moves to a new host.
      responses:
        "201":
          description: Proposed and waiting for a checker. Keep the `changeId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/business-signal-source-change-response.schema"
              examples:
                register:
                  summary: Real captured response
                  value:
                    changeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                    action: register
                    name: erp
                    endpointUrl: https://orders.example.com/parmana/signals
                    timeoutMs: 5000
                    publicKeyPem: |
                      -----BEGIN PUBLIC KEY-----
                      MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                      -----END PUBLIC KEY-----
                    keyId: erp-2026-10
                    reason: Release goods only against invoices the ERP says are paid.
                    proposedBy: source-maker
                    proposedAt: 2026-10-11T02:58:24.725Z
                    status: PENDING_APPROVAL
                revoke:
                  summary: Real captured response
                  value:
                    changeId: 48579135-1b91-4ac1-ade4-245e7ca304a3
                    action: revoke
                    name: erp
                    reason: The ERP moves to a new host.
                    proposedBy: source-maker
                    proposedAt: 2026-10-11T02:58:24.789Z
                    status: PENDING_APPROVAL
        "400":
          description: "`INVALID_BUSINESS_SIGNAL_SOURCE`: a field is missing or malformed,
            the key is not Ed25519, a keyId comes without a key, or a revoke
            carries registration fields. `EXTERNAL_ENDPOINT_ADDRESS_REFUSED`:
            the endpoint is not https, names an IP address or localhost,
            resolves to an address that is not public, or does not resolve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badName:
                  summary: Real captured response
                  value:
                    error: name must match ^[a-z0-9][a-z0-9-]{0,62}$, the form a policy's
                      signalSources uses.
                    code: INVALID_BUSINESS_SIGNAL_SOURCE
                notEd25519:
                  summary: Real captured response
                  value:
                    error: publicKeyPem must be an Ed25519 key.
                    code: INVALID_BUSINESS_SIGNAL_SOURCE
                keyIdWithoutKey:
                  summary: Real captured response
                  value:
                    error: keyId labels a publicKeyPem; give the key too, or leave keyId out.
                    code: INVALID_BUSINESS_SIGNAL_SOURCE
                badTimeout:
                  summary: Real captured response
                  value:
                    error: timeoutMs must be an integer from 1000 to 30000; it defaults to 10000.
                    code: INVALID_BUSINESS_SIGNAL_SOURCE
                revokeWithRegistrationFields:
                  summary: Real captured response
                  value:
                    error: A revoke takes only action, name and reason.
                    code: INVALID_BUSINESS_SIGNAL_SOURCE
                plainHttp:
                  summary: Real captured response
                  value:
                    error: endpointUrl must use https.
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
                privateAddress:
                  summary: Real captured response
                  value:
                    error: The endpoint host 'internal.example.com' resolves to an address that is
                      not public (10.0.0.7).
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "409":
          description: "`CONFLICT`: the name already has an active registration (to
            register) or has none (to revoke), or another change for it is
            pending."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    error: A change for business signal source 'erp' is already pending. It must be
                      approved or rejected first.
                    code: CONFLICT
                alreadyActive:
                  summary: Real captured response
                  value:
                    error: Business signal source 'erp' is already registered. Revoke it first.
                    code: CONFLICT
                revokeUnregistered:
                  summary: Real captured response
                  value:
                    error: Business signal source 'crm' is not registered.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            # erp-answers.public.pem: the Ed25519 public key the ERP signs its
            answers with.

            curl -X POST
            https://parmana-api-real.vercel.app/business-signal-sources/changes
            \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "$(jq -n --rawfile key erp-answers.public.pem '{
                action: "register",
                name: "erp",
                endpointUrl: "https://orders.example.com/parmana/signals",
                timeoutMs: 5000,
                publicKeyPem: $key,
                keyId: "erp-2026-10",
                reason: "Release goods only against invoices the ERP says are paid."
              }')"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const change = await client.proposeBusinessSignalSourceChange({
              action: "register",
              name: "erp",
              endpointUrl: "https://orders.example.com/parmana/signals",
              timeoutMs: 5000,
              publicKeyPem: readFileSync("erp-answers.public.pem", "utf8"),
              keyId: "erp-2026-10",
              reason: "Release goods only against invoices the ERP says are paid.",
            });


            console.log(change.changeId);
        - lang: python
          label: Python
          source: >-
            import os

            from pathlib import Path


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            change = client.business_signal_sources.propose_register(
                name="erp",
                endpoint_url="https://orders.example.com/parmana/signals",
                timeout_ms=5000,
                public_key_pem=Path("erp-answers.public.pem").read_text(),
                key_id="erp-2026-10",
                reason="Release goods only against invoices the ERP says are paid.",
            )


            print(change.change_id)
    get:
      tags:
        - Business Signal Sources
      summary: List business signal source changes
      operationId: listBusinessSignalSourceChanges
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists business signal source changes, newest first. A checker uses
        `status=PENDING_APPROVAL` to

        find what waits for review. Needs a human key.
      parameters:
        - name: status
          description: "`PENDING_APPROVAL`, `APPROVED` or `REJECTED`. Omit for all."
          in: query
          required: false
          schema:
            type: string
            enum:
              - PENDING_APPROVAL
              - APPROVED
              - REJECTED
      responses:
        "200":
          description: The changes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/business-signal-source-changes-list-response.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    changes:
                      - changeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                        action: register
                        name: erp
                        endpointUrl: https://orders.example.com/parmana/signals
                        timeoutMs: 5000
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                          -----END PUBLIC KEY-----
                        keyId: erp-2026-10
                        reason: Release goods only against invoices the ERP says are paid.
                        proposedBy: source-maker
                        proposedAt: 2026-10-11T02:58:24.725Z
                        status: PENDING_APPROVAL
                approved:
                  summary: "Real captured response, status=APPROVED: a register and the revoke
                    that ended it"
                  value:
                    changes:
                      - changeId: 92ba551c-02bb-4089-9762-6d2d3a89392e
                        action: revoke
                        name: erp
                        reason: The ERP moves to a new host.
                        proposedBy: source-maker
                        proposedAt: 2026-10-11T02:58:24.798Z
                        status: APPROVED
                        resolvedBy: source-checker
                        resolvedAt: 2026-10-11T02:58:24.800Z
                      - changeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                        action: register
                        name: erp
                        endpointUrl: https://orders.example.com/parmana/signals
                        timeoutMs: 5000
                        publicKeyPem: |
                          -----BEGIN PUBLIC KEY-----
                          MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                          -----END PUBLIC KEY-----
                        keyId: erp-2026-10
                        reason: Release goods only against invoices the ERP says are paid.
                        proposedBy: source-maker
                        proposedAt: 2026-10-11T02:58:24.725Z
                        status: APPROVED
                        resolvedBy: source-checker
                        resolvedAt: 2026-10-11T02:58:24.745Z
        "400":
          description: "`status` is not `PENDING_APPROVAL`, `APPROVED` or `REJECTED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badStatus:
                  summary: Real captured response
                  value:
                    error: status must be one of PENDING_APPROVAL, APPROVED, REJECTED.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/business-signal-sources/changes?status=PENDING_APPROVAL" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: |-
            import { ParmanaClient } from "@parmana/sdk";

            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });

            for (const change of await client.businessSignalSourceChanges(
              "PENDING_APPROVAL",
            )) {
              console.log(change.changeId, change.action, change.name);
            }
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            for change in
            client.business_signal_source_changes("PENDING_APPROVAL"):
                print(change.change_id, change.action, change.name)
  /business-signal-sources/changes/{id}/approve:
    post:
      tags:
        - Business Signal Sources
      summary: Approve a business signal source change
      operationId: approveBusinessSignalSourceChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Approves a business signal source change and applies it at once:
        `register` makes the

        registration active, so policies that name it can be evaluated; `revoke`
        ends it, so they are

        refused as `SOURCE_UNAVAILABLE`. For a register, the endpoint's address
        is checked again first.

        Checked in order: a human key, not the proposer, and a step up
        authorization for this change id

        and action `approve`, valid once, with the change id in
        `payload.pendingPolicyChangeId`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/approve-policy-change-request.schema"
            examples:
              stepUp:
                summary: Real captured request
                value:
                  stepUpAuthorization:
                    payload:
                      version: 1
                      nonce: 7fd4e45b-1bda-4224-83d0-c8676cff2180
                      pendingPolicyChangeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                      action: approve
                      authorizedAt: 2026-10-11T02:58:24.742Z
                      expiresAt: 2026-10-11T03:00:24.742Z
                    signature: hP3qDu2ZHNasz1zFZBHlkxqTKT6wiP6IHgFishmbTM7P56w7Jwel5DSx8xSvxynFDDO0sART/xxFxVFFBx8hAw==
                    keyId: checker-step-up-key
                    algorithm: ed25519
      responses:
        "200":
          description: Approved and applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/business-signal-source-change-response.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    changeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
                    action: register
                    name: erp
                    endpointUrl: https://orders.example.com/parmana/signals
                    timeoutMs: 5000
                    publicKeyPem: |
                      -----BEGIN PUBLIC KEY-----
                      MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
                      -----END PUBLIC KEY-----
                    keyId: erp-2026-10
                    reason: Release goods only against invoices the ERP says are paid.
                    proposedBy: source-maker
                    proposedAt: 2026-10-11T02:58:24.725Z
                    status: APPROVED
                    resolvedBy: source-checker
                    resolvedAt: 2026-10-11T02:58:24.745Z
                revokeApproved:
                  summary: Real captured response, a revoke
                  value:
                    changeId: 92ba551c-02bb-4089-9762-6d2d3a89392e
                    action: revoke
                    name: erp
                    reason: The ERP moves to a new host.
                    proposedBy: source-maker
                    proposedAt: 2026-10-11T02:58:24.798Z
                    status: APPROVED
                    resolvedBy: source-checker
                    resolvedAt: 2026-10-11T02:58:24.800Z
        "400":
          description: "`EXTERNAL_ENDPOINT_ADDRESS_REFUSED`: the endpoint now resolves to
            an address that is not public, or does not resolve. The change stays
            pending; fix the DNS and approve with a new step up authorization,
            or reject it."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                addressChanged:
                  summary: Real captured response, the host moved to a private address after the
                    proposal
                  value:
                    error: The endpoint host 'moving.example.com' resolves to an address that is not
                      public (10.0.0.7).
                    code: EXTERNAL_ENDPOINT_ADDRESS_REFUSED
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`. Sign a new step up authorization
            for this change and action."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response
                  value:
                    error: Business signal source change '5ba7d3a6-d56d-4e90-b588-402d44aeee0c' was
                      proposed by this same caller — the proposer (maker) may
                      not also approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
        "404":
          description: "`BUSINESS_SIGNAL_SOURCE_CHANGE_NOT_FOUND`: no business signal
            source change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Business signal source change '00000000-0000-0000-0000-000000000000' not
                      found.
                    code: BUSINESS_SIGNAL_SOURCE_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved, the name now has an
            active registration (register) or no longer has one (revoke).
            Nothing was written."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response, the same change approved twice
                  value:
                    error: Business signal source change '5ba7d3a6-d56d-4e90-b588-402d44aeee0c' is
                      already APPROVED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id 5ba7d3a6-d56d-4e90-b588-402d44aeee0c --action approve > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/business-signal-sources/changes/5ba7d3a6-d56d-4e90-b588-402d44aeee0c/approve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
              action: "approve",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.approveBusinessSignalSourceChange(
              "5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
                action="approve",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.approve_business_signal_source_change(
                "5ba7d3a6-d56d-4e90-b588-402d44aeee0c", step_up
            )

            print(change.status)
  /business-signal-sources/changes/{id}/reject:
    post:
      tags:
        - Business Signal Sources
      summary: Reject a business signal source change
      operationId: rejectBusinessSignalSourceChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rejects a business signal source change. No registration changes. The
        same checks as approve,

        with a step up authorization for action `reject` and a
        `rejectionReason`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/reject-policy-change-request.schema"
      responses:
        "200":
          description: Rejected. No registration changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/business-signal-source-change-response.schema"
              examples:
                rejected:
                  summary: Real captured response
                  value:
                    changeId: 48579135-1b91-4ac1-ade4-245e7ca304a3
                    action: revoke
                    name: erp
                    reason: The ERP moves to a new host.
                    proposedBy: source-maker
                    proposedAt: 2026-10-11T02:58:24.789Z
                    status: REJECTED
                    resolvedBy: source-checker
                    resolvedAt: 2026-10-11T02:58:24.792Z
                    rejectionReason: Keep the current host until the new one is live.
        "400":
          description: "`rejectionReason` is missing, empty or over 2000 characters."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                emptyReason:
                  summary: Real captured response
                  value:
                    error: rejectionReason is required, at most 2000 characters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`
            or `STEP_UP_AUTHORIZATION_INVALID`, as on approve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "404":
          description: "`BUSINESS_SIGNAL_SOURCE_CHANGE_NOT_FOUND`: no business signal
            source change has this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownChange:
                  summary: Real captured response
                  value:
                    error: Business signal source change '00000000-0000-0000-0000-000000000000' not
                      found.
                    code: BUSINESS_SIGNAL_SOURCE_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response
                  value:
                    error: Business signal source change '48579135-1b91-4ac1-ade4-245e7ca304a3' is
                      already REJECTED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id 5ba7d3a6-d56d-4e90-b588-402d44aeee0c --action reject > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/business-signal-sources/changes/5ba7d3a6-d56d-4e90-b588-402d44aeee0c/reject \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"rejectionReason\": \"Register the ERP with its signing key.\", \"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
              action: "reject",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.rejectBusinessSignalSourceChange(
              "5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
              "Register the ERP with its signing key.",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
                action="reject",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.reject_business_signal_source_change(
                "5ba7d3a6-d56d-4e90-b588-402d44aeee0c",
                "Register the ERP with its signing key.",
                step_up,
            )

            print(change.status)
  /authority-grants:
    get:
      tags:
        - Authority Grants
      summary: List authority grants
      operationId: listAuthorityGrants
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists every authority grant, active and revoked: which caller may have
        which capability decided,

        within which limits, from when until when. A grant matters only under a
        policy with

        `requireAuthorityGrant`. Revoked grants stay listed, so every decision
        that rested on one can be

        explained. Needs a human key. See [Grant an agent
        authority](/guides/grant-an-agent-authority).
      responses:
        "200":
          description: The grants.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/authority-grants-list-response.schema"
              examples:
                active:
                  summary: Real captured response
                  value:
                    grants:
                      - grantId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                        callerId: warehouse-agent
                        capability: erp:release-goods
                        limits:
                          parameters.amount:
                            max: 100000
                          parameters.currency:
                            oneOf:
                              - INR
                        validFrom: 2026-10-11T03:29:05.247Z
                        validUntil: 2026-12-30T00:00:00.000Z
                        status: active
                        grantedAt: 2026-10-11T03:29:05.247Z
                withRevoked:
                  summary: "Real captured response, after a revoke: the grant stays listed"
                  value:
                    grants:
                      - grantId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                        callerId: warehouse-agent
                        capability: erp:release-goods
                        limits:
                          parameters.amount:
                            max: 100000
                          parameters.currency:
                            oneOf:
                              - INR
                        validFrom: 2026-10-11T03:29:05.247Z
                        validUntil: 2026-12-30T00:00:00.000Z
                        status: revoked
                        grantedAt: 2026-10-11T03:29:05.247Z
                        revokedByChangeId: 77a3b517-4ae0-4c72-8f0c-5aa6ed913df1
                        revokedAt: 2026-10-11T03:29:05.300Z
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://parmana-api-real.vercel.app/authority-grants \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            for (const grant of await client.authorityGrants()) {
              console.log(grant.callerId, grant.capability, grant.status, grant.validUntil);
            }
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            for grant in client.list_authority_grants():
                print(grant.caller_id, grant.capability, grant.status, grant.valid_until)
  /authority-grants/changes:
    post:
      tags:
        - Authority Grants
      summary: Propose granting or revoking an agent's authority
      operationId: proposeAuthorityGrantChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Proposes per-agent authority (RFC-0023 phase 3): `grant` says the API
        key with `callerId` may have

        requests for `capability` decided, within `limits` on the request, until
        `validUntil` (at most 366

        days); `revoke` ends the active grant. Nothing changes until a different
        person approves it. You

        may not propose authority for your own key. Under a policy with
        `requireAuthorityGrant`, a request

        from a key with no grant in force, or outside its limits, is refused as
        `NOT_AUTHORIZED`, and one

        after `validUntil` as `AUTHORITY_EXPIRED`. To change a grant, revoke it,
        then grant again. Needs a

        human key.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/propose-authority-grant-change-request.schema"
            examples:
              grant:
                summary: Real captured request
                value:
                  action: grant
                  callerId: warehouse-agent
                  capability: erp:release-goods
                  limits:
                    parameters.amount:
                      max: 100000
                    parameters.currency:
                      oneOf:
                        - INR
                  validUntil: 2026-12-30T00:00:00.000Z
                  reason: The warehouse agent releases goods for paid invoices up to 100000 INR
                    this quarter.
              revoke:
                summary: Real captured request
                value:
                  action: revoke
                  callerId: warehouse-agent
                  capability: erp:release-goods
                  reason: The warehouse agent's key is being rotated.
      responses:
        "201":
          description: Proposed and waiting for a checker. Keep the `changeId`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/authority-grant-change-response.schema"
              examples:
                grant:
                  summary: Real captured response
                  value:
                    changeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                    action: grant
                    callerId: warehouse-agent
                    capability: erp:release-goods
                    limits:
                      parameters.amount:
                        max: 100000
                      parameters.currency:
                        oneOf:
                          - INR
                    validUntil: 2026-12-30T00:00:00.000Z
                    reason: The warehouse agent releases goods for paid invoices up to 100000 INR
                      this quarter.
                    proposedBy: grant-maker
                    proposedAt: 2026-10-11T03:29:05.220Z
                    status: PENDING_APPROVAL
                revoke:
                  summary: Real captured response
                  value:
                    changeId: c7955228-f718-4f21-8ac9-c48ce907b91a
                    action: revoke
                    callerId: warehouse-agent
                    capability: erp:release-goods
                    reason: The warehouse agent's key is being rotated.
                    proposedBy: grant-maker
                    proposedAt: 2026-10-11T03:29:05.283Z
                    status: PENDING_APPROVAL
        "400":
          description: "`INVALID_AUTHORITY_GRANT`: a field is missing or malformed, a
            limit is malformed, validUntil is missing, past, before validFrom or
            more than 366 days away, or a revoke carries grant fields."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badCapability:
                  summary: Real captured response
                  value:
                    error: capability must be namespace:verb, at most 128 characters, matching
                      ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$.
                    code: INVALID_AUTHORITY_GRANT
                badLimitPath:
                  summary: Real captured response
                  value:
                    error: limits key 'amount' must be target or parameters.<name>.
                    code: INVALID_AUTHORITY_GRANT
                badLimit:
                  summary: Real captured response
                  value:
                    error: limits['parameters.amount'].min must not be above its max.
                    code: INVALID_AUTHORITY_GRANT
                noExpiry:
                  summary: Real captured response
                  value:
                    error: validUntil must be an ISO 8601 date and time.
                    code: INVALID_AUTHORITY_GRANT
                tooLong:
                  summary: Real captured response
                  value:
                    error: A grant may last at most 366 days. Grant again when it ends.
                    code: INVALID_AUTHORITY_GRANT
                revokeWithGrantFields:
                  summary: Real captured response
                  value:
                    error: A revoke takes only action, callerId, capability and reason.
                    code: INVALID_AUTHORITY_GRANT
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key.
            `AUTHORITY_SELF_GRANT_DENIED`: the grant is for your own key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
                selfGrant:
                  summary: Real captured response
                  value:
                    error: "'grant-maker' may not propose, approve or reject authority for itself."
                    code: AUTHORITY_SELF_GRANT_DENIED
        "409":
          description: "`CONFLICT`: the caller already holds a grant for the capability
            (to grant) or holds none (to revoke), or another change for it is
            pending."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    error: A change for the authority of 'warehouse-agent' over 'erp:release-goods'
                      is already pending. It must be approved or rejected first.
                    code: CONFLICT
                alreadyActive:
                  summary: Real captured response
                  value:
                    error: "'warehouse-agent' already holds a grant for 'erp:release-goods'. Revoke
                      it first."
                    code: CONFLICT
                revokeUnknown:
                  summary: Real captured response
                  value:
                    error: "'x-agent' holds no grant for 'erp:release-goods'."
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X POST
            https://parmana-api-real.vercel.app/authority-grants/changes \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "action": "grant",
                "callerId": "warehouse-agent",
                "capability": "erp:release-goods",
                "limits": {
                  "parameters.amount": { "max": 100000 },
                  "parameters.currency": { "oneOf": ["INR"] }
                },
                "validUntil": "2026-12-30T00:00:00.000Z",
                "reason": "The warehouse agent releases goods for paid invoices up to 100000 INR this quarter."
              }'
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const change = await client.proposeAuthorityGrantChange({
              action: "grant",
              callerId: "warehouse-agent",
              capability: "erp:release-goods",
              limits: {
                "parameters.amount": { max: 100000 },
                "parameters.currency": { oneOf: ["INR"] },
              },
              validUntil: "2026-12-30T00:00:00.000Z",
              reason:
                "The warehouse agent releases goods for paid invoices up to 100000 INR this quarter.",
            });


            console.log(change.changeId);
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            change = client.authority_grants.propose_grant(
                caller_id="warehouse-agent",
                capability="erp:release-goods",
                limits={
                    "parameters.amount": {"max": 100000},
                    "parameters.currency": {"oneOf": ["INR"]},
                },
                valid_until="2026-12-30T00:00:00.000Z",
                reason=(
                    "The warehouse agent releases goods for paid invoices up to 100000 INR "
                    "this quarter."
                ),
            )


            print(change.change_id)
    get:
      tags:
        - Authority Grants
      summary: List authority grant changes
      operationId: listAuthorityGrantChanges
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Lists authority grant changes, newest first. A checker uses
        `status=PENDING_APPROVAL` to find what

        waits for review. Needs a human key.
      parameters:
        - name: status
          description: "`PENDING_APPROVAL`, `APPROVED` or `REJECTED`. Omit for all."
          in: query
          required: false
          schema:
            type: string
            enum:
              - PENDING_APPROVAL
              - APPROVED
              - REJECTED
      responses:
        "200":
          description: The changes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/authority-grant-changes-list-response.schema"
              examples:
                pending:
                  summary: Real captured response
                  value:
                    changes:
                      - changeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                        action: grant
                        callerId: warehouse-agent
                        capability: erp:release-goods
                        limits:
                          parameters.amount:
                            max: 100000
                          parameters.currency:
                            oneOf:
                              - INR
                        validUntil: 2026-12-30T00:00:00.000Z
                        reason: The warehouse agent releases goods for paid invoices up to 100000 INR
                          this quarter.
                        proposedBy: grant-maker
                        proposedAt: 2026-10-11T03:29:05.220Z
                        status: PENDING_APPROVAL
                approved:
                  summary: "Real captured response, status=APPROVED: a grant and the revoke that
                    ended it"
                  value:
                    changes:
                      - changeId: 77a3b517-4ae0-4c72-8f0c-5aa6ed913df1
                        action: revoke
                        callerId: warehouse-agent
                        capability: erp:release-goods
                        reason: The warehouse agent's key is being rotated.
                        proposedBy: grant-maker
                        proposedAt: 2026-10-11T03:29:05.296Z
                        status: APPROVED
                        resolvedBy: grant-checker
                        resolvedAt: 2026-10-11T03:29:05.300Z
                      - changeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                        action: grant
                        callerId: warehouse-agent
                        capability: erp:release-goods
                        limits:
                          parameters.amount:
                            max: 100000
                          parameters.currency:
                            oneOf:
                              - INR
                        validUntil: 2026-12-30T00:00:00.000Z
                        reason: The warehouse agent releases goods for paid invoices up to 100000 INR
                          this quarter.
                        proposedBy: grant-maker
                        proposedAt: 2026-10-11T03:29:05.220Z
                        status: APPROVED
                        resolvedBy: grant-checker
                        resolvedAt: 2026-10-11T03:29:05.247Z
        "400":
          description: "`status` is not `PENDING_APPROVAL`, `APPROVED` or `REJECTED`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                badStatus:
                  summary: Real captured response
                  value:
                    error: status must be one of PENDING_APPROVAL, APPROVED, REJECTED.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`: this needs a human key."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl "https://parmana-api-real.vercel.app/authority-grants/changes?status=PENDING_APPROVAL" \
              -H "Authorization: Bearer $PARMANA_API_KEY"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient } from "@parmana/sdk";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            for (const change of await
            client.authorityGrantChanges("PENDING_APPROVAL")) {
              console.log(
                change.changeId,
                change.action,
                change.callerId,
                change.capability,
              );
            }
        - lang: python
          label: Python
          source: >-
            import os


            from parmana import ParmanaClient


            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )


            for change in client.authority_grant_changes("PENDING_APPROVAL"):
                print(change.change_id, change.action, change.caller_id, change.capability)
  /authority-grants/changes/{id}/approve:
    post:
      tags:
        - Authority Grants
      summary: Approve an authority grant change
      operationId: approveAuthorityGrantChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Approves an authority grant change and applies it at once: `grant` makes
        the grant active,

        starting now unless it names a later `validFrom`; `revoke` ends it, so
        the caller's next request

        under a policy with `requireAuthorityGrant` is refused. A grant whose
        `validUntil` has passed is

        refused. Checked in order: a human key, not the proposer, not the
        grantee, and a step up

        authorization for this change id and action `approve`, valid once, with
        the change id in

        `payload.pendingPolicyChangeId`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/approve-policy-change-request.schema"
            examples:
              stepUp:
                summary: Real captured request
                value:
                  stepUpAuthorization:
                    payload:
                      version: 1
                      nonce: 3cc39c28-9ed5-4e7d-ae2e-3f3c2bbc06ba
                      pendingPolicyChangeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                      action: approve
                      authorizedAt: 2026-10-11T03:29:05.244Z
                      expiresAt: 2026-10-11T03:31:05.244Z
                    signature: HCrlwJPXLRR4YNa1RAW7f6mq46l0gNoqPt52vqvIzQv2NWg5EaCuqO0gAkDOgTL6nbUsTy8E0gpyf5PyKVI6Ag==
                    keyId: checker-step-up-key
                    algorithm: ed25519
      responses:
        "200":
          description: Approved and applied.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/authority-grant-change-response.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    changeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
                    action: grant
                    callerId: warehouse-agent
                    capability: erp:release-goods
                    limits:
                      parameters.amount:
                        max: 100000
                      parameters.currency:
                        oneOf:
                          - INR
                    validUntil: 2026-12-30T00:00:00.000Z
                    reason: The warehouse agent releases goods for paid invoices up to 100000 INR
                      this quarter.
                    proposedBy: grant-maker
                    proposedAt: 2026-10-11T03:29:05.220Z
                    status: APPROVED
                    resolvedBy: grant-checker
                    resolvedAt: 2026-10-11T03:29:05.247Z
                revokeApproved:
                  summary: Real captured response, a revoke
                  value:
                    changeId: 77a3b517-4ae0-4c72-8f0c-5aa6ed913df1
                    action: revoke
                    callerId: warehouse-agent
                    capability: erp:release-goods
                    reason: The warehouse agent's key is being rotated.
                    proposedBy: grant-maker
                    proposedAt: 2026-10-11T03:29:05.296Z
                    status: APPROVED
                    resolvedBy: grant-checker
                    resolvedAt: 2026-10-11T03:29:05.300Z
        "400":
          description: "`INVALID_AUTHORITY_GRANT`: the grant's `validUntil` has passed.
            Reject it and propose a new one."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                expired:
                  summary: Real captured response, a grant whose validUntil passed while it waited
                  value:
                    error: This grant's validUntil has passed. Reject it and propose a new one.
                    code: INVALID_AUTHORITY_GRANT
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`,
            `AUTHORITY_SELF_GRANT_DENIED` (you are the grantee) or
            `STEP_UP_AUTHORIZATION_INVALID`."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                sameActor:
                  summary: Real captured response
                  value:
                    error: Authority grant change '9f3cf733-a8ed-487c-bb58-a53950388cdf' was
                      proposed by this same caller — the proposer (maker) may
                      not also approve or reject it (checker).
                    code: SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE
                grantee:
                  summary: Real captured response, the grantee approving its own grant
                  value:
                    error: "'grant-checker' may not propose, approve or reject authority for
                      itself."
                    code: AUTHORITY_SELF_GRANT_DENIED
        "404":
          description: "`AUTHORITY_GRANT_CHANGE_NOT_FOUND`: no authority grant change has
            this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                notFound:
                  summary: Real captured response
                  value:
                    error: Authority grant change '00000000-0000-0000-0000-000000000000' not found.
                    code: AUTHORITY_GRANT_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved, or the caller now
            holds a grant (grant) or no longer does (revoke). Nothing was
            written."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response, the same change approved twice
                  value:
                    error: Authority grant change '9f3cf733-a8ed-487c-bb58-a53950388cdf' is already
                      APPROVED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id 9f3cf733-a8ed-487c-bb58-a53950388cdf --action approve > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/authority-grants/changes/9f3cf733-a8ed-487c-bb58-a53950388cdf/approve \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "9f3cf733-a8ed-487c-bb58-a53950388cdf",
              action: "approve",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.approveAuthorityGrantChange(
              "9f3cf733-a8ed-487c-bb58-a53950388cdf",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="9f3cf733-a8ed-487c-bb58-a53950388cdf",
                action="approve",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.approve_authority_grant_change(
                "9f3cf733-a8ed-487c-bb58-a53950388cdf", step_up
            )

            print(change.status)
  /authority-grants/changes/{id}/reject:
    post:
      tags:
        - Authority Grants
      summary: Reject an authority grant change
      operationId: rejectAuthorityGrantChange
      security:
        - operatorBearer: []
      servers:
        - url: https://parmana-api-real.vercel.app
          description: Production. The public sandbox refuses this route for its demo key,
            so use a key issued by the operator.
      description: >
        Rejects an authority grant change. No grant changes. The same checks as
        approve, with a step up

        authorization for action `reject` and a `rejectionReason`.
      parameters:
        - name: id
          in: path
          required: true
          description: The `changeId` from the proposal.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/reject-policy-change-request.schema"
      responses:
        "200":
          description: Rejected. No grant changed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/authority-grant-change-response.schema"
              examples:
                rejected:
                  summary: Real captured response
                  value:
                    changeId: c7955228-f718-4f21-8ac9-c48ce907b91a
                    action: revoke
                    callerId: warehouse-agent
                    capability: erp:release-goods
                    reason: The warehouse agent's key is being rotated.
                    proposedBy: grant-maker
                    proposedAt: 2026-10-11T03:29:05.283Z
                    status: REJECTED
                    resolvedBy: grant-checker
                    resolvedAt: 2026-10-11T03:29:05.285Z
                    rejectionReason: Keep the grant until the new key is live.
        "400":
          description: "`rejectionReason` is missing, empty or over 2000 characters."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                emptyReason:
                  summary: Real captured response
                  value:
                    error: rejectionReason is required, at most 2000 characters.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: "`NON_HUMAN_CALLER_DENIED`, `SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE`,
            `AUTHORITY_SELF_GRANT_DENIED` or `STEP_UP_AUTHORIZATION_INVALID`, as
            on approve."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                service:
                  summary: Real captured response, a service credential
                  value:
                    error: "This action requires a caller credential provisioned as a verified human
                      (credentialHolderType: USER)."
                    code: NON_HUMAN_CALLER_DENIED
        "404":
          description: "`AUTHORITY_GRANT_CHANGE_NOT_FOUND`: no authority grant change has
            this id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                unknownChange:
                  summary: Real captured response
                  value:
                    error: Authority grant change '00000000-0000-0000-0000-000000000000' not found.
                    code: AUTHORITY_GRANT_CHANGE_NOT_FOUND
        "409":
          description: "`CONFLICT`: the change is already resolved."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                alreadyResolved:
                  summary: Real captured response
                  value:
                    error: Authority grant change 'c7955228-f718-4f21-8ac9-c48ce907b91a' is already
                      REJECTED.
                    code: CONFLICT
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            # Sign the step up authorization on your own machine first:
            #   npx tsx scripts/sign-policy-change-step-up.ts --private-key-file step-up.private.pem \
            #     --key-id checker-step-up-1 --pending-policy-change-id 9f3cf733-a8ed-487c-bb58-a53950388cdf --action reject > step-up.json
            curl -X POST https://parmana-api-real.vercel.app/authority-grants/changes/9f3cf733-a8ed-487c-bb58-a53950388cdf/reject \
              -H "Authorization: Bearer $PARMANA_API_KEY" \
              -H "Content-Type: application/json" \
              --data "{\"rejectionReason\": \"Lower the limit to 50000 first.\", \"stepUpAuthorization\": $(cat step-up.json)}"
        - lang: typescript
          label: TypeScript
          source: >-
            import { ParmanaClient, signPolicyChangeStepUp } from
            "@parmana/sdk";

            import { readFileSync } from "node:fs";


            const client = new ParmanaClient({
              endpoint: "https://parmana-api-real.vercel.app",
              apiKey: process.env.PARMANA_API_KEY!,
            });


            const stepUp = signPolicyChangeStepUp({
              pendingPolicyChangeId: "9f3cf733-a8ed-487c-bb58-a53950388cdf",
              action: "reject",
              privateKeyPem: readFileSync("step-up.private.pem", "utf8"),
              keyId: "checker-step-up-1",
            });


            const change = await client.rejectAuthorityGrantChange(
              "9f3cf733-a8ed-487c-bb58-a53950388cdf",
              "Lower the limit to 50000 first.",
              stepUp,
            );


            console.log(change.status);
        - lang: python
          label: Python
          source: |-
            import os
            from pathlib import Path

            from parmana import ParmanaClient
            from parmana.crypto import sign_policy_change_step_up

            client = ParmanaClient(
                endpoint="https://parmana-api-real.vercel.app",
                api_key=os.environ["PARMANA_API_KEY"],
            )

            step_up = sign_policy_change_step_up(
                pending_policy_change_id="9f3cf733-a8ed-487c-bb58-a53950388cdf",
                action="reject",
                private_key_pem=Path("step-up.private.pem").read_text(),
                key_id="checker-step-up-1",
            )

            change = client.reject_authority_grant_change(
                "9f3cf733-a8ed-487c-bb58-a53950388cdf",
                "Lower the limit to 50000 first.",
                step_up,
            )

            print(change.status)
  /sandbox/approvals:
    post:
      tags:
        - Sandbox
      summary: Get a demo approval (sandbox only)
      operationId: createSandboxApproval
      description: >
        **Sandbox only.** Returns an approval signed by the sandbox's demo
        approver, so you can try an approved

        request from a browser. In production a person signs every approval with
        their own key on their own

        machine; this route does not exist there.


        It signs only for `sandbox:receipt`, for the one `resourceId` you name
        (the target of the request you will

        send), valid 5 minutes. Put it in your request's
        `signals.approvalArtifact`. `POST /execute` checks it like

        any other approval: a trusted approver key, the signature, the
        capability, the resource and the expiry, and

        it accepts it once. Needs the published demo key.


        Live at `https://parmana-sandbox.vercel.app`. The request that uses it
        carries

        `signals: { "note": "<the same text as parameters.note>",
        "receiptApproved": true, "approvalArtifact": <this response> }`,

        with `target` equal to the `resourceId` you named here. The
        [Playground](/playground) walks through every step.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/sandbox-approval-request.schema"
      responses:
        "201":
          description: The signed approval. Send it as `signals.approvalArtifact` within 5
            minutes; it is accepted once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/signed-approval.schema"
              examples:
                approved:
                  summary: Real captured response
                  value:
                    payload:
                      version: 1
                      approvalId: f328a67e-a527-4119-8617-1eadead9367d
                      issuer:
                        approverId: sandbox-demo-approver
                        keyId: sandbox-demo-approver-key-1
                      issuedAt: 2026-10-01T11:10:57.825Z
                      expiresAt: 2026-10-01T11:15:57.825Z
                      capability: sandbox:receipt
                      resourceId: demo-order-1
                      scope:
                        field: resourceId
                        comparator: eq
                        value: demo-order-1
                      nonce: 9c199e50-746d-409f-98f4-26c319af22ae
                    signature:
                      algorithm: ed25519
                      keyId: sandbox-demo-approver-key-1
                      value: H5Anvojj4J3325IkJnL/SD50WEDJxhv7AafGgkSauSyQKQ6y3Fw5d+ZoC0k2BUfDDJvR0jn1IDgZ/1I+nx6ZAQ==
                      signedAt: 2026-10-01T11:10:57.825Z
        "400":
          description: "`INVALID_SANDBOX_APPROVAL_REQUEST`: `capability` is not
            `sandbox:receipt`, or `resourceId` is missing, empty or over 200
            characters. Nothing was signed."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/error.schema"
              examples:
                otherCapability:
                  summary: Real captured response, capability paytm:refund
                  value:
                    error: The sandbox approver signs only for capability "sandbox:receipt".
                    code: INVALID_SANDBOX_APPROVAL_REQUEST
                missingResource:
                  summary: Real captured response, no resourceId
                  value:
                    error: "resourceId is required: the request's target, at most 200 characters."
                    code: INVALID_SANDBOX_APPROVAL_REQUEST
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: bash
          label: cURL
          source: >-
            curl -X POST
            https://parmana-sandbox.vercel.app/sandbox/approvals   -H
            "Authorization: Bearer $PARMANA_API_KEY"   -H "Content-Type:
            application/json"   -d '{
                "capability": "sandbox:receipt",
                "resourceId": "demo-order-1"
              }'
        - lang: typescript
          label: TypeScript
          source: >-
            // Sandbox only, and not in the SDK: a demo for trying Parmana, not
            part of

            // the product API. Call the route directly.

            const response = await fetch(
              "https://parmana-sandbox.vercel.app/sandbox/approvals",
              {
                method: "POST",
                headers: {
                  Authorization: `Bearer ${process.env.PARMANA_API_KEY}`,
                  "Content-Type": "application/json",
                },
                body: JSON.stringify({
                  capability: "sandbox:receipt",
                  resourceId: "demo-order-1",
                }),
              },
            );


            // Send it as signals.approvalArtifact within 5 minutes.

            const approval: unknown = await response.json();


            console.log(response.status, approval);
        - lang: python
          label: Python
          source: >-
            # Sandbox only, and not in the SDK: a demo for trying Parmana, not
            part of

            # the product API. Call the route directly.

            import os


            import requests


            response = requests.post(
                "https://parmana-sandbox.vercel.app/sandbox/approvals",
                headers={"Authorization": f"Bearer {os.environ['PARMANA_API_KEY']}"},
                json={"capability": "sandbox:receipt", "resourceId": "demo-order-1"},
                timeout=30,
            )


            # Send it as signals.approvalArtifact within 5 minutes.

            approval = response.json()


            print(response.status_code, approval)
components:
  schemas:
    Error:
      $ref: "#/components/schemas/error.schema"
    root-response.schema:
      title: Root Response
      description: Response returned by GET / (packages/api/src/app.ts). Distinct
        from, and simpler than, GET /health and GET /version; it is not
        registered under any router file.
      type: object
      additionalProperties: false
      required:
        - name
        - status
      properties:
        name:
          type: string
          enum:
            - Parmana
        status:
          type: string
          enum:
            - UP
      examples:
        - name: Parmana
          status: UP
    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
            - POLICY_DENIED
            - CAPABILITY_NOT_ALLOWED
            - RATE_LIMITED
            - AUDIT_UNAVAILABLE
            - SIGNING_UNAVAILABLE
            - CONNECTOR_NOT_REGISTERED
            - EXECUTION_RECORD_INCOMPLETE
            - EXECUTION_INTENT_UNAVAILABLE
            - EXECUTION_INTENT_RESULT_NOT_RECORDED
            - EXECUTION_INTENT_NOT_FOUND
            - EXECUTION_INTENTS_NOT_ENABLED
            - EXECUTION_INTENT_NOT_RESOLVABLE
            - EXECUTION_INTENT_RESOLUTION_INVALID
            - NON_HUMAN_CALLER_DENIED
            - EXECUTION_OUTCOME_UNKNOWN
            - AUTHORIZATION_NO_LONGER_VALID
        executionStatus:
          type: string
          enum:
            - EXECUTION_UNKNOWN
            - NOT_EXECUTED
          description: "RFC-0023 phase 4. EXECUTION_UNKNOWN with
            EXECUTION_OUTCOME_UNKNOWN: the action was released and the call
            failed, so whether it ran is unknown; never reported as failed, and
            the Execution Intent stays ERRORED until a person resolves it.
            NOT_EXECUTED with AUTHORIZATION_NO_LONGER_VALID: the release was
            refused because a business condition or the authority grant it
            rested on no longer held, and nothing was sent."
      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
    health-response.schema:
      title: Health Response
      description: 'Response returned by GET /health. The route unconditionally
        returns status: "UP"; it does not check storage, policy, or any
        dependent component; a 200 response means only that the Express process
        is handling requests.'
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - UP
      examples:
        - status: UP
    ready-response.schema:
      title: Ready Response
      description: "Response returned by GET /ready on both status codes (200 READY,
        503 NOT_READY). Distinct from GET /health's pure liveness check: when
        storage is Supabase-backed, this one actually queries Postgres (SELECT
        1) so an orchestrator can route around a process that is up but backed
        by dead storage."
      type: object
      additionalProperties: false
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - READY
            - NOT_READY
        storage:
          type: string
          description: "Present only when status is READY and storage is not
            Supabase-backed (NODE_ENV=test, or PARMANA_STORAGE=memory outside
            test): there is no external dependency to probe, so readiness is
            reported unconditionally."
          enum:
            - not-supabase-backed
        reason:
          type: string
          description: "Present only when status is NOT_READY: the underlying Postgres
            error message."
        authDisabled:
          type: boolean
          description: True when this process was started with PARMANA_AUTH_DISABLED=true.
            Surfaced here, not only as a startup log line, so an operator's own
            synthetic checks against GET /ready can alert on it directly.
        warning:
          type: string
          description: Present only when authDisabled is true.
      examples:
        - status: READY
          storage: not-supabase-backed
          authDisabled: false
        - status: READY
          storage: not-supabase-backed
          authDisabled: true
          warning: PARMANA_AUTH_DISABLED=true -- this deployment is accepting requests
            with no caller authentication. Must never be set in a real
            deployment.
    version-response.schema:
      title: Version Response
      description: Response returned by GET /version. All three fields are hardcoded
        literals in packages/api/src/routes/version.ts, not read from
        package.json or any build metadata.
      type: object
      additionalProperties: false
      required:
        - name
        - version
        - api
      properties:
        name:
          type: string
          enum:
            - Parmana
        version:
          type: string
          description: Hardcoded running service version string.
        api:
          type: string
          enum:
            - v1
      examples:
        - name: Parmana
          version: 0.4.0
          api: v1
    callers-me-response.schema:
      title: Callers Me Response
      description: "Response returned by GET /callers/me
        (packages/api/src/routes/callers-me.ts). Resolved, not raw:
        allowedPrincipalIds/allowedCapabilities reflect the effective scope
        after defaults are applied (isPrincipalAllowed.ts /
        isCapabilityAllowed.ts), not the raw ApiKeyEntry configuration. Never
        returns key material."
      type: object
      additionalProperties: false
      required:
        - callerId
        - allowedPrincipalIds
        - allowedCapabilities
        - unrestrictedCapabilities
      properties:
        callerId:
          type: string
          description: The authenticated caller's identity.
        allowedPrincipalIds:
          type: array
          items:
            type: string
          description: Principal IDs this caller may assert as authority.principalId.
            Defaults to [callerId] when the key's allowedPrincipalIds is unset.
        allowedCapabilities:
          type: array
          items:
            type: string
          description: Capabilities (intent.action values) this caller may execute.
            Defaults to an empty array, not all capabilities, when the key's
            allowedCapabilities is unset.
        unrestrictedCapabilities:
          type: boolean
          description: True when allowedCapabilities contains the wildcard "*".
      examples:
        - callerId: demo
          allowedPrincipalIds:
            - demo
          allowedCapabilities:
            - test:fixture-execute
          unrestrictedCapabilities: false
    metadata.schema:
      title: Metadata
      description: Immutable Business Transaction metadata supplied by the calling
        application. Not evaluated by Policy. businessTransactionId is the only
        required field; it must match the top-level businessTransactionId.
        Parmana rejects a mismatch with a 400.
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
      properties:
        businessTransactionId:
          type: string
          format: uuid
          description: Unique Business Transaction identifier. Must be a valid UUID.
        correlationId:
          type: string
          description: Optional correlation identifier used by the calling application.
        tenantId:
          type: string
          description: Optional tenant identifier for multi-tenant deployments.
        sourceSystem:
          type: string
          description: Originating application or service.
        submittedBy:
          type: string
          description: Identity of the calling application or principal.
        submittedAt:
          type: string
          format: date-time
          description: UTC timestamp when the Business Transaction was submitted.
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          correlationId: 6b97caca-1605-4711-bf00-7e5a65434d93
          sourceSystem: vendor-payment-service
          submittedBy: ap-automation
          submittedAt: 2026-07-07T16:38:59.285Z
    authority.schema:
      title: Authority
      description: Entity empowered to authorize execution within a trust domain.
        Immutable once issued.
      type: object
      additionalProperties: true
      required:
        - authorityId
        - authorityType
        - principalId
        - issuedAt
      properties:
        authorityId:
          type: string
          description: Unique Authority identifier.
        authorityType:
          type: string
          description: Type of authority.
          enum:
            - USER
            - ROLE
            - SERVICE
            - ORGANIZATION
        principalId:
          type: string
          description: Principal identifier.
        displayName:
          type: string
          description: Human-readable display name.
        issuedAt:
          type: string
          format: date-time
          description: UTC timestamp when Authority became effective.
      examples:
        - authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
          authorityType: SERVICE
          principalId: ap-automation-svc
          displayName: Accounts Payable Automation
          issuedAt: 2026-07-07T16:38:59.285Z
    authorization.schema:
      title: Authorization
      description: Immutable trust artifact proving that an Authority granted approval
        for an intended execution.
      type: object
      additionalProperties: true
      required:
        - authorizationId
        - authorityId
        - purpose
        - issuedAt
      properties:
        authorizationId:
          type: string
          description: Unique Authorization identifier.
        authorityId:
          type: string
          description: Authority issuing this Authorization. Must match
            authority.authorityId. Parmana rejects a mismatch with a 400.
        purpose:
          type: string
          description: Business purpose for which the Authorization was granted.
        issuedAt:
          type: string
          format: date-time
          description: UTC timestamp when the Authorization was issued.
        expiresAt:
          type: string
          format: date-time
          description: Optional expiration timestamp. After this time the Authorization is
            no longer valid.
      examples:
        - authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
          authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
          purpose: Authorize vendor payment disbursement
          issuedAt: 2026-07-07T16:38:59.285Z
    intent.schema:
      title: Intent
      description: Immutable declaration of the action an Authority intends to be
        executed under an Authorization.
      type: object
      additionalProperties: true
      required:
        - intentId
        - authorizationId
        - action
        - target
        - parameters
        - createdAt
      properties:
        intentId:
          type: string
          description: Unique Intent identifier.
        authorizationId:
          type: string
          description: Authorization under which this Intent was created. Must match
            authorization.authorizationId. Parmana rejects a mismatch with a
            400.
        action:
          type: string
          description: Business action being requested. Required and non-empty. Parmana
            rejects an empty action with a 400.
          examples:
            - VendorPayment
            - TransferFunds
            - DeployApplication
        target:
          type: string
          description: Target of the intended action.
          examples:
            - vendor/V-100
            - account/12345
        parameters:
          type: object
          description: Immutable business parameters describing the intended action. Not
            evaluated by policy, see signals.
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
          description: UTC timestamp when the Intent was created.
      examples:
        - intentId: ae5865f6-181b-409a-90ec-1b4b8b8414ba
          authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
          action: payments:execute
          target: vendor/V-100
          parameters:
            amount: 4500
            currency: USD
          createdAt: 2026-07-07T16:38:59.285Z
    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
        contentHash:
          type: string
          description: sha256 of the canonicalized policy.json content actually loaded for
            this decision (G-24). Server-computed, never caller-settable; absent
            on a request-supplied PolicyReference and on a PolicyReference built
            before this field existed. Only present on the copy embedded in an
            Execution Trust Record's transaction.policy.
        governanceAnchor:
          type: object
          description: Whether the policy content above is traceable to a completed Policy
            Governance approval, resolved at decision time (G-45). Absent on a
            PolicyReference built before this field existed. Its presence does
            not imply POLICY_EXECUTION_VERIFICATION_ENFORCED is on -- only that
            the lookup ran and recorded what it found, which may honestly be "no
            approval record exists for this policy yet."
          additionalProperties: false
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - VERIFIED
                - NO_APPROVAL_RECORD
                - SIGNATURE_INVALID
                - CONTENT_MISMATCH
            approvalRecordId:
              type: string
              description: The PolicyChangeApprovalRecord this anchor resolved against.
                Present only when a record was found at all -- absent for
                NO_APPROVAL_RECORD.
      examples:
        - name: vendor-payment
          version: 2.0.0
          schemaVersion: 1.0.0
        - name: vendor-payment
          version: 2.0.0
          schemaVersion: 1.0.0
          contentHash: 3f2504e04f8964e7ad0dbb0cf7fea1b4e0e1e0a3a1e1a4b6c8e0d5f6a7b8c9d0
          governanceAnchor:
            status: NO_APPROVAL_RECORD
    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
    transaction-create-request.schema:
      title: Transaction Create Request
      description: "Request payload for POST /execute and POST /transactions. status
        and createdAt are assigned by Parmana and must not be supplied by the
        client. Validation performed identically by both endpoints (each has its
        own inline businessTransactionId UUID check, then shares
        BusinessTransactionMapper.fromRequest and BusinessTransactionValidator):
        metadata.businessTransactionId must equal businessTransactionId;
        authorization.authorityId must equal authority.authorityId;
        intent.authorizationId must equal authorization.authorizationId;
        policy.name, policy.version, and intent.action must each be non-empty.
        No other structural validation is performed: authority, authorization,
        intent, policy, and signals objects are otherwise passed through as
        supplied, including any additional properties."
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
        - metadata
        - authority
        - authorization
        - intent
        - policy
        - signals
      properties:
        businessTransactionId:
          type: string
          format: uuid
          description: Unique Business Transaction identifier. Must be a valid UUID on
            both POST /execute and POST /transactions (rejected with 400
            otherwise).
        metadata:
          $ref: "#/components/schemas/metadata.schema"
        authority:
          $ref: "#/components/schemas/authority.schema"
        authorization:
          $ref: "#/components/schemas/authorization.schema"
        intent:
          $ref: "#/components/schemas/intent.schema"
        policy:
          $ref: "#/components/schemas/policy.schema"
        signals:
          $ref: "#/components/schemas/signals.schema"
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          metadata:
            businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
            correlationId: 6b97caca-1605-4711-bf00-7e5a65434d93
            sourceSystem: vendor-payment-service
            submittedBy: ap-automation
            submittedAt: 2026-07-07T16:38:59.285Z
          authority:
            authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
            authorityType: SERVICE
            principalId: ap-automation-svc
            displayName: Accounts Payable Automation
            issuedAt: 2026-07-07T16:38:59.285Z
          authorization:
            authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
            authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
            purpose: Authorize vendor payment disbursement
            issuedAt: 2026-07-07T16:38:59.285Z
          intent:
            intentId: ae5865f6-181b-409a-90ec-1b4b8b8414ba
            authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
            action: payments:execute
            target: vendor/V-100
            parameters:
              amount: 4500
              currency: USD
            createdAt: 2026-07-07T16:38:59.285Z
          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
    business-transaction.schema:
      title: Business Transaction
      description: "Canonical immutable business context accepted by Parmana for
        execution: Authority -> Authorization -> Intent -> Business Transaction
        -> Policy. Every Business Transaction produces exactly one Decision, one
        Execution, and one Execution Trust Record."
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
        - metadata
        - authority
        - authorization
        - intent
        - policy
        - signals
        - status
        - createdAt
      properties:
        businessTransactionId:
          type: string
          format: uuid
          description: Unique Business Transaction identifier. Same value as
            metadata.businessTransactionId. Must be a valid UUID on both POST
            /execute and POST /transactions (rejected with a 400 otherwise).
          x-default: 8ddd9ae9-eebe-4a5a-8182-4e6187466a74
        metadata:
          $ref: "#/components/schemas/metadata.schema"
        authority:
          $ref: "#/components/schemas/authority.schema"
        authorization:
          $ref: "#/components/schemas/authorization.schema"
        intent:
          $ref: "#/components/schemas/intent.schema"
        policy:
          $ref: "#/components/schemas/policy.schema"
        signals:
          $ref: "#/components/schemas/signals.schema"
        status:
          type: string
          description: Current Business Transaction lifecycle state. Always RECEIVED at
            creation on both POST /execute and POST /transactions. Parmana sets
            this field via BusinessTransactionMapper.fromRequest and ignores any
            status the client sends.
          enum:
            - RECEIVED
            - POLICY_EVALUATED
            - APPROVED
            - REJECTED
            - OVERRIDDEN
            - EXECUTING
            - EXECUTED
            - FAILED
            - VERIFIED
        createdAt:
          type: string
          format: date-time
          description: UTC timestamp when the Business Transaction was accepted by Parmana.
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          metadata:
            businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
            correlationId: 6b97caca-1605-4711-bf00-7e5a65434d93
            sourceSystem: vendor-payment-service
            submittedBy: ap-automation
            submittedAt: 2026-07-07T16:38:59.285Z
          authority:
            authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
            authorityType: SERVICE
            principalId: ap-automation-svc
            displayName: Accounts Payable Automation
            issuedAt: 2026-07-07T16:38:59.285Z
          authorization:
            authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
            authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
            purpose: Authorize vendor payment disbursement
            issuedAt: 2026-07-07T16:38:59.285Z
          intent:
            intentId: ae5865f6-181b-409a-90ec-1b4b8b8414ba
            authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
            action: payments:execute
            target: vendor/V-100
            parameters:
              amount: 4500
              currency: USD
            createdAt: 2026-07-07T16:38:59.285Z
          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
          status: RECEIVED
          createdAt: 2026-07-07T16:38:59.325Z
    override.schema:
      title: Override
      description: Immutable trust artifact recording an authorized human override for
        a Business Transaction. No API route in this repository currently
        creates an Override; this schema exists only because
        ExecutionTrustRecord.overrides is part of the append-only Trust Record
        shape; every captured example has an empty overrides array.
      type: object
      additionalProperties: true
      required:
        - overrideId
        - businessTransactionId
        - approvedBy
        - reason
        - approvedAt
      properties:
        overrideId:
          type: string
          description: Unique Override identifier.
        businessTransactionId:
          type: string
          description: Business Transaction to which this Override belongs.
        approvedBy:
          type: string
          description: Authorized user or system that approved the Override.
        reason:
          type: string
          description: Human-readable reason for the Override.
        justification:
          type: string
          description: Optional business justification.
        approvedAt:
          type: string
          format: date-time
          description: UTC timestamp when the Override was approved.
    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.
        matchedRuleId:
          type: string
          description: Identifier of the Policy rule that matched, or "none" when no rule
            matched. Absent on a Decision built before this field existed
            (docs/VERIFICATION-GAPS.md G-44).
        evaluatedRules:
          type: integer
          description: Number of rules evaluated before reaching a match (or exhausting
            the rule list). Same optionality as matchedRuleId.
        matchedPath:
          type: array
          items:
            type: string
          description: Ordered rule-id trace evaluation walked to reach matchedRuleId.
            Same optionality as matchedRuleId.
        evaluatedAt:
          type: string
          format: date-time
          description: UTC timestamp when policy evaluation completed.
        assessment:
          type: object
          description: Authority, business validation and, for a refusal, execution, each
            as its own status (RFC-0023). AUTHORIZED means the action type may
            be decided, not that this action is valid or was executed. Absent on
            decisions made before it existed.
          required:
            - authority
            - businessValidation
          properties:
            authority:
              type: object
              required:
                - status
                - reason
              properties:
                status:
                  type: string
                  enum:
                    - AUTHORIZED
                    - NOT_AUTHORIZED
                    - AUTHORITY_UNCLEAR
                    - AUTHORITY_EXPIRED
                reason:
                  type: string
            businessValidation:
              type: object
              required:
                - status
                - reason
              properties:
                status:
                  type: string
                  enum:
                    - VALID
                    - INVALID
                    - MISSING_DATA
                    - CONFLICTING_DATA
                    - SOURCE_UNAVAILABLE
                    - VALIDATION_EXPIRED
                    - NOT_EVALUATED
                reason:
                  type: string
                signals:
                  type: array
                  description: "Trusted Signals: facts established by their sources for this exact
                    business object and action."
                  items:
                    type: object
                failures:
                  type: array
                  description: Why business validation did not pass, one entry per signal that
                    failed.
                  items:
                    type: object
                    required:
                      - signalKey
                      - status
                      - reason
                    properties:
                      signalKey:
                        type: string
                      status:
                        type: string
                        enum:
                          - VALID
                          - INVALID
                          - MISSING_DATA
                          - CONFLICTING_DATA
                          - SOURCE_UNAVAILABLE
                          - VALIDATION_EXPIRED
                          - NOT_EVALUATED
                      reason:
                        type: string
            execution:
              type: object
              required:
                - status
                - reason
              properties:
                status:
                  type: string
                  enum:
                    - EXECUTED
                    - NOT_EXECUTED
                    - EXECUTION_FAILED
                    - EXECUTION_UNKNOWN
                reason:
                  type: string
      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.
          matchedRuleId: approve-payment
          evaluatedRules: 1
          matchedPath:
            - approve-payment
          evaluatedAt: 2026-07-07T16:38:59.326Z
    execution.schema:
      title: Execution
      description: Immutable trust artifact recording what actually happened while
        processing a Business Transaction. Every Execution records exactly one
        Decision produced by deterministic Policy evaluation.
      type: object
      additionalProperties: true
      required:
        - executionId
        - businessTransactionId
        - decision
        - status
        - mode
        - startedAt
      properties:
        executionId:
          type: string
          description: Unique Execution identifier.
        businessTransactionId:
          type: string
          description: Business Transaction to which this Execution belongs.
        decision:
          $ref: "#/components/schemas/decision.schema"
        status:
          type: string
          description: Execution lifecycle state.
          enum:
            - PROCESSING
            - COMPLETED
            - FAILED
        mode:
          type: string
          description: Execution mode.
          enum:
            - SYNC
            - ASYNC
        startedAt:
          type: string
          format: date-time
          description: UTC timestamp when execution started.
        completedAt:
          type: string
          format: date-time
          description: UTC timestamp when execution completed. Present only for terminal
            executions.
        evidence:
          type: object
          description: "Immutable execution evidence: businessTransactionId, action,
            target, parameters, success, executedAt, and an optional attributes
            bag for execution-system-specific evidence (for example Connector
            SDK evidence, when a Connector executed this Execution)."
          additionalProperties: true
        metadata:
          type: object
          description: Execution-specific metadata. Currently populated with
            authorizationId when the Execution is APPROVED.
          additionalProperties: true
      examples:
        - executionId: 90746492-7721-4228-b244-67bf8a45ca68
          businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
          decision:
            decisionId: 8ec880cb-677a-4889-96f1-5a18a4edc1b5
            intentId: 1e77d0b3-2e57-4e31-b780-03abc92f1b69
            policy:
              name: vendor-payment
              version: 2.0.0
              schemaVersion: 1.0.0
            signals:
              vendorVerified: true
              invoiceVerified: true
              paymentApproved: true
              sufficientFunds: true
              paymentAmount: 5000
              riskScore: 12
            outcome: APPROVED
            reason: Vendor payment authorized. Vendor verification, invoice verification,
              payment approval, funding, and risk assessment requirements were
              satisfied.
            evaluatedAt: 2026-07-13T17:36:00.967Z
          status: COMPLETED
          mode: SYNC
          startedAt: 2026-07-13T17:36:00.968Z
          metadata:
            authorizationId: dbc09266-2078-4d4e-93d0-f355eb7eebcc
          evidence:
            businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
            action: payments:execute
            target: vendor/V-500
            parameters:
              amount: 5000
              currency: USD
            success: true
            executedAt: 2026-07-13T17:36:00.970Z
            attributes:
              connector:
                connectorId: vendor-payment
                connectorVersion: 1.0.0
                capability: payments:execute
                sanitizedEndpoint: vendor/V-500
                credentialProviderId: environment
                requestSummary:
                  businessTransactionId: 44b34a79-e0f2-49d7-a48e-e52fff88182e
                  action: payments:execute
                  target: vendor/V-500
                  parameters:
                    amount: 5000
                    currency: USD
                responseSummary:
                  success: true
                  metadata: {}
                startedAt: 2026-07-13T17:36:00.970Z
                completedAt: 2026-07-13T17:36:00.970Z
                connectorEvidenceHash: c97411026a82b608d9b0f8b137a64e26d8af24bdda09310b639e527e08a9aada
          completedAt: 2026-07-13T17:36:00.970Z
    verification.schema:
      title: Verification
      description: "Immutable result of verifying an entire Execution Trust Record:
        recomputed hash matches the stored hash, signature verifies, and every
        APPROVED Execution carries a non-empty authorizationId. All checks
        always run and are reported together in message; a failure in one does
        not skip the others."
      type: object
      additionalProperties: true
      required:
        - verificationId
        - businessTransactionId
        - status
        - verifiedAt
        - trustRecordHash
      properties:
        verificationId:
          type: string
          description: Unique Verification identifier.
        businessTransactionId:
          type: string
          description: Business Transaction being verified.
        status:
          type: string
          description: Verification result.
          enum:
            - VERIFIED
            - FAILED
        message:
          type: string
          description: "Human-readable verification summary: either the success message,
            or every failed check's message joined with \"; \"."
        verifiedAt:
          type: string
          format: date-time
          description: UTC timestamp when verification completed.
        trustRecordHash:
          type: string
          description: Hash of the verified Execution Trust Record, proving exactly which
            record was verified.
      examples:
        - verificationId: 0ad69d3f-7fd0-4507-ab38-e386146757ae
          businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          status: VERIFIED
          message: Execution Trust Record verified successfully.
          verifiedAt: 2026-07-07T16:38:59.344Z
          trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
    receipt.schema:
      title: Receipt
      description: Cryptographically signed, immutable Execution Trust Receipt proving
        the outcome of an Execution Trust Record at the time it was issued.
      type: object
      additionalProperties: true
      required:
        - receiptId
        - businessTransactionId
        - trustRecordHash
        - receiptHash
        - signature
        - algorithm
        - issuedAt
      properties:
        receiptId:
          type: string
          description: Unique Receipt identifier.
        businessTransactionId:
          type: string
          description: Business Transaction represented by this Receipt.
        executionId:
          type: string
          description: Execution represented by this Receipt. Absent when the Receipt
            represents the latest transaction state; every Receipt currently
            produced by ReceiptService omits this field.
        trustRecordHash:
          type: string
          description: Canonical hash of the Execution Trust Record, used for independent
            verification.
        receiptHash:
          type: string
          description: Hash of this Receipt.
        signature:
          type: string
          description: Base64-encoded digital signature over the Receipt.
        algorithm:
          type: string
          description: Signing algorithm.
          examples:
            - ed25519
        issuedAt:
          type: string
          format: date-time
          description: UTC timestamp when the Receipt was generated.
      examples:
        - receiptId: 312ce65d-8ec4-4978-ac50-aeb0062ad7be
          businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
          receiptHash: 2cc963456dd71e0e85f2d2fe743d4475072997287356ac1b9c6a9314b189ea8b
          issuedAt: 2026-07-07T16:38:59.350Z
          algorithm: ed25519
          signature: 2Z49nDSXubFNTpy0ovPwguGW3IpgvdJuiusCAadMNPS4cqmjRUthZmrfN3kZUO5m7Ku5v/poeLxtjsFKvXe9DA==
    execution-trust-record.schema:
      title: Execution Trust Record
      description: "Canonical immutable record representing everything Parmana knows
        about a Business Transaction: the authoritative source for replay,
        verification, audit, and receipt generation. One Execution Trust Record
        exists per Business Transaction. overrides, executions, verifications,
        and receipts are append-only: existing entries are never modified or
        removed."
      type: object
      additionalProperties: true
      required:
        - trustRecordId
        - businessTransactionId
        - transaction
        - overrides
        - executions
        - verifications
        - receipts
        - trustRecordHash
        - signature
        - createdAt
        - updatedAt
      properties:
        trustRecordId:
          type: string
          description: Unique Execution Trust Record identifier.
        businessTransactionId:
          type: string
          description: Business Transaction identifier.
        transaction:
          $ref: "#/components/schemas/business-transaction.schema"
        overrides:
          type: array
          description: Override history. Append-only; empty on every transaction in this
            repository today (no route creates an Override).
          items:
            $ref: "#/components/schemas/override.schema"
        executions:
          type: array
          description: Execution history. Append-only.
          items:
            $ref: "#/components/schemas/execution.schema"
        verifications:
          type: array
          description: Verification history. Append-only.
          items:
            $ref: "#/components/schemas/verification.schema"
        receipts:
          type: array
          description: Receipt history. Append-only.
          items:
            $ref: "#/components/schemas/receipt.schema"
        trustRecordHash:
          type: string
          description: Canonical hash of the Execution Trust Record, computed over its
            canonical serialized form.
        signature:
          type: object
          description: Cryptographic signature over the canonical Execution Trust Record,
            proving it was produced by Parmana and has not been modified since
            signing.
          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 the Execution Trust Record was first created.
        updatedAt:
          type: string
          format: date-time
          description: UTC timestamp when the Execution Trust Record was last extended
            with a new immutable artifact.
        evidenceAnchor:
          type: object
          description: Explicit binding linking policy-governance provenance
            (transaction.policy.contentHash/governanceAnchor) to what a
            connector actually did (executions[].evidence.attributes.connector).
            Not a new cryptographic guarantee on its own -- both were already
            covered by trustRecordHash/signature -- but a single,
            explicitly-named pointer an auditor can check without reconstructing
            the binding themselves. Absent on a Trust Record built before this
            field existed.
          additionalProperties: false
          required:
            - anchorHash
          properties:
            policyContentHash:
              type: string
              description: Copied from transaction.policy.contentHash (G-24). Present on every
                real Execution Trust Record.
            governanceAnchorStatus:
              type: string
              enum:
                - VERIFIED
                - NO_APPROVAL_RECORD
                - SIGNATURE_INVALID
                - CONTENT_MISMATCH
              description: Copied from transaction.policy.governanceAnchor.status (G-45).
                Absent only when no governance anchor resolver was configured
                for this deployment.
            connectorEvidenceHash:
              type: string
              description: Copied from
                executions[0].evidence.attributes.connector.connectorEvidenceHash,
                when a real connector executed.
            anchorHash:
              type: string
              description: sha256 of the canonicalized {policyContentHash,
                governanceAnchorStatus, connectorEvidenceHash} above.
      examples:
        - trustRecordId: 759e916e-66d2-48c3-8869-55d557acf155
          businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          transaction:
            businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
            metadata:
              businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
              correlationId: 6b97caca-1605-4711-bf00-7e5a65434d93
              sourceSystem: vendor-payment-service
              submittedBy: ap-automation
              submittedAt: 2026-07-07T16:38:59.285Z
            authority:
              authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
              authorityType: SERVICE
              principalId: ap-automation-svc
              displayName: Accounts Payable Automation
              issuedAt: 2026-07-07T16:38:59.285Z
            authorization:
              authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
              authorityId: c0c01c23-04a2-45f2-a821-ef24bfca02d3
              purpose: Authorize vendor payment disbursement
              issuedAt: 2026-07-07T16:38:59.285Z
            intent:
              intentId: ae5865f6-181b-409a-90ec-1b4b8b8414ba
              authorizationId: 6e9d6aa6-6d20-40a8-b05d-383516365cc7
              action: payments:execute
              target: vendor/V-100
              parameters:
                amount: 4500
                currency: USD
              createdAt: 2026-07-07T16:38:59.285Z
            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
            status: RECEIVED
            createdAt: 2026-07-07T16:38:59.325Z
          overrides: []
          executions:
            - executionId: 2be38c15-f93c-4621-9a1c-7570c130dea0
              businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
              decision:
                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
              status: COMPLETED
              mode: SYNC
              startedAt: 2026-07-07T16:38:59.328Z
              metadata:
                authorizationId: 1dc2d137-fde2-4d9c-b298-79e9f8a0d03e
              evidence:
                businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
                action: payments:execute
                target: vendor/V-100
                parameters:
                  amount: 4500
                  currency: USD
                success: true
                executedAt: 2026-07-07T16:38:59.328Z
                attributes:
                  connector:
                    connectorId: vendor-payment
                    connectorVersion: 1.0.0
                    capability: payments:execute
                    sanitizedEndpoint: vendor/V-100
                    credentialProviderId: environment
                    requestSummary:
                      businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
                      action: payments:execute
                      target: vendor/V-100
                      parameters:
                        amount: 4500
                        currency: USD
                    responseSummary:
                      success: true
                      metadata: {}
                    startedAt: 2026-07-07T16:38:59.328Z
                    completedAt: 2026-07-07T16:38:59.328Z
                    connectorEvidenceHash: 7bcacb3e399a713e44a4f48aa2783de8060ae28591e4fc1256b504bd32cd7aea
              completedAt: 2026-07-07T16:38:59.329Z
          verifications:
            - verificationId: 0b96c8b4-9cd8-4162-81fc-086575cf0924
              businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
              status: VERIFIED
              message: Execution Trust Record verified successfully.
              verifiedAt: 2026-07-07T16:38:59.331Z
              trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
          receipts:
            - receiptId: 3073a598-f927-4caf-8bb8-4f083f62b7e9
              businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
              trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
              receiptHash: 8a5a0e9255dafef5ec588f0834e3c5b81dc7feb98a59f8bfa7083438fa58e231
              issuedAt: 2026-07-07T16:38:59.331Z
              algorithm: ed25519
              signature: vzpykBuJhoy/2BdiR1x02KYZqMtKJu39olRJdzM5veSo9nYmoNSYQnt9HELlb2jlGWf07jPhP8ZXlrQnN4KQBA==
          createdAt: 2026-07-07T16:38:59.329Z
          updatedAt: 2026-07-07T16:38:59.329Z
          trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
          signature:
            algorithm: ed25519
            keyId: default
            value: l41lr6nCUbrzEfoil/3+gkXd4zEQ9TR1qFaSwLuE70L/9U6cVz3VXdM1lPsDfELKh6RO4qcDrFWF/S3k4CAxAQ==
            signedAt: 2026-07-07T16:38:59.330Z
    execution-trust-record-response.schema:
      title: Execution Trust Record Response
      description: Response returned by POST /execute, POST /transactions, and GET
        /trust-records/{businessTransactionId}. A bare Execution Trust Record,
        no wrapper. POST /execute returns this with status 200; POST
        /transactions returns the identical shape with status 201.
      $ref: "#/components/schemas/execution-trust-record.schema"
    transactions-list-response.schema:
      title: Transactions List Response
      description: Response returned by GET /transactions. A bare array, no pagination
        envelope (no page/pageSize/totalItems metadata), even though the
        endpoint accepts page and pageSize query parameters.
      type: array
      items:
        $ref: "#/components/schemas/business-transaction.schema"
    transaction-response.schema:
      title: Transaction Response
      description: Response returned by GET /transactions/{businessTransactionId} (and
        as each item of GET /transactions' array). A bare Business Transaction,
        no wrapper, no links, no embedded Decision (that lives on each
        Execution, not on the transaction).
      $ref: "#/components/schemas/business-transaction.schema"
    verification-request.schema:
      title: Verification Request
      description: Request payload for POST /verify. businessTransactionId is required
        and must be a valid UUID; both are rejected with a 400, with distinct
        messages depending on which check fails.
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
      properties:
        businessTransactionId:
          type: string
          format: uuid
          description: Business Transaction to verify.
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
    verification-response.schema:
      title: Verification Response
      description: "Response returned by POST /verify and GET
        /verification/{businessTransactionId}: a Verification."
      allOf:
        - $ref: "#/components/schemas/verification.schema"
      examples:
        - verificationId: 0ad69d3f-7fd0-4507-ab38-e386146757ae
          businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          status: VERIFIED
          message: Execution Trust Record verified successfully.
          verifiedAt: 2026-07-07T16:38:59.344Z
          trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
    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.
        policyContentHash:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: SHA-256 hash of the policy content that decided the refusal, the
            same value an Execution Trust Record carries in
            transaction.policy.contentHash. Inside the signed record. Absent on
            Refusal Records written before 2026-10-06.
        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
    refusal-verify-request.schema:
      title: Refusal Verify Request
      description: "Request body for POST /refusal/verify: the Refusal Record itself,
        not a lookup by ID. Checked structurally on the server (refusalRecordId,
        businessTransactionId, decision, refusalRecordHash, signature required)
        before signature verification runs."
      $ref: "#/components/schemas/refusal-record.schema"
    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
    refusal-record-response.schema:
      title: Refusal Record Response
      description: Response returned by GET /refusal/{businessTransactionId}. A bare
        Refusal Record, no wrapper.
      $ref: "#/components/schemas/refusal-record.schema"
    execution-intent.schema:
      title: Execution Intent
      description: "A signed statement, made and stored BEFORE an action is released
        to a connector, of exactly what is about to be released (ADR-0012). It
        exists so that an action that was released always has signed evidence
        behind it, even when the Execution Trust Record cannot be produced
        afterwards. It contains only facts that exist before release: never the
        execution result, and never the raw intent parameters (the
        businessTransactionHash binds the intent to them). It is a separate
        record from the Execution Trust Record, which keeps its own format. An
        intent proves what was about to be released. It does NOT prove the
        action was released or what its result was. At most one Execution Intent
        exists per businessTransactionId."
      type: object
      additionalProperties: true
      required:
        - intentId
        - businessTransactionId
        - decisionId
        - authorizationId
        - policyName
        - policyVersion
        - businessTransactionHash
        - action
        - target
        - createdAt
        - intentHash
        - signature
      properties:
        intentId:
          type: string
          description: Unique Execution Intent identifier.
        businessTransactionId:
          type: string
          description: The Business Transaction this intent belongs to.
        decisionId:
          type: string
          description: The Decision that approved the action.
        authorizationId:
          type: string
          description: The signed execution authorization this intent refers to. Matches
            authorization.payload.authorizationId in the Execution Trust Record.
        policyName:
          type: string
        policyVersion:
          type: string
        policyContentHash:
          type: string
          description: Hash of the exact policy content in force, copied from the signed
            authorization. Absent when the authorization carries none.
        signalsHash:
          type: string
          description: Hash of the signals that were evaluated, copied from the signed
            authorization. Absent when the authorization carries none.
        businessTransactionHash:
          type: string
          description: Hash of the executable content (action, target and parameters),
            copied from the signed authorization. It binds the intent to the
            exact parameters that were authorized without repeating them here.
        action:
          type: string
          description: The capability about to be released, for example paytm:refund.
        target:
          type: string
          description: The target of the action, for example the order being refunded.
            Together with action and businessTransactionId it is what an
            operator searches for at the connector.
        submittedBy:
          type: string
          description: Authenticated caller who submitted the request. Absent when caller
            authentication is disabled.
        grantedCapability:
          type: string
          description: The capability the caller was granted, when one was recorded.
        createdAt:
          type: string
          format: date-time
          description: UTC timestamp when the intent was created, before the action was
            released.
        intentHash:
          type: string
          description: Canonical hash of the intent, same convention as
            ExecutionTrustRecord.trustRecordHash.
        signature:
          type: object
          description: Signature over the canonical Execution Intent, made with the
            deployment's signing key (local or AWS KMS), the same root of trust
            as the Execution Trust Record.
          additionalProperties: true
          required:
            - algorithm
            - keyId
            - value
            - signedAt
          properties:
            algorithm:
              type: string
              examples:
                - ed25519
            keyId:
              type: string
              description: Identifier of the signing key. Fetch its public half from GET
                /keys/{keyId}.
            value:
              type: string
              description: Base64 encoded signature value.
            signedAt:
              type: string
              format: date-time
      examples:
        - intentId: b41fe9b9-5730-4d34-aab0-4ab63ae69d3b
          businessTransactionId: 4c2eb3b2-3382-4bc2-bf22-2a072e6cf428
          decisionId: 20b4e030-09cd-4447-86fc-b73279d23121
          authorizationId: 5977535b-d887-423d-b422-01ece1fc7bf1
          policyName: customer-refund
          policyVersion: 1.0.0
          policyContentHash: 6a2e92fbde0f4c5dc56b4e202bf45ce24c99e565cf3ddaacaa21a11c71b7e1ea
          signalsHash: e22a7b9fd6e0a39e28521f9ffb726d6a57499236055fa6ff2caabd2268f4a395
          businessTransactionHash: 9df286daf38c6eba272f47153e0fb3efef5ee61d778f6baecd5a53861763763a
          action: paytm:refund
          target: DRY-ORDER-1789969223742
          submittedBy: sandbox-agent
          grantedCapability: paytm:refund
          createdAt: 2026-09-21T05:40:28.434Z
          intentHash: 13922de380518d582c6bb3e88b1d88908e71565f191119f4cadc7de4e30cf5ed
          signature:
            keyId: default
            value: UhXnkSnm5A3QU4CVrKGbP3FcTWkShnitn86psJprJpqgEBnsXWzDcZ+ktSO0FJek1X+n9DvO3SjR+WvSofS1CA==
            signedAt: 2026-09-21T05:40:28.555Z
            algorithm: ed25519
    execution-intent-verify-request.schema:
      title: Execution Intent Verify Request
      description: "Request body for POST /execution-intents/verify: the Execution
        Intent itself (the intent field of GET
        /execution-intents/{businessTransactionId}), not a lookup by ID. Checked
        structurally on the server (intentId, businessTransactionId,
        authorizationId, intentHash, signature required) before signature
        verification runs."
      $ref: "#/components/schemas/execution-intent.schema"
    execution-intent-status.schema:
      title: Execution Intent Status
      description: "Operational status kept next to a signed Execution Intent. It is
        NOT part of what is signed, so it changes as the request progresses.
        PREPARED: the intent is signed and stored, and the action may or may not
        have been released. An intent that stays PREPARED needs reconciliation
        with the connector. RELEASED: the release stage returned and the
        execution context was saved, so the Trust Record can be rebuilt.
        FINALIZED: a signed Execution Trust Record exists for this transaction.
        ERRORED: the release stage raised an error. The action may still have
        been executed, so the outcome is unknown and must be reconciled.
        RESOLVED: a verified human reconciled a PREPARED or ERRORED intent at
        the connector and closed it with POST
        /execution-intents/{businessTransactionId}/resolve, recording what they
        found and a note. The resolution is an attributed operator statement
        stored in this unsigned status. It is NOT tamper evident and it is not a
        Trust Record."
      type: object
      additionalProperties: true
      required:
        - state
      properties:
        state:
          type: string
          enum:
            - PREPARED
            - RELEASED
            - FINALIZED
            - ERRORED
            - RESOLVED
        releasedAt:
          type: string
          format: date-time
          description: When the release stage returned and the execution context was
            saved. Absent from PREPARED and from an ERRORED intent.
        finalizedAt:
          type: string
          format: date-time
          description: When the intent was marked FINALIZED. Present only when state is
            FINALIZED.
        finalizationMode:
          type: string
          enum:
            - INLINE
            - REPAIRED
          description: "INLINE: the Trust Record was produced in the original request.
            REPAIRED: it was rebuilt afterwards by POST
            /execution-intents/{businessTransactionId}/finalize. Present only
            when state is FINALIZED."
        trustRecordId:
          type: string
          description: The Execution Trust Record for this transaction. Present only when
            state is FINALIZED.
        failureReason:
          type: string
          description: The error message the release stage raised. Present only when state
            is ERRORED.
        resolution:
          type: string
          enum:
            - NOT_EXECUTED
            - EXECUTED
          description: What the operator found at the connector. Present only when state
            is RESOLVED.
        resolutionNote:
          type: string
          description: The operator's note. Required when resolving. Present only when
            state is RESOLVED.
        resolvedBy:
          type: string
          description: The authenticated caller who resolved it. Absent when caller
            authentication is disabled.
        resolvedAt:
          type: string
          format: date-time
          description: When it was resolved. Present only when state is RESOLVED.
      examples:
        - state: FINALIZED
          releasedAt: 2026-09-21T05:40:32.743Z
          finalizedAt: 2026-09-21T05:40:32.855Z
          finalizationMode: INLINE
          trustRecordId: a3c8d068-af6a-46c7-9976-e43612d5ab9c
    execution-intents-unfinalized-response.schema:
      title: Unfinalized Execution Intents Response
      description: "Response returned by GET /execution-intents/unfinalized: intents
        that never reached a signed Execution Trust Record and were not closed
        by hand, oldest first. Each is an action that may have been released
        with no signed record, or whose outcome nobody has reconciled yet.
        Entries in state RELEASED are repaired with POST
        /execution-intents/{businessTransactionId}/finalize. Entries in state
        PREPARED or ERRORED are reconciled at the connector and then closed with
        POST /execution-intents/{businessTransactionId}/resolve. A RESOLVED
        intent no longer appears here."
      type: object
      additionalProperties: false
      required:
        - intents
      properties:
        intents:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - intent
              - status
            properties:
              intent:
                $ref: "#/components/schemas/execution-intent.schema"
              status:
                $ref: "#/components/schemas/execution-intent-status.schema"
      examples:
        - intents: []
    execution-intent-response.schema:
      title: Execution Intent Response
      description: "Response returned by GET
        /execution-intents/{businessTransactionId}: the signed Execution Intent
        and its operational status. The saved release context used to rebuild a
        Trust Record is internal and is never returned."
      type: object
      additionalProperties: false
      required:
        - intent
        - status
      properties:
        intent:
          $ref: "#/components/schemas/execution-intent.schema"
        status:
          $ref: "#/components/schemas/execution-intent-status.schema"
      examples:
        - intent:
            intentId: b41fe9b9-5730-4d34-aab0-4ab63ae69d3b
            businessTransactionId: 4c2eb3b2-3382-4bc2-bf22-2a072e6cf428
            decisionId: 20b4e030-09cd-4447-86fc-b73279d23121
            authorizationId: 5977535b-d887-423d-b422-01ece1fc7bf1
            policyName: customer-refund
            policyVersion: 1.0.0
            policyContentHash: 6a2e92fbde0f4c5dc56b4e202bf45ce24c99e565cf3ddaacaa21a11c71b7e1ea
            signalsHash: e22a7b9fd6e0a39e28521f9ffb726d6a57499236055fa6ff2caabd2268f4a395
            businessTransactionHash: 9df286daf38c6eba272f47153e0fb3efef5ee61d778f6baecd5a53861763763a
            action: paytm:refund
            target: DRY-ORDER-1789969223742
            submittedBy: sandbox-agent
            grantedCapability: paytm:refund
            createdAt: 2026-09-21T05:40:28.434Z
            intentHash: 13922de380518d582c6bb3e88b1d88908e71565f191119f4cadc7de4e30cf5ed
            signature:
              keyId: default
              value: UhXnkSnm5A3QU4CVrKGbP3FcTWkShnitn86psJprJpqgEBnsXWzDcZ+ktSO0FJek1X+n9DvO3SjR+WvSofS1CA==
              signedAt: 2026-09-21T05:40:28.555Z
              algorithm: ed25519
          status:
            state: FINALIZED
            releasedAt: 2026-09-21T05:40:32.743Z
            finalizedAt: 2026-09-21T05:40:32.855Z
            finalizationMode: INLINE
            trustRecordId: a3c8d068-af6a-46c7-9976-e43612d5ab9c
    execution-intent-finalize-response.schema:
      title: Finalize Execution Intent Response
      description: "Response returned by POST
        /execution-intents/{businessTransactionId}/finalize. The finalize
        operation never calls a connector. It is safe to run more than once:
        FINALIZED means a Trust Record was rebuilt by this call,
        ALREADY_FINALIZED means one already existed and nothing was built."
      type: object
      additionalProperties: false
      required:
        - outcome
        - businessTransactionId
        - trustRecordId
        - trustRecord
      properties:
        outcome:
          type: string
          enum:
            - FINALIZED
            - ALREADY_FINALIZED
        businessTransactionId:
          type: string
        trustRecordId:
          type: string
          description: Identifier of the Execution Trust Record. Equal to
            trustRecord.trustRecordId.
        trustRecord:
          $ref: "#/components/schemas/execution-trust-record.schema"
    execution-intent-resolve-request.schema:
      title: Resolve Execution Intent Request
      description: Request body for POST
        /execution-intents/{businessTransactionId}/resolve. Both fields are
        required. The note is required on purpose, because it is the only record
        of what the operator found at the connector.
      type: object
      additionalProperties: false
      required:
        - resolution
        - note
      properties:
        resolution:
          type: string
          enum:
            - NOT_EXECUTED
            - EXECUTED
          description: "What you established at the connector. NOT_EXECUTED: the action
            did not run. EXECUTED: it did run."
        note:
          type: string
          minLength: 1
          maxLength: 2000
          description: What you checked and found, for example the connector and the
            reference you looked at. Surrounding space is trimmed.
      examples:
        - resolution: NOT_EXECUTED
          note: Checked the Paytm dashboard for this order. No refund exists.
    execution-intent-resolve-response.schema:
      title: Resolve Execution Intent Response
      description: "Response returned by POST
        /execution-intents/{businessTransactionId}/resolve. RESOLVED means this
        call closed the intent. ALREADY_RESOLVED means it was already closed and
        nothing was changed: the original resolution, note and author are
        returned, never the new request's."
      type: object
      additionalProperties: false
      required:
        - outcome
        - businessTransactionId
        - intent
        - status
      properties:
        outcome:
          type: string
          enum:
            - RESOLVED
            - ALREADY_RESOLVED
        businessTransactionId:
          type: string
        intent:
          $ref: "#/components/schemas/execution-intent.schema"
        status:
          $ref: "#/components/schemas/execution-intent-status.schema"
    audit-verify-request.schema:
      title: Audit Verify Request
      description: "Request body for POST /audit/verify: the signed
        caller-authentication audit event and its stored signature, not a lookup
        by ID. Checked structurally on the server before signature verification
        runs. Verification operates on canonical bytes and the signature alone,
        never on event.type."
      type: object
      additionalProperties: false
      required:
        - event
        - signature
      properties:
        event:
          type: object
          description: The caller-authentication audit event as stored. Only type,
            occurredAt, and route are structurally required; the event may carry
            further fields depending on what produced it.
          additionalProperties: true
          required:
            - type
            - occurredAt
            - route
          properties:
            type:
              type: string
            occurredAt:
              type: string
              format: date-time
            route:
              type: string
        signature:
          type: object
          description: The signature stored alongside the event. Only production
            (Supabase) audit sinks sign; in-memory test sinks never produce a
            genuine one of these.
          additionalProperties: true
          required:
            - algorithm
            - keyId
            - value
          properties:
            algorithm:
              type: string
              examples:
                - ed25519
            keyId:
              type: string
            value:
              type: string
              description: Base64-encoded signature value.
      examples:
        - event:
            type: caller_authenticated
            occurredAt: 2026-09-15T03:00:00.000Z
            route: /version
          signature:
            algorithm: ed25519
            keyId: default
            value: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
    receipt-request.schema:
      title: Receipt Request
      description: Request payload for POST /receipt. businessTransactionId is
        required and must be a valid UUID; both are rejected with a 400, with
        distinct messages depending on which check fails.
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
      properties:
        businessTransactionId:
          type: string
          format: uuid
          description: Business Transaction to generate a Receipt for. Its Execution Trust
            Record must already have a successful (VERIFIED) Verification, or
            the request fails with a 409.
    receipt-response.schema:
      title: Receipt Response
      description: Response returned by POST /receipt and GET
        /receipt/latest/{businessTransactionId}. A bare Receipt, no wrapper.
      $ref: "#/components/schemas/receipt.schema"
    trust-records-list-response.schema:
      title: Trust Records List Response
      description: Response returned by GET /trust-records (bulk/compliance export). A
        bare array, no pagination envelope (no page/pageSize/totalItems
        metadata) -- the same shape convention as GET /transactions. Each item
        is a full Execution Trust Record, the same shape GET
        /trust-records/{businessTransactionId} returns for one.
      type: array
      items:
        $ref: "#/components/schemas/execution-trust-record-response.schema"
    replay-request.schema:
      title: Replay Request
      description: Request payload for POST /replay. businessTransactionId is required
        (rejected with a 400 if missing or empty), unlike POST /verify and POST
        /receipt, this route does not additionally validate UUID format.
      type: object
      additionalProperties: true
      required:
        - businessTransactionId
      properties:
        businessTransactionId:
          type: string
          description: Business Transaction to replay.
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
    replay-response.schema:
      title: Replay Response
      description: "Response returned by POST /replay. A deliberately small shape
        distinct from the Execution Trust Record it replays: it re-verifies the
        record's stored signature via VerificationCrypto and reports only
        whether that succeeded, alongside the hash it checked."
      type: object
      additionalProperties: false
      required:
        - businessTransactionId
        - trustRecordHash
        - verified
      properties:
        businessTransactionId:
          type: string
          description: Business Transaction that was replayed.
        trustRecordHash:
          type: string
          description: Hash of the Execution Trust Record that was replayed.
        verified:
          type: boolean
          description: Whether the Execution Trust Record's stored signature verified
            successfully.
      examples:
        - businessTransactionId: eed2a972-1bf5-4166-8472-761f76fbf1b2
          trustRecordHash: 989aef83d595202ec02bb338ac00461abec0541098ea065f965b5cde342d3b25
          verified: true
    policy-validate-request.schema:
      title: Policy Validate Request
      description: Request payload for POST /policies/validate. policyId and
        policyVersion are each required non-empty strings, checked in that
        order; each missing/empty field is rejected with its own 400 and a
        distinct message.
      type: object
      additionalProperties: true
      required:
        - policyId
        - policyVersion
      properties:
        policyId:
          type: string
          minLength: 1
          description: Policy name, matching the directory name under the configured
            policy directory.
        policyVersion:
          type: string
          minLength: 1
          description: Policy version, matching the version subdirectory.
      examples:
        - policyId: vendor-payment
          policyVersion: 2.0.0
    policy-validate-response.schema:
      title: Policy Validate Response
      description: Response returned by POST /policies/validate on every status code
        (200, 400, and 404 all use this exact shape). This endpoint does NOT use
        the shared Error envelope (../common/error.schema.json) for its failure
        responses.
      type: object
      additionalProperties: false
      required:
        - valid
        - errors
      properties:
        valid:
          type: boolean
          description: Whether the referenced policy exists and is readable.
        errors:
          type: array
          description: Empty when valid is true; otherwise exactly one human-readable
            message.
          items:
            type: string
      examples:
        - valid: true
          errors: []
        - valid: false
          errors:
            - Policy 'does-not-exist' version '9.9.9' was not found.
        - valid: false
          errors:
            - policyVersion is required.
    propose-policy-change-request.schema:
      title: Propose Policy Change Request
      description: Request body for POST /policies/{name}/{version}/pending-changes.
      type: object
      additionalProperties: false
      required:
        - proposedContent
        - reason
      properties:
        proposedContent:
          type: object
          description: The full proposed policy.json content, in its entirety. Its
            policyId must equal the {name} path parameter, and its policyVersion
            must match ^[A-Za-z0-9._-]+$ and not be "." or "..". Validated
            against the same rules POST /policies/validate checks.
          additionalProperties: true
        reason:
          type: string
          description: Free-text justification. Required, non-empty.
      examples:
        - proposedContent:
            policyId: openapi-capture-demo
            policyVersion: 1.0.0
            schemaVersion: 1.0.0
            rules:
              - id: always-approve
                condition:
                  always: true
                outcome:
                  action: approve
                  reason: OpenAPI spec capture fixture
          reason: Capture a real Pending Policy Change for the OpenAPI spec
    pending-policy-change.schema:
      title: Pending Policy Change
      description: A proposed change to a policy's content, held in a durable pending
        state until a second, distinct human explicitly approves or rejects it
        (Policy Governance, maker-checker). Only ever moves from
        PENDING_APPROVAL to APPROVED or REJECTED, exactly once.
      type: object
      additionalProperties: true
      required:
        - pendingPolicyChangeId
        - policyName
        - policyVersion
        - proposedContent
        - proposedBy
        - proposedAt
        - status
        - reason
      properties:
        pendingPolicyChangeId:
          type: string
          description: Unique Pending Policy Change identifier.
        policyName:
          type: string
          description: The policy this proposal targets, same identifier as Policy.policyId.
        policyVersion:
          type: string
          description: "The existing version this proposal is a change against (\"the
            version being replaced\"). Not necessarily equal to
            proposedContent's own declared version: an in-place patch and a
            version bump are both legitimate."
        proposedContent:
          type: object
          description: The full proposed policy.json content, in its entirety, not a diff
            or patch.
          additionalProperties: true
        proposedBy:
          type: string
          description: Identity of the proposer (maker). Always a human-authenticated
            (credentialHolderType USER) caller.
        proposedAt:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
        reason:
          type: string
          description: Free-text justification from the proposer, required at creation.
        resolvedBy:
          type: string
          description: Identity of the resolver (checker). Absent while PENDING_APPROVAL.
            Never equal to proposedBy.
        resolvedAt:
          type: string
          format: date-time
          description: Absent while PENDING_APPROVAL.
        rejectionReason:
          type: string
          description: Present only when status is REJECTED.
        coverageWarnings:
          type: array
          description: "Present only when non-empty: uncovered-fact warnings from
            PolicyValidator.findUncoveredFacts against proposedContent."
          items:
            type: string
        ruleConflicts:
          type: array
          description: "Present only when non-empty: advisory rule-conflict warnings from
            PolicyValidator.findRuleConflicts against proposedContent, never
            blocking."
          items:
            type: string
      examples:
        - pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
          policyName: openapi-capture-demo
          policyVersion: 1.0.0
          proposedContent:
            policyId: openapi-capture-demo
            policyVersion: 1.0.0
            schemaVersion: 1.0.0
            rules:
              - id: always-approve
                condition:
                  always: true
                outcome:
                  action: approve
                  reason: OpenAPI spec capture fixture
          proposedBy: human-maker
          proposedAt: 2026-09-15T03:47:13.920Z
          status: APPROVED
          reason: Capture a real Pending Policy Change for the OpenAPI spec
          resolvedBy: human-checker
          resolvedAt: 2026-09-15T03:48:03.586Z
    pending-policy-change-response.schema:
      title: Pending Policy Change Response
      description: Response returned by POST
        /policies/{name}/{version}/pending-changes (201), POST
        /policies/pending-changes/{id}/approve (200), and POST
        /policies/pending-changes/{id}/reject (200). A bare Pending Policy
        Change, no wrapper.
      $ref: "#/components/schemas/pending-policy-change.schema"
    pending-changes-list-response.schema:
      title: Pending Changes List Response
      description: "Response returned by GET /policies/pending-changes. Each entry is
        a Pending Policy Change plus a diff view: current (the live content at
        (policyName, policyVersion) today, or null if that version has never
        been published) and proposed (the change's own proposedContent)."
      type: object
      additionalProperties: false
      required:
        - changes
      properties:
        changes:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/pending-policy-change.schema"
              - type: object
                required:
                  - diff
                properties:
                  diff:
                    type: object
                    additionalProperties: false
                    required:
                      - current
                      - proposed
                    properties:
                      current:
                        description: The live policy.json content today, or null if this version has
                          never been published.
                      proposed:
                        description: The same value as this entry's own proposedContent.
      examples:
        - changes:
            - pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
              policyName: openapi-capture-demo
              policyVersion: 1.0.0
              proposedContent:
                policyId: openapi-capture-demo
                policyVersion: 1.0.0
                schemaVersion: 1.0.0
                rules:
                  - id: always-approve
                    condition:
                      always: true
                    outcome:
                      action: approve
                      reason: OpenAPI spec capture fixture
              proposedBy: human-maker
              proposedAt: 2026-09-15T03:47:13.920Z
              status: PENDING_APPROVAL
              reason: Capture a real Pending Policy Change for the OpenAPI spec
              diff:
                current: null
                proposed:
                  policyId: openapi-capture-demo
                  policyVersion: 1.0.0
                  schemaVersion: 1.0.0
                  rules:
                    - id: always-approve
                      condition:
                        always: true
                      outcome:
                        action: approve
                        reason: OpenAPI spec capture fixture
    policy-change-step-up-authorization.schema:
      title: Policy Change Step-Up Authorization
      description: "Signed envelope proving a checker's explicit, fresh intent to
        approve or reject one specific Pending Policy Change (Policy Governance,
        Layer 4). Produced by PolicyChangeStepUpAuthorizationSigner
        (@parmana/crypto) using the checker's own step-up private key, never the
        bearer API key. Verified server-side against: the checker's registered
        stepUpPublicKey, payload.pendingPolicyChangeId matching the URL's {id},
        payload.action matching the endpoint (approve vs reject),
        payload.expiresAt not yet passed, and payload.nonce not previously seen
        (single-use, replay-rejected on a second attempt with the same
        envelope)."
      type: object
      additionalProperties: true
      required:
        - payload
        - signature
        - keyId
        - algorithm
      properties:
        payload:
          type: object
          additionalProperties: true
          required:
            - version
            - nonce
            - pendingPolicyChangeId
            - action
            - authorizedAt
            - expiresAt
          properties:
            version:
              type: integer
            nonce:
              type: string
              description: Single-use value; a second request replaying the same envelope is
                rejected.
            pendingPolicyChangeId:
              type: string
            action:
              type: string
              enum:
                - approve
                - reject
            authorizedAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
        signature:
          type: string
          description: Base64-encoded signature over the canonical payload, signed with
            the checker's step-up private key.
        keyId:
          type: string
        algorithm:
          type: string
          examples:
            - ed25519
      examples:
        - payload:
            version: 1
            nonce: 343ee454-92c6-4ef9-9cfe-e98329e51f53
            pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
            action: approve
            authorizedAt: 2026-09-15T03:47:51.939Z
            expiresAt: 2026-09-15T03:49:51.939Z
          signature: keOXnzUk8KcGrQTkpXC4Ki8ps3GTvMANwqq96b+toxba07gjkcmpyL/vaPZ7+pPZ1HAOJOOy3rj5XfhcwOTnAw==
          keyId: human-checker-step-up-key
          algorithm: ed25519
    approve-policy-change-request.schema:
      title: Approve Policy Change Request
      description: Request body for POST /policies/pending-changes/{id}/approve.
      type: object
      additionalProperties: false
      required:
        - stepUpAuthorization
      properties:
        stepUpAuthorization:
          $ref: "#/components/schemas/policy-change-step-up-authorization.schema"
      examples:
        - stepUpAuthorization:
            payload:
              version: 1
              nonce: 343ee454-92c6-4ef9-9cfe-e98329e51f53
              pendingPolicyChangeId: 6f4e4020-adf5-4449-938d-27aab58f66b7
              action: approve
              authorizedAt: 2026-09-15T03:47:51.939Z
              expiresAt: 2026-09-15T03:49:51.939Z
            signature: keOXnzUk8KcGrQTkpXC4Ki8ps3GTvMANwqq96b+toxba07gjkcmpyL/vaPZ7+pPZ1HAOJOOy3rj5XfhcwOTnAw==
            keyId: human-checker-step-up-key
            algorithm: ed25519
    reject-policy-change-request.schema:
      title: Reject Policy Change Request
      description: Request body for POST /policies/pending-changes/{id}/reject.
      type: object
      additionalProperties: false
      required:
        - rejectionReason
        - stepUpAuthorization
      properties:
        rejectionReason:
          type: string
          description: Free-text reason. Required, non-empty.
        stepUpAuthorization:
          $ref: "#/components/schemas/policy-change-step-up-authorization.schema"
      examples:
        - rejectionReason: Does not meet the bar for this fixture
          stepUpAuthorization:
            payload:
              version: 1
              nonce: 4807fe45-0379-415b-9455-b8cb84eb5554
              pendingPolicyChangeId: 5617ba3c-fb07-437d-9131-232b84bbb863
              action: reject
              authorizedAt: 2026-09-15T03:48:16.755Z
              expiresAt: 2026-09-15T03:50:16.755Z
            signature: Wu5/oibGCjvkTklGQPiDMDemqxCVbSyuoVT7WHW63rGq8Av5KFTW6vMkUCP03pANv8bDu60bZVuVcTDVmLXpDA==
            keyId: human-checker-step-up-key
            algorithm: ed25519
    approval-issuer.schema:
      title: Approver Key
      description: An approver key the server trusts, or trusted, to sign approvals.
        source is code for keys listed in the server's code, and governed for
        keys added through approver changes.
      type: object
      additionalProperties: false
      required:
        - approverId
        - keyId
        - publicKeyPem
        - revoked
        - source
      properties:
        approverId:
          type: string
        keyId:
          type: string
        publicKeyPem:
          type: string
          description: Ed25519 public key, PEM (SPKI).
        revoked:
          type: boolean
          description: "true: every approval signed with this key is refused."
        source:
          type: string
          enum:
            - code
            - governed
          description: "code: listed in createApprovalIssuerRegistry.ts, changed only by a
            deploy. governed: added through POST /approval-issuers/changes."
        addedByChangeId:
          type: string
          description: "governed only: the approved change that added it."
        addedAt:
          type: string
          format: date-time
          description: governed only.
        revokedByChangeId:
          type: string
          description: "governed and revoked only: the approved change that revoked it."
        revokedAt:
          type: string
          format: date-time
          description: governed and revoked only.
    approval-issuers-list-response.schema:
      title: Approver Keys List Response
      description: "Response of GET /approval-issuers: keys in code first, then keys
        added through approver changes, revoked ones included."
      type: object
      additionalProperties: false
      required:
        - issuers
      properties:
        issuers:
          type: array
          items:
            $ref: "#/components/schemas/approval-issuer.schema"
    approval-issuer-change.schema:
      title: Approver Change
      description: A proposal to add an approver key, or to revoke one added this way,
        and its resolution. One person proposes it; a different person approves
        or rejects it with a step up authorization. Only an approved change
        affects which approvals verify.
      type: object
      additionalProperties: false
      required:
        - changeId
        - action
        - approverId
        - keyId
        - reason
        - proposedBy
        - proposedAt
        - status
      properties:
        changeId:
          type: string
          description: Unique id of the change, a UUID. The step up authorization for
            approve or reject names it in payload.pendingPolicyChangeId.
        action:
          type: string
          enum:
            - add
            - revoke
          description: add trusts a new key; revoke stops trusting a key added this way,
            and every approval it ever signed.
        approverId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
          description: The approver, as named in the payload.issuer.approverId of the
            approvals they sign.
        keyId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
          description: "The approver's key, as named in payload.issuer.keyId. A key id is
            used once: a revoked key id cannot be added again."
        publicKeyPem:
          type: string
          description: The approver's Ed25519 public key, PEM (SPKI), as the server stored
            it. Present on add, absent on revoke.
        reason:
          type: string
          maxLength: 2000
          description: Why, from the proposer.
        proposedBy:
          type: string
          description: The proposer's caller id. Always a human credential.
        proposedAt:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
          description: PENDING_APPROVAL until a second person approves or rejects it; then
            APPROVED or REJECTED, once.
        resolvedBy:
          type: string
          description: Who approved or rejected it. Never the proposer.
        resolvedAt:
          type: string
          format: date-time
        rejectionReason:
          type: string
          description: Present when REJECTED.
      examples:
        - changeId: ba7c5827-5844-4069-94fc-9b438ef08f78
          action: add
          approverId: approver-example
          keyId: approver-example-key-1
          publicKeyPem: |
            -----BEGIN PUBLIC KEY-----
            MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
            -----END PUBLIC KEY-----
          reason: Priya approves refunds for the West region from October.
          proposedBy: human-maker
          proposedAt: 2026-09-28T19:05:59.726Z
          status: APPROVED
          resolvedBy: human-checker
          resolvedAt: 2026-09-28T19:05:59.738Z
    approval-issuer-changes-list-response.schema:
      title: Approver Changes List Response
      description: Response of GET /approval-issuers/changes, newest first.
      type: object
      additionalProperties: false
      required:
        - changes
      properties:
        changes:
          type: array
          items:
            $ref: "#/components/schemas/approval-issuer-change.schema"
    propose-approval-issuer-change-request.schema:
      title: Propose Approver Change Request
      description: Request body for POST /approval-issuers/changes.
      type: object
      additionalProperties: true
      required:
        - action
        - approverId
        - keyId
        - reason
      properties:
        action:
          type: string
          enum:
            - add
            - revoke
        approverId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
        keyId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
        publicKeyPem:
          type: string
          description: Required for add, not allowed for revoke. The .public.pem file
            scripts/generate-approver-key.ts writes.
        reason:
          type: string
          minLength: 1
          maxLength: 2000
      examples:
        - action: add
          approverId: approver-example
          keyId: approver-example-key-1
          publicKeyPem: |
            -----BEGIN PUBLIC KEY-----
            MCowBQYDK2VwAyEAEDuWHf+bbRY7J/Mr0RVmAKYHH5CDijTWhdnISXWLD6Y=
            -----END PUBLIC KEY-----
          reason: Priya approves refunds for the West region from October.
        - action: revoke
          approverId: approver-example
          keyId: approver-example-key-1
          reason: Priya moved to another team.
    approval-issuer-change-response.schema:
      title: Approver Change Response
      description: Response of POST /approval-issuers/changes (201) and of approve and
        reject (200). A bare Approver Change, no wrapper.
      $ref: "#/components/schemas/approval-issuer-change.schema"
    external-connector.schema:
      title: External Connector
      description: "A capability bound to an HTTPS endpoint an operator runs, and to
        the policy that governs it, registered through external connector
        changes (ADR-0013). Nothing is deleted: a revoked registration stays
        listed."
      type: object
      additionalProperties: false
      required:
        - registrationId
        - capability
        - endpointUrl
        - policy
        - allowedParameters
        - timeoutMs
        - status
        - registeredAt
      properties:
        registrationId:
          type: string
          description: The changeId of the approved register change that created it.
        capability:
          type: string
        endpointUrl:
          type: string
        policy:
          type: string
        allowedParameters:
          type: array
          items:
            type: string
        timeoutMs:
          type: integer
        status:
          type: string
          enum:
            - active
            - revoked
          description: At most one active registration per capability.
        registeredAt:
          type: string
          format: date-time
        revokedByChangeId:
          type: string
          description: "revoked only: the approved change that revoked it."
        revokedAt:
          type: string
          format: date-time
          description: revoked only.
    external-connectors-list-response.schema:
      title: External Connectors List Response
      description: "Response of GET /external-connectors: every registration, active
        and revoked."
      type: object
      additionalProperties: false
      required:
        - connectors
      properties:
        connectors:
          type: array
          items:
            $ref: "#/components/schemas/external-connector.schema"
    external-connector-change.schema:
      title: External Connector Change
      description: A proposal to register an external connector, or to revoke the
        active one for a capability, and its resolution (ADR-0013). One person
        proposes it; a different person approves or rejects it with a step up
        authorization. Only an approved change affects which external connectors
        are registered.
      type: object
      additionalProperties: false
      required:
        - changeId
        - action
        - capability
        - reason
        - proposedBy
        - proposedAt
        - status
      properties:
        changeId:
          type: string
          description: Unique id of the change, a UUID. The step up authorization for
            approve or reject names it in payload.pendingPolicyChangeId. An
            approved register change's id is also the registrationId.
        action:
          type: string
          enum:
            - register
            - revoke
          description: register binds the capability to the endpoint; revoke ends the
            active registration for the capability.
        capability:
          type: string
          pattern: ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$
          maxLength: 128
          description: namespace:verb. Never in a built in connector's namespace (paytm,
            hubspot, github, slack, test).
        endpointUrl:
          type: string
          description: "register only. The endpoint as the server stored it, normalized by
            URL parsing: https, a host name with a domain, no IP literal, no
            localhost, no user name, password or fragment, resolving only to
            public addresses. It is the audience of every release to this
            endpoint."
        policy:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,127}$
          description: register only. The name of the policy that governs the capability.
            The version in effect is decided by policy governance.
        allowedParameters:
          type: array
          maxItems: 64
          uniqueItems: true
          items:
            type: string
            pattern: ^[A-Za-z_][A-Za-z0-9_]{0,63}$
          description: register only. The only parameter names Parmana will forward to the
            endpoint.
        timeoutMs:
          type: integer
          minimum: 1000
          maximum: 30000
          description: register only. How long Parmana waits for the endpoint's answer.
            Defaults to 10000.
        reason:
          type: string
          maxLength: 2000
          description: Why, from the proposer.
        proposedBy:
          type: string
          description: The proposer's caller id. Always a human credential.
        proposedAt:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
          description: PENDING_APPROVAL until a second person approves or rejects it; then
            APPROVED or REJECTED, once.
        resolvedBy:
          type: string
          description: Who approved or rejected it. Never the proposer.
        resolvedAt:
          type: string
          format: date-time
        rejectionReason:
          type: string
          description: Present when REJECTED.
      examples:
        - changeId: 1aa68ad8-ab5c-4beb-9ff3-7a6b1fc1ff37
          action: register
          capability: erp:create-invoice
          endpointUrl: https://erp.example.com/parmana/release
          policy: erp-invoice
          allowedParameters:
            - amount
            - currency
            - customerId
          timeoutMs: 10000
          reason: Finance creates invoices in the ERP through Parmana from October.
          proposedBy: human-maker
          proposedAt: 2026-09-30T17:25:46.316Z
          status: APPROVED
          resolvedBy: human-checker
          resolvedAt: 2026-09-30T17:25:46.385Z
    external-connector-changes-list-response.schema:
      title: External Connector Changes List Response
      description: Response of GET /external-connectors/changes, newest first.
      type: object
      additionalProperties: false
      required:
        - changes
      properties:
        changes:
          type: array
          items:
            $ref: "#/components/schemas/external-connector-change.schema"
    propose-external-connector-change-request.schema:
      title: Propose External Connector Change Request
      description: Request body for POST /external-connectors/changes. A register
        carries endpointUrl, policy, allowedParameters and optionally timeoutMs;
        a revoke carries none of them.
      type: object
      additionalProperties: true
      required:
        - action
        - capability
        - reason
      properties:
        action:
          type: string
          enum:
            - register
            - revoke
        capability:
          type: string
          pattern: ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$
          maxLength: 128
        endpointUrl:
          type: string
          maxLength: 2048
          description: Required for register. https only, a public host name.
        policy:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,127}$
          description: Required for register.
        allowedParameters:
          type: array
          maxItems: 64
          uniqueItems: true
          items:
            type: string
            pattern: ^[A-Za-z_][A-Za-z0-9_]{0,63}$
          description: Required for register. May be empty.
        timeoutMs:
          type: integer
          minimum: 1000
          maximum: 30000
          default: 10000
        reason:
          type: string
          minLength: 1
          maxLength: 2000
      examples:
        - action: register
          capability: erp:create-invoice
          endpointUrl: https://erp.example.com/parmana/release
          policy: erp-invoice
          allowedParameters:
            - amount
            - currency
            - customerId
          timeoutMs: 10000
          reason: Finance creates invoices in the ERP through Parmana from October.
        - action: revoke
          capability: erp:create-invoice
          reason: The ERP endpoint moves to a new host.
    external-connector-change-response.schema:
      title: External Connector Change Response
      description: Response of POST /external-connectors/changes (201) and of approve
        and reject (200). A bare External Connector Change, no wrapper.
      $ref: "#/components/schemas/external-connector-change.schema"
    business-signal-source.schema:
      title: Business Signal Source
      description: "A business system Parmana asks for the facts a policy declares in
        signalSources (RFC-0023), registered through business signal source
        changes. Nothing is deleted: a revoked registration stays listed."
      type: object
      additionalProperties: false
      required:
        - registrationId
        - name
        - endpointUrl
        - timeoutMs
        - status
        - registeredAt
      properties:
        registrationId:
          type: string
          description: The changeId of the approved register change that created it.
        name:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,62}$
          description: The name a policy's signalSources uses in `source`.
        endpointUrl:
          type: string
          description: The HTTPS endpoint Parmana posts each signed query to. It is the
            query's audience.
        timeoutMs:
          type: integer
        publicKeyPem:
          type: string
          description: The Ed25519 public key, SPKI PEM, the source signs its answers
            with. When present, an answer without a valid signature is refused
            as SOURCE_UNAVAILABLE.
        keyId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
          description: A label for publicKeyPem. When present, an answer signed under
            another keyId is refused.
        status:
          type: string
          enum:
            - active
            - revoked
          description: At most one active registration per name.
        registeredAt:
          type: string
          format: date-time
        revokedByChangeId:
          type: string
          description: "revoked only: the approved change that revoked it."
        revokedAt:
          type: string
          format: date-time
          description: revoked only.
    business-signal-sources-list-response.schema:
      title: Business Signal Sources List Response
      description: "Response of GET /business-signal-sources: every registration,
        active and revoked."
      type: object
      additionalProperties: false
      required:
        - sources
      properties:
        sources:
          type: array
          items:
            $ref: "#/components/schemas/business-signal-source.schema"
    business-signal-source-change.schema:
      title: Business Signal Source Change
      description: A proposal to register a business signal source, or to revoke the
        active one for a name, and its resolution (RFC-0023 phase 2). One person
        proposes it; a different person approves or rejects it with a step up
        authorization. Only an approved change affects which sources Parmana
        asks.
      type: object
      additionalProperties: false
      required:
        - changeId
        - action
        - name
        - reason
        - proposedBy
        - proposedAt
        - status
      properties:
        changeId:
          type: string
          description: Unique id of the change, a UUID. The step up authorization for
            approve or reject names it in payload.pendingPolicyChangeId. An
            approved register change's id is also the registrationId.
        action:
          type: string
          enum:
            - register
            - revoke
          description: register names a source and its endpoint; revoke ends the active
            registration for the name.
        name:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,62}$
          description: The name a policy's signalSources uses in `source`.
        endpointUrl:
          type: string
          description: "register only. The endpoint as the server stored it, normalized by
            URL parsing: https, a host name with a domain, no IP literal, no
            localhost, no user name, password or fragment, resolving only to
            public addresses."
        timeoutMs:
          type: integer
          minimum: 1000
          maximum: 30000
          description: register only. How long Parmana waits for the source's answer.
            Defaults to 10000.
        publicKeyPem:
          type: string
          description: register only, optional. The Ed25519 public key the source signs
            its answers with, as the server stored it (SPKI PEM).
        keyId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
          description: register only, optional, and only with publicKeyPem.
        reason:
          type: string
          maxLength: 2000
          description: Why, from the proposer.
        proposedBy:
          type: string
          description: The proposer's caller id. Always a human credential.
        proposedAt:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
          description: PENDING_APPROVAL until a second person approves or rejects it; then
            APPROVED or REJECTED, once.
        resolvedBy:
          type: string
          description: Who approved or rejected it. Never the proposer.
        resolvedAt:
          type: string
          format: date-time
        rejectionReason:
          type: string
          description: Present when REJECTED.
      examples:
        - changeId: 5ba7d3a6-d56d-4e90-b588-402d44aeee0c
          action: register
          name: erp
          endpointUrl: https://orders.example.com/parmana/signals
          timeoutMs: 5000
          publicKeyPem: |
            -----BEGIN PUBLIC KEY-----
            MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
            -----END PUBLIC KEY-----
          keyId: erp-2026-10
          reason: Release goods only against invoices the ERP says are paid.
          proposedBy: source-maker
          proposedAt: 2026-10-11T02:58:24.725Z
          status: APPROVED
          resolvedBy: source-checker
          resolvedAt: 2026-10-11T02:58:24.745Z
    business-signal-source-changes-list-response.schema:
      title: Business Signal Source Changes List Response
      description: Response of GET /business-signal-sources/changes, newest first.
      type: object
      additionalProperties: false
      required:
        - changes
      properties:
        changes:
          type: array
          items:
            $ref: "#/components/schemas/business-signal-source-change.schema"
    propose-business-signal-source-change-request.schema:
      title: Propose Business Signal Source Change Request
      description: Request body for POST /business-signal-sources/changes. A register
        carries endpointUrl and optionally timeoutMs, publicKeyPem and keyId; a
        revoke carries none of them.
      type: object
      additionalProperties: true
      required:
        - action
        - name
        - reason
      properties:
        action:
          type: string
          enum:
            - register
            - revoke
        name:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,62}$
        endpointUrl:
          type: string
          maxLength: 2048
          description: Required for register. https only, a public host name.
        timeoutMs:
          type: integer
          minimum: 1000
          maximum: 30000
          default: 10000
        publicKeyPem:
          type: string
          maxLength: 1000
          description: "Optional, register only. An Ed25519 public key in PEM.
            Recommended: without it, Parmana trusts the answer on the strength
            of the pinned HTTPS connection alone."
        keyId:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
          description: Optional, register only, and only with publicKeyPem.
        reason:
          type: string
          minLength: 1
          maxLength: 2000
      examples:
        - action: register
          name: erp
          endpointUrl: https://orders.example.com/parmana/signals
          timeoutMs: 5000
          publicKeyPem: |
            -----BEGIN PUBLIC KEY-----
            MCowBQYDK2VwAyEAsgWd/PtRf9OsENif9bgJegghUpmEOOZN5NROxc3zpPc=
            -----END PUBLIC KEY-----
          keyId: erp-2026-10
          reason: Release goods only against invoices the ERP says are paid.
        - action: revoke
          name: erp
          reason: The ERP moves to a new host.
    business-signal-source-change-response.schema:
      title: Business Signal Source Change Response
      description: Response of POST /business-signal-sources/changes (201) and of
        approve and reject (200). A bare Business Signal Source Change, no
        wrapper.
      $ref: "#/components/schemas/business-signal-source-change.schema"
    authority-grant.schema:
      title: Authority Grant
      description: "Authority granted to one caller for one capability, within limits,
        until an expiry (RFC-0023 phase 3), made through authority grant
        changes. A policy with requireAuthorityGrant decides a request only when
        its authenticated caller holds a grant in force and the request is
        within its limits. Nothing is deleted: a revoked grant stays listed."
      type: object
      additionalProperties: false
      required:
        - grantId
        - callerId
        - capability
        - validFrom
        - validUntil
        - status
        - grantedAt
      properties:
        grantId:
          type: string
          description: The changeId of the approved grant change that created it.
        callerId:
          type: string
          description: The callerId of the API key the grant is for.
        capability:
          type: string
          pattern: ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$
        limits:
          type: object
          description: "Limits on the request, by Intent path: target or
            parameters.<name>. min and max (inclusive) apply to a number there;
            oneOf lists the only values allowed there. A request whose value is
            missing, of another type, or outside a limit is NOT_AUTHORIZED."
          maxProperties: 32
          propertyNames:
            pattern: ^(?:target|parameters\.[A-Za-z_][A-Za-z0-9_]{0,63})$
          additionalProperties:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
              min:
                type: number
              max:
                type: number
              oneOf:
                type: array
                minItems: 1
                maxItems: 64
                items:
                  type:
                    - string
                    - number
        validFrom:
          type: string
          format: date-time
          description: "From when it holds: the proposal's validFrom, or the approval time."
        validUntil:
          type: string
          format: date-time
          description: Until when it holds. After this, requests are refused as
            AUTHORITY_EXPIRED.
        status:
          type: string
          enum:
            - active
            - revoked
          description: At most one active grant per caller and capability. Active says not
            revoked; check validFrom and validUntil for whether it is in force.
        grantedAt:
          type: string
          format: date-time
        revokedByChangeId:
          type: string
          description: "revoked only: the approved change that revoked it."
        revokedAt:
          type: string
          format: date-time
          description: revoked only.
    authority-grants-list-response.schema:
      title: Authority Grants List Response
      description: "Response of GET /authority-grants: every grant, active and revoked."
      type: object
      additionalProperties: false
      required:
        - grants
      properties:
        grants:
          type: array
          items:
            $ref: "#/components/schemas/authority-grant.schema"
    authority-grant-change.schema:
      title: Authority Grant Change
      description: A proposal to grant a caller authority for a capability, or to
        revoke the active grant, and its resolution (RFC-0023 phase 3). One
        person proposes it; a different person approves or rejects it with a
        step up authorization; neither may be the caller the grant is for.
      type: object
      additionalProperties: false
      required:
        - changeId
        - action
        - callerId
        - capability
        - reason
        - proposedBy
        - proposedAt
        - status
      properties:
        changeId:
          type: string
          description: Unique id of the change, a UUID. The step up authorization for
            approve or reject names it in payload.pendingPolicyChangeId. An
            approved grant change's id is also the grantId.
        action:
          type: string
          enum:
            - grant
            - revoke
        callerId:
          type: string
        capability:
          type: string
          pattern: ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$
        limits:
          type: object
          description: "grant only, optional. Limits on the request, by Intent path:
            target or parameters.<name>. min and max (inclusive) apply to a
            number there; oneOf lists the only values allowed there. A request
            whose value is missing, of another type, or outside a limit is
            NOT_AUTHORIZED."
          maxProperties: 32
          propertyNames:
            pattern: ^(?:target|parameters\.[A-Za-z_][A-Za-z0-9_]{0,63})$
          additionalProperties:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
              min:
                type: number
              max:
                type: number
              oneOf:
                type: array
                minItems: 1
                maxItems: 64
                items:
                  type:
                    - string
                    - number
        validFrom:
          type: string
          format: date-time
          description: grant only, optional. Without it the grant starts when it is
            approved.
        validUntil:
          type: string
          format: date-time
          description: grant only, required. At most 366 days after validFrom (or the
            proposal).
        reason:
          type: string
          maxLength: 2000
        proposedBy:
          type: string
          description: The proposer's caller id. Always a human credential, never the
            grantee.
        proposedAt:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - PENDING_APPROVAL
            - APPROVED
            - REJECTED
        resolvedBy:
          type: string
          description: Who approved or rejected it. Never the proposer or the grantee.
        resolvedAt:
          type: string
          format: date-time
        rejectionReason:
          type: string
          description: Present when REJECTED.
      examples:
        - changeId: 9f3cf733-a8ed-487c-bb58-a53950388cdf
          action: grant
          callerId: warehouse-agent
          capability: erp:release-goods
          limits:
            parameters.amount:
              max: 100000
            parameters.currency:
              oneOf:
                - INR
          validUntil: 2026-12-30T00:00:00.000Z
          reason: The warehouse agent releases goods for paid invoices up to 100000 INR
            this quarter.
          proposedBy: grant-maker
          proposedAt: 2026-10-11T03:29:05.220Z
          status: APPROVED
          resolvedBy: grant-checker
          resolvedAt: 2026-10-11T03:29:05.247Z
    authority-grant-changes-list-response.schema:
      title: Authority Grant Changes List Response
      description: Response of GET /authority-grants/changes, newest first.
      type: object
      additionalProperties: false
      required:
        - changes
      properties:
        changes:
          type: array
          items:
            $ref: "#/components/schemas/authority-grant-change.schema"
    propose-authority-grant-change-request.schema:
      title: Propose Authority Grant Change Request
      description: Request body for POST /authority-grants/changes. A grant carries
        validUntil and optionally limits and validFrom; a revoke carries none of
        them.
      type: object
      additionalProperties: true
      required:
        - action
        - callerId
        - capability
        - reason
      properties:
        action:
          type: string
          enum:
            - grant
            - revoke
        callerId:
          type: string
          pattern: ^\S{1,256}$
          description: The callerId of the API key the grant is for. Not your own.
        capability:
          type: string
          pattern: ^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*:[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$
          maxLength: 128
        limits:
          type: object
          description: "Optional, grant only. Limits on the request, by Intent path:
            target or parameters.<name>. min and max (inclusive) apply to a
            number there; oneOf lists the only values allowed there. A request
            whose value is missing, of another type, or outside a limit is
            NOT_AUTHORIZED."
          maxProperties: 32
          propertyNames:
            pattern: ^(?:target|parameters\.[A-Za-z_][A-Za-z0-9_]{0,63})$
          additionalProperties:
            type: object
            additionalProperties: false
            minProperties: 1
            properties:
              min:
                type: number
              max:
                type: number
              oneOf:
                type: array
                minItems: 1
                maxItems: 64
                items:
                  type:
                    - string
                    - number
        validFrom:
          type: string
          format: date-time
          description: Optional, grant only.
        validUntil:
          type: string
          format: date-time
          description: Required for grant. In the future, after validFrom, and at most 366
            days after it.
        reason:
          type: string
          minLength: 1
          maxLength: 2000
      examples:
        - action: grant
          callerId: warehouse-agent
          capability: erp:release-goods
          limits:
            parameters.amount:
              max: 100000
            parameters.currency:
              oneOf:
                - INR
          validUntil: 2026-12-30T00:00:00.000Z
          reason: The warehouse agent releases goods for paid invoices up to 100000 INR
            this quarter.
        - action: revoke
          callerId: warehouse-agent
          capability: erp:release-goods
          reason: The warehouse agent's key is being rotated.
    authority-grant-change-response.schema:
      title: Authority Grant Change Response
      description: Response of POST /authority-grants/changes (201) and of approve and
        reject (200). A bare Authority Grant Change, no wrapper.
      $ref: "#/components/schemas/authority-grant-change.schema"
    sandbox-approval-request.schema:
      title: Sandbox Approval Request
      description: Asks the sandbox's demo approver to approve one sandbox:receipt request.
      type: object
      additionalProperties: false
      required:
        - capability
        - resourceId
      properties:
        capability:
          type: string
          const: sandbox:receipt
          description: The only capability the demo approver signs for.
        resourceId:
          type: string
          minLength: 1
          maxLength: 200
          description: The target of the request you will send, its intent.target.
    signed-approval.schema:
      title: Signed Approval
      description: A person's approval of one action, signed with their approver key.
        A request carries it in signals.approvalArtifact when its policy
        declares approvalSignals. The server checks the issuer is a trusted
        approver key, the signature, the capability, the resource, the scope and
        the expiry, and consumes the nonce, so an approval is used once.
      type: object
      additionalProperties: false
      required:
        - payload
        - signature
      properties:
        payload:
          type: object
          additionalProperties: false
          required:
            - version
            - approvalId
            - issuer
            - issuedAt
            - expiresAt
            - capability
            - resourceId
            - scope
            - nonce
          properties:
            version:
              const: 1
            approvalId:
              type: string
            issuer:
              type: object
              additionalProperties: false
              required:
                - approverId
                - keyId
              properties:
                approverId:
                  type: string
                keyId:
                  type: string
            issuedAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
            capability:
              type: string
              description: The action approved, the request's intent.action.
            resourceId:
              type: string
              description: The resource approved, as the policy's approvalSignals names it,
                for example the request's target.
            scope:
              type: object
              additionalProperties: false
              required:
                - field
                - comparator
                - value
              description: "What exactly is approved: an amount limit, or exactly this
                resource."
              properties:
                field:
                  type: string
                comparator:
                  type: string
                  enum:
                    - eq
                    - lte
                    - gte
                    - lt
                    - gt
                    - between
                value:
                  oneOf:
                    - type: number
                    - type: string
                    - type: object
                      additionalProperties: false
                      required:
                        - min
                        - max
                      properties:
                        min:
                          type: number
                        max:
                          type: number
            constraints:
              type: object
            nonce:
              type: string
              description: Consumed when the approval is used, so it is accepted once.
        signature:
          type: object
          additionalProperties: false
          required:
            - algorithm
            - keyId
            - value
            - signedAt
          properties:
            algorithm:
              type: string
              const: ed25519
            keyId:
              type: string
            value:
              type: string
              description: Base64 Ed25519 signature over the canonical payload.
            signedAt:
              type: string
              format: date-time
  securitySchemes:
    operatorBearer:
      type: apiKey
      in: header
      name: Authorization
      description: "A human key (credentialHolderType: USER) issued by the operator.
        Sent as Authorization: Bearer <key>. The public sandbox and its demo key
        are refused on these routes, so no key is prefilled here."
    bearerAuth:
      type: apiKey
      in: header
      name: Authorization
      x-default: Bearer 2VfYWCzt_cBAPK-8uufX6ordfY2JuQhFPsohuEumKME
      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.
  responses:
    Unauthorized:
      description: The `Authorization` header is missing, is not a bearer key, or
        names a key the server does not know. Nothing ran. Send a valid key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/error.schema"
          examples:
            authRequired:
              summary: Real captured response, missing or invalid Authorization header
              value:
                error: authentication required
    RateLimited:
      description: Too many requests from this caller on this route in the last
        minute. The default limits are 30 per caller per minute on `/execute`,
        300 per IP address on `/health` and `/ready`, and 60 per IP address
        shared by the unauthenticated verification and handbook routes. The
        request never reached the handler, so nothing was recorded or executed.
        Wait the number of seconds in the `Retry-After` header, then retry.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/error.schema"
          examples:
            rateLimited:
              summary: Rate limit exceeded
              value:
                error: Rate limit exceeded. Try again later.
                code: RATE_LIMITED
    SigningUnavailable:
      description: "Refused before release: nothing was executed.
        `SIGNING_UNAVAILABLE`: the server could not prove it can sign a Trust
        Record. `AUDIT_UNAVAILABLE`: the caller audit trail could not be
        written. `EXECUTION_INTENT_UNAVAILABLE`: the signed Execution Intent
        could not be stored. For these three, retry later with a new
        `businessTransactionId`, because the first one was already accepted.
        `CONNECTOR_NOT_REGISTERED`: no connector is configured for this action
        on this deployment; the operator must configure it before any retry."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/error.schema"
          examples:
            executionIntentUnavailable:
              summary: The signed Execution Intent could not be stored, nothing was executed
                (real captured response)
              value:
                error: "The signed Execution Intent for business transaction
                  '2c3cae0f-fd3c-4962-8147-83f3710a2fc9' could not be created
                  and stored; refusing to release the action. Nothing was
                  executed. Cause: new row for relation \"execution_intents\"
                  violates check constraint \"sbx_block_intents\""
                code: EXECUTION_INTENT_UNAVAILABLE
            signingUnavailable:
              summary: Evidence signing is unhealthy, nothing was executed
              value:
                error: Evidence signing is unavailable; refusing to release the action because a
                  signed Execution Trust Record could not be produced. Nothing
                  was executed.
                code: SIGNING_UNAVAILABLE
            auditUnavailable:
              summary: The caller audit trail could not be written
              value:
                error: Caller authentication audit trail is unavailable; refusing to proceed
                  without an audit record.
                code: AUDIT_UNAVAILABLE
            connectorNotRegistered:
              summary: No connector is registered for the action, nothing was executed
              value:
                error: No connector is registered for capability 'paytm:refund' on this
                  deployment. Nothing was executed. The operator must configure
                  the connector.
                code: CONNECTOR_NOT_REGISTERED
