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

# SDK — approve a proposal

> Records the caller's consent, signed with their session key. Requires
step-up authentication: approving is the act that authorizes the mutation,
so it carries the same weight as performing it.

Recording an approval does not apply the change — call execute once the
requirements are satisfied. An initiator can never approve their own
intent; the enclave refuses it and so does this endpoint.




## OpenAPI

````yaml https://app.dynamic.xyz/api-docs/public-api.yaml post /sdk/{environmentId}/businessAccounts/{businessAccountId}/proposals/{proposalId}/approvals
openapi: 3.0.1
info:
  title: Dashboard API
  description: Dashboard API documentation
  version: 1.0.0
servers:
  - url: https://app.dynamicauth.com/api/v0
  - url: https://app.dynamic.xyz/api/v0
  - url: http://localhost:3333/api/v0
security: []
tags:
  - name: Analytics
    description: Query usage analytics and event counts for your environment.
  - name: SDK
    description: SDK-facing endpoints consumed by embedded Dynamic clients.
  - name: Server
    description: Server-side SDK endpoints authenticated by environment API tokens.
  - name: Organizations
    description: >-
      Manage organizations — the top-level container for all environments and
      projects.
  - name: Projects
    description: Manage projects that group related environments under an organization.
  - name: Environments
    description: >-
      Manage environments (live and sandbox) where your authentication
      configuration lives.
  - name: Users
    description: List, search, and manage authenticated users within an environment.
  - name: Invites
    description: >-
      Manage invitation links that grant specific external users access to your
      environment.
  - name: Tokens
    description: >-
      Create and manage API tokens used to authenticate requests to the Dynamic
      API.
  - name: Origins
    description: >-
      Configure allowed origins (CORS) for SDK and API access to your
      environment.
  - name: Allowlists
    description: >-
      Manage allowlists to control which wallet addresses or email addresses can
      access your environment.
  - name: Wallets
    description: View and manage wallets linked to users in your environment.
  - name: Members
    description: Manage team members and their roles within an organization.
  - name: Sessions
    description: View and revoke active user sessions within an environment.
  - name: Settings
    description: Read and update environment-level configuration settings.
  - name: Exchanges
    description: >-
      Configure exchange integrations (Coinbase, Binance, etc.) for fiat on-ramp
      within your environment.
  - name: Yield
    description: List yield vaults registered for your environment.
  - name: Providers
    description: >-
      List, enable, disable, and configure authentication providers (OAuth,
      email, wallet, SMS, etc.) for your environment.
  - name: Captcha
    description: >-
      Configure captcha verification (provider, site key, secret key) for your
      environment.
  - name: Gates
    description: >-
      Define access gates that restrict environment entry to users who meet
      specific criteria.
  - name: Chains
    description: >-
      Manage the blockchain networks available for wallet connection in your
      environment.
  - name: Exports
    description: Export user and wallet data from your environment as downloadable files.
  - name: Events
    description: Browse the event log of actions performed within your environment.
  - name: Webhooks
    description: >-
      Create and manage webhooks to receive real-time events (user.created,
      wallet.linked, etc.) at your endpoint.
  - name: Custom Fields
    description: >-
      Define and manage custom metadata fields collected from users at sign-up
      or login.
  - name: MfaSettings
    description: >-
      Configure multi-factor authentication (MFA) policies for your
      organization.
  - name: Mfa
    description: Manage MFA enrollment and verification for individual users.
  - name: CustomHostnames
    description: >-
      Configure custom hostnames for white-labeling the Dynamic authentication
      experience.
  - name: TestAccount
    description: >-
      Manage test accounts used for automated testing and QA within an
      environment.
  - name: NameServices
    description: >-
      Configure name service integrations (ENS, Unstoppable Domains, etc.) for
      human-readable wallet addresses.
  - name: GlobalWallets
    description: >-
      Manage global wallets shared across multiple environments in an
      organization.
  - name: GlobalWalletConnections
    description: Configure which environments can access and use global wallets.
  - name: GlobalWalletAccessControl
    description: Control which users and roles can access and operate global wallets.
  - name: UserApiTokens
    description: >-
      Create and manage user-scoped API tokens for programmatic access on behalf
      of a user.
  - name: Waas
    description: Wallet-as-a-Service endpoints for creating and managing embedded wallets.
  - name: WalletConnect
    description: Configure WalletConnect integration settings for your environment.
  - name: Checkout
    description: >-
      Accept crypto payments and deposits from any wallet. Settle in any token
      you choose.
  - name: Flow
    description: >-
      Accept crypto payments, deposits, and withdrawals. Amount and destination
      are fixed server-side at create; the post-create lifecycle is driven with
      a capability session token.
  - name: FlowAnalytics
    description: >-
      Query aggregated analytics and paginated transaction lists for the Flow
      product.
  - name: Gasless
    description: >-
      Query billing summaries and paginated transaction lists for sponsored
      (gasless) transactions.
  - name: Custom Networks
    description: >-
      Add and manage custom EVM-compatible networks beyond the built-in chain
      list.
  - name: Chainalysis
    description: >-
      Configure Chainalysis integration for blockchain address screening and
      risk assessment.
  - name: Visits
    description: Track and query user visit activity within your environment.
  - name: ExternalJwt
    description: >-
      Configure external JWT providers so existing auth tokens can be used with
      Dynamic.
  - name: SDK Views
    description: >-
      Manage SDK view configurations that customize the appearance of Dynamic
      modals and flows.
  - name: DeeplinkUrls
    description: Configure deep link URLs for mobile app integration with the Dynamic SDK.
  - name: OrganizationSettings
    description: >-
      Read and update organization-level settings such as approval workflow
      configuration.
  - name: AdminActions
    description: >-
      Manage admin action requests that require approval from another
      administrator.
  - name: ClientGrant
    description: |
      OAuth 2.0 Device Authorization Grant flow (RFC 8628) used by first-party
      Dynamic clients (CLI, MCP server, demo apps, IDE plugins) to obtain a
      revocable, 30-day client JWT without ever seeing the user's dashboard
      session token.
  - name: AuthorizedClients
    description: |
      Manage first-party clients (CLI / MCP / demo / IDE plugins) authorized
      to act as a dashboard user. Surfaced under Profile → Authorized Clients.
  - name: Kyb
    description: >-
      Verify a business's identity via AI Prise passthrough and check KYB tier
      status.
  - name: BusinessAccounts
    description: >-
      Administer the business accounts (multi-operator wallet containers) that
      end users create in an environment.
  - name: ScreeningPolicies
    description: |
      Author per-vendor address screening policies that map TRM and Chainalysis
      signals to block, alert, or allow, plus per-address exceptions. The
      sanctions floor sits underneath every policy and is not configurable.
paths:
  /sdk/{environmentId}/businessAccounts/{businessAccountId}/proposals/{proposalId}/approvals:
    post:
      tags:
        - SDK
      summary: SDK — approve a proposal
      description: >
        Records the caller's consent, signed with their session key. Requires

        step-up authentication: approving is the act that authorizes the
        mutation,

        so it carries the same weight as performing it.


        Recording an approval does not apply the change — call execute once the

        requirements are satisfied. An initiator can never approve their own

        intent; the enclave refuses it and so does this endpoint.
      operationId: sdkApproveBusinessAccountProposal
      parameters:
        - $ref: '#/components/parameters/environmentId'
        - $ref: '#/components/parameters/businessAccountId'
        - $ref: '#/components/parameters/businessAccountProposalId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApproveBusinessAccountProposalSdkRequest'
      responses:
        '200':
          description: Approval recorded; the proposal with its current approvals
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BusinessAccountProposal'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/BusinessAccountForbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - bearerAuth: []
components:
  parameters:
    environmentId:
      in: path
      name: environmentId
      required: true
      description: ID of the environment
      schema:
        $ref: '#/components/schemas/uuid'
    businessAccountId:
      in: path
      name: businessAccountId
      required: true
      description: ID of the business account
      schema:
        $ref: '#/components/schemas/uuid'
    businessAccountProposalId:
      in: path
      name: proposalId
      required: true
      description: ID of the business account proposal
      schema:
        $ref: '#/components/schemas/uuid'
  schemas:
    ApproveBusinessAccountProposalSdkRequest:
      type: object
      description: >
        SDK approveProposal body. The signature is over the proposal's stored
        intent together with `approvedAt`, produced by the approver's session
        key — which never leaves their device, so approving cannot be done on
        their behalf.
      required:
        - approvedAt
        - signature
      properties:
        approvedAt:
          type: string
          format: date-time
          description: |
            Inside the signed payload, so only the approver can set their own.
        signature:
          $ref: '#/components/schemas/BusinessAccountIntentSignature'
    BusinessAccountProposal:
      type: object
      description: >
        A governed mutation waiting on consent. Untrusted workflow state: it
        records what was proposed and who has signed, and the enclave verifies
        every signature when the proposal is executed. The signature is over
        `intent` as stored, so an approver must sign these exact bytes —
        re-deriving the intent would produce a different `intentId` and
        invalidate the approvals already collected.
      required:
        - actionType
        - approvals
        - businessAccountId
        - completeBy
        - createdAt
        - id
        - initiatorUserId
        - intent
        - status
      properties:
        id:
          type: string
          format: uuid
        businessAccountId:
          type: string
          format: uuid
        actionType:
          $ref: '#/components/schemas/BusinessAccountGovernedAction'
        status:
          $ref: '#/components/schemas/BusinessAccountProposalStatus'
        initiatorUserId:
          type: string
          format: uuid
          description: >
            Who proposed it. They can never approve their own intent, so they
            are never counted toward a requirement.
        intent:
          $ref: '#/components/schemas/BusinessAccountMutationIntent'
        completeBy:
          type: string
          format: date-time
          description: >
            The one deadline for the whole proposal, derived from the signed
            intent's `createdAt`. Past it the proposal accepts no further
            approvals AND can no longer be executed.
        consumedAt:
          type: string
          format: date-time
          nullable: true
          description: When it left `pending`; null while still open.
        createdAt:
          type: string
          format: date-time
        approvals:
          type: array
          items:
            $ref: '#/components/schemas/BusinessAccountProposalApprovalRecord'
        autoExecute:
          type: boolean
          description: >
            Whether this applies as soon as its quorum is met, or waits for an
            explicit execute. A copy of the flag inside the signed intent, which
            is the authority. An approver needs this to know whether their
            approval is the one that lands the change.
        subjects:
          type: array
          description: >
            The users this change is about, resolved for display — the member
            being added, the signer being removed, the new owner. Empty for a
            change that names no user, such as `setGovernance`.
          items:
            $ref: '#/components/schemas/BusinessAccountProposalSubject'
        approvalRequirements:
          type: array
          description: >
            Every governance rule matching this proposal, with how close each
            is. Do NOT derive progress from `approvals` — that counts rows,
            including approvers outside a rule's pool and approvers demoted
            since they signed.

            Absent means the tally is unknown (not fetched, or no longer
            pending); an empty array means nothing is outstanding. Treat the two
            differently.
          items:
            $ref: '#/components/schemas/BusinessAccountApprovalRequirementStatus'
    uuid:
      type: string
      pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
      minLength: 36
      maxLength: 36
      example: 95b11417-f18f-457f-8804-68e361f9164f
    BusinessAccountIntentSignature:
      type: string
      pattern: ^[0-9a-fA-F]{128}$
      description: >
        Hex-encoded ECDSA P-256 signature over the canonicalized payload,
        produced by the signer's session key — which never leaves their device,
        so consent cannot be given on their behalf.

        Exactly 128 hex characters. WebCrypto emits the raw r‖s form with both
        halves padded to 32 bytes, so unlike DER the length never varies.
        Constrained here so a malformed value is a 400 at the edge rather than
        an enclave round trip that returns INTENT_SIGNATURE_INVALID, and so an
        unbounded string cannot be persisted against a proposal.
    BusinessAccountGovernedAction:
      type: string
      description: A business-account mutation that an approval requirement can gate.
      enum:
        - addMember
        - removeMember
        - updateMemberRole
        - transferOwnership
        - createWallet
        - linkWallet
        - removeWallet
        - addSignerToWallet
        - removeSignerFromWallet
        - setGovernance
        - defineRole
        - deleteRole
    BusinessAccountProposalStatus:
      type: string
      description: >
        Lifecycle of a governed mutation awaiting approvals. Only `pending`
        accepts approvals; the other three are terminal.
      enum:
        - pending
        - executed
        - vetoed
        - expired
    BusinessAccountMutationIntent:
      type: object
      description: >
        The initiator's proposal, signed with their session key. Built by the
        API, not the client — sign these exact bytes verbatim, because
        re-serializing can change them and invalidate the signature.

        `autoExecute` is inside the signed bytes, so an approver consents to it
        and nothing can flip it afterwards.
      additionalProperties: true
    BusinessAccountProposalApprovalRecord:
      type: object
      description: >
        One approver's recorded consent. Withdrawing sets `revokedAt` rather
        than deleting the row, so a change of mind stays auditable — a revoked
        approval is not forwarded to the enclave and does not count toward a
        requirement.
      required:
        - approvedAt
        - approverUserId
      properties:
        approverUserId:
          type: string
          format: uuid
        approvedAt:
          type: string
          format: date-time
          description: The approver's own signed timestamp, stored as given.
        revokedAt:
          type: string
          format: date-time
          nullable: true
          description: Set when the approver withdrew; null while the consent counts.
    BusinessAccountProposalSubject:
      type: object
      description: >
        A user the proposed change is about, resolved for display. An approver
        is consenting to a change that names people by id, so without this they
        cannot see who they are approving. Derived at read time, never signed —
        the authority for what will be applied is `intent`.
      required:
        - userId
      properties:
        userId:
          type: string
          format: uuid
        email:
          type: string
          nullable: true
          description: >
            Their oldest verified email, null when they have none or are not yet
            a member of this environment.
    BusinessAccountApprovalRequirementStatus:
      type: object
      description: >
        One governance requirement and how close it is to being met. Every
        matching requirement is reported, not only the unmet ones, so a client
        can show per-rule progress rather than a rule appearing only once it is
        short.

        Computed inside the enclave: eligibility resolves from the signed
        document, so whether a given approval counts depends on roles that are
        not readable outside it. A client cannot derive these numbers from the
        approval list.
      required:
        - action
        - approved
        - required
        - requirementId
        - satisfied
      properties:
        action:
          type: string
          description: >
            The change type this rule governs. Requirement ids are unique per
            action only, so a change set spanning two actions can report the
            same id twice.
          example: addMember
        requirementId:
          type: string
        required:
          type: integer
        approved:
          type: integer
          description: >
            Approvals that COUNT toward this requirement — not the raw approval
            count. An approver outside the eligible pool, or one demoted since
            they approved, is not included.
        satisfied:
          type: boolean
          description: >
            Authoritative. `approved >= required` is not sufficient: a mandatory
            approver role with nobody's approval leaves this false with the
            count already met.
        unmetMandatoryRoles:
          type: array
          description: >
            Mandatory approver roles still lacking an approval, present only
            when they are what is holding the requirement back.
          items:
            type: string
    BadRequest:
      type: object
      properties:
        error:
          description: Human-readable error message
          type: string
    Unauthorized:
      type: object
      properties:
        error:
          description: Human-readable error message describing the authentication failure
          type: string
          example: No jwt provided!
    BusinessAccountForbidden:
      allOf:
        - $ref: '#/components/schemas/Forbidden'
        - type: object
          description: >
            Carries the requirement tallies when the caller is authorized but
            consent is outstanding, which is a different thing from being
            refused outright.
          properties:
            approvalRequirements:
              type: array
              items:
                $ref: '#/components/schemas/BusinessAccountApprovalRequirementStatus'
    NotFound:
      type: object
      required:
        - error
        - code
      properties:
        error:
          description: Human-readable error message
          type: string
          example: Not Found
        code:
          description: Machine-readable error code
          type: string
          example: not_found
    Conflict:
      type: object
      properties:
        error:
          description: Human-readable error message
          type: string
          example: 'Conflict: invalid state transition'
    InternalServerError:
      type: object
      properties:
        error:
          description: Human-readable error message
          type: string
          example: Internal Server Error
    Forbidden:
      type: object
      properties:
        error:
          description: Human-readable error message
          type: string
          example: Access Forbidden
  responses:
    BadRequest:
      description: Bad Request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/BadRequest'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Unauthorized'
    BusinessAccountForbidden:
      description: >
        Forbidden. Carries the outstanding approval requirements when the caller
        is authorized but consent is incomplete.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/BusinessAccountForbidden'
    NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/NotFound'
    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Conflict'
    InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/InternalServerError'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````