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

# Approve an approver change

> Approves an approver change and applies it in one step: add trusts the key from the next approval checked, revoke refuses every approval the key ever signed from the next request on.



## OpenAPI

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


    **Every route requires a caller bearer key**, except the liveness/readiness
    probes and documentation/verification routes that must be reachable with no
    credential: GET /health, GET /ready, GET /openapi.yaml, GET /documentation,
    GET /reference, POST /refusal/verify, POST /execution-intents/verify, POST
    /audit/verify, GET /keys/{keyId}, and GET /.well-known/jwks.json. Send
    `Authorization: Bearer <key>` on every other request. Keys are issued by
    `scripts/generate-api-key.ts` and configured server-side via
    `PARMANA_API_KEYS`; only a hash of each key is ever held by the server,
    verified in constant time. A missing or invalid credential returns 401
    before a Business Transaction is even constructed, independent of Policy
    evaluation and gateway attestation, see
    `packages/api/src/middleware/caller-auth.ts` and
    [Authentication](/api-reference/authentication). Local development may set
    `PARMANA_AUTH_DISABLED=true` to skip this middleware entirely; that flag
    must never be set in a real deployment.
  contact:
    name: Parmana Systems
    email: founder@parmanasystems.com
  license:
    name: Proprietary, source-available for evaluation only, see LICENSE
    url: https://github.com/pavancharak/AgentLabsBuildathon/blob/main/LICENSE
servers:
  - url: https://parmana-api-real.vercel.app
    description: >-
      Production (real, deployed instance -- the docs site playground uses this
      by default)
  - 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: 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:
  /approval-issuers/changes/{id}/approve:
    post:
      tags:
        - Approvers
      summary: Approve an approver change
      description: >
        Approves an approver change and applies it in one step: add trusts the
        key from the next approval checked, revoke refuses every approval the
        key ever signed from the next request on. Requires, in order: a human
        credential, not the proposer (403 SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE),
        and a step up authorization signed with the checker step up key for this
        change id and action approve, used once (403
        STEP_UP_AUTHORIZATION_INVALID). It is the same step up authorization
        policy changes use, with the change id in payload.pendingPolicyChangeId.
      operationId: approveApprovalIssuerChange
      parameters:
        - name: id
          in: path
          required: true
          description: The changeId.
          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.schema'
              examples:
                approved:
                  summary: Real captured response
                  value:
                    changeId: ba7c5827-5844-4069-94fc-9b438ef08f78
                    action: add
                    approverId: manager-priya
                    keyId: manager-priya-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'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            NON_HUMAN_CALLER_DENIED, SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE, or
            STEP_UP_AUTHORIZATION_INVALID.
          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: No approver change has this id (APPROVAL_ISSUER_CHANGE_NOT_FOUND).
          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'
components:
  schemas:
    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
    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: manager-priya
          keyId: manager-priya-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'
    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
      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
    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
  responses:
    Unauthorized:
      description: >-
        Missing or invalid caller credential (StaticKeyAuthenticator returned no
        identity). Real captured response,
        packages/api/src/middleware/caller-auth.ts.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/error.schema'
          examples:
            authRequired:
              summary: Real captured response, missing or invalid Authorization header
              value:
                error: authentication required
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        Caller API key issued by scripts/generate-api-key.ts. Sent as
        Authorization: Bearer <key>. Verified against a stored SHA-256 hash in
        constant time by packages/api/src/auth/StaticKeyAuthenticator.ts.
        Required on every route not listed as exempt in this document's
        top-level description. See /api-reference/authentication.

````