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

# Get a demo approval (sandbox only)

> **Sandbox only.** Returns an approval signed by the sandbox's demo approver, so you can try an approved request from a browser.



## OpenAPI

````yaml openapi.bundled.yaml POST /sandbox/approvals
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.


    **Start here:** 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/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: External Connectors
    description: >-
      Maker-checker for binding a capability to an operator's HTTPS endpoint and
      its policy, without a deploy (ADR-0013)
  - 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:
  /sandbox/approvals:
    post:
      tags:
        - Sandbox
      summary: Get a demo approval (sandbox only)
      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.
      operationId: createSandboxApproval
      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:
    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
    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
  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
  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.

````