> ## 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 authority grant change

> 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 re...



## OpenAPI

````yaml openapi.bundled.yaml POST /authority-grants/changes/{id}/approve
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: Proprietary, source-available for evaluation only, see LICENSE
    url: https://github.com/pavancharak/parmana/blob/main/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: 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:
  /authority-grants/changes/{id}/approve:
    post:
      tags:
        - Authority Grants
      summary: Approve an authority grant change
      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`.
      operationId: approveAuthorityGrantChange
      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)
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
    authority-grant-change-response.schema:
      $ref: '#/components/schemas/authority-grant-change.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.
    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
    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
    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'
  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
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      x-default: 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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.