openapi: 3.0.3
info:
  title: Vega Payments API
  description: |
    The Vega Payments API is organized around REST. It accepts JSON request bodies,
    returns JSON-encoded responses, and uses standard HTTP response codes.

    Authenticate with an API key in the `X-API-Key` header. Keys that start with
    `vega_test_` run in **sandbox**. Keys that start with `vega_live_` run in **live**.
    The key you use selects the mode — the same as Stripe.

    ## Just getting started?

    [Get a sandbox API key](https://vega-marketing-website-cylxuep3eq-uc.a.run.app/#signup)
    on Vega, or [sign up with Vera](https://vera-marketing-website-hlexeiliia-uc.a.run.app/#signup).

    Most successful responses use `{ "success": true, "data": ... }`.
    Errors use `{ "success": false, "error": { "code", "message" } }`.

    For agentic payments, mint an agent under **Agents**, then call the **VACP** endpoints.
  version: 0.1.0
  contact:
    name: Vega Payments Developers
    email: developers@circinussoft.com
  license:
    name: Proprietary

servers:
  - url: https://pay-server-fnnnchfena-uc.a.run.app
    description: Sandbox
  - url: https://api.vega.com
    description: Live

tags:
  - name: Health
    description: Service liveness
  - name: Merchants
    description: Accounts, API keys, and billing
  - name: Auth
    description: Session tokens for the merchant portal
  - name: Routing
    description: Compare rails and choose a payment path
  - name: Payments
    description: Create and look up payments
  - name: Bank accounts
    description: Link customer bank accounts for ACH and other bank rails
  - name: VACP
    description: Agentic commerce — handshake and execute signed agent payments
  - name: Agents
    description: Mint and manage payment agents

paths:
  /api/v1/merchants/signup:
    post:
      tags: [Merchants]
      summary: Create a merchant account
      security: []
      operationId: merchantSignup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, full_name, product_line]
              properties:
                email: { type: string, format: email }
                password: { type: string, format: password, minLength: 8 }
                full_name: { type: string }
                company: { type: string }
                product_line: { type: string, enum: [vega, vera] }
      responses:
        "201":
          description: Merchant created; verify email to receive sandbox key
        "409":
          description: Email already registered

  /api/v1/merchants/login:
    post:
      tags: [Merchants]
      summary: Merchant password login — returns session JWT + API key metadata
      description: |
        Full API secrets are never re-displayed. Use key metadata (prefix + last4)
        or `POST /merchants/keys/sandbox/rotate` to issue a new sandbox key (shown once).
        No social login for merchants.
      security: []
      operationId: merchantLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string, format: password }
      responses:
        "200":
          description: Session JWT + merchant profile + key metadata
        "401":
          description: Invalid credentials
        "403":
          description: Email not verified or account inactive
        "429":
          description: Rate limited

  /api/v1/merchants/verify-email:
    post:
      tags: [Merchants]
      summary: Verify work email and issue sandbox API key (once)
      security: []
      operationId: merchantVerifyEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, token]
              properties:
                email: { type: string, format: email }
                token: { type: string }
      responses:
        "200":
          description: Email verified; sandbox_api_key shown once

  /api/v1/merchants/me:
    get:
      tags: [Merchants]
      summary: Retrieve merchant profile and key metadata
      operationId: merchantMe
      security:
        - apiKeyAuth: []
        - merchantSessionAuth: []
      responses:
        "200":
          description: Merchant profile including api_keys (prefix/last4 only)

  /api/v1/merchants/keys:
    get:
      tags: [Merchants]
      summary: List API key metadata (never full secrets)
      operationId: merchantListKeys
      security:
        - apiKeyAuth: []
        - merchantSessionAuth: []
      responses:
        "200":
          description: Active key metadata

  /api/v1/merchants/keys/sandbox/rotate:
    post:
      tags: [Merchants]
      summary: Rotate sandbox API key (new secret shown once)
      operationId: merchantRotateSandboxKey
      security:
        - apiKeyAuth: []
        - merchantSessionAuth: []
      responses:
        "200":
          description: New sandbox_api_key (shown once); prior sandbox keys deactivated

  /api/v1/merchant-payments/initiate:
    post:
      tags: [Merchants]
      summary: Initiate sandbox payment (incomplete on free tier)
      description: Free tier allows 5 incomplete payments. Completion requires paid plan.
      operationId: merchantInitiatePayment
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount]
              properties:
                amount: { type: number }
                currency: { type: string, default: USD }
                description: { type: string }
      responses:
        "200":
          description: Incomplete payment created
        "402":
          description: Free tier exhausted — upgrade required

  /api/v1/merchant-payments/{id}/complete:
    post:
      tags: [Merchants]
      summary: Complete a payment (paid merchants only)
      operationId: merchantCompletePayment
      security:
        - apiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment completed
        "402":
          description: Upgrade required

  /api/v1/merchants/billing/checkout:
    post:
      tags: [Merchants]
      summary: Start Stripe Checkout to become a paying customer
      operationId: merchantCheckout
      security:
        - apiKeyAuth: []
        - merchantSessionAuth: []
      responses:
        "200":
          description: Checkout session URL

  /api/v1/merchants/keys/live:
    post:
      tags: [Merchants]
      summary: Issue a live API key
      operationId: merchantIssueLiveKey
      security:
        - apiKeyAuth: []
        - merchantSessionAuth: []
      responses:
        "200":
          description: Live key (shown once)
        "402":
          description: Upgrade required
        "403":
          description: KYB incomplete

  /health:
    get:
      tags: [Health]
      summary: Health check
      operationId: getHealth
      security: []
      responses:
        "200":
          description: Service is up
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  service:
                    type: string
                    example: vega-pay-server
                  timestamp:
                    type: string
                    format: date-time

  /api/v1/auth/register:
    post:
      tags: [Auth]
      summary: Register user
      operationId: register
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterRequest"
      responses:
        "200":
          description: Registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthSuccessResponse"
        "400":
          $ref: "#/components/responses/Error"

  /api/v1/auth/login:
    post:
      tags: [Auth]
      summary: Login
      operationId: login
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthSuccessResponse"
        "401":
          $ref: "#/components/responses/Error"

  /api/v1/routing/compare-fees:
    post:
      tags: [Routing]
      summary: Best route + runner-up with savings
      description: |
        Runs the intelligent routing engine and returns only the top two scored rails
        plus fee savings vs the runner-up. Does not dump every rail quote.
      operationId: compareFees
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompareFeesRequest"
            example:
              amount: 100
              currency: USD
      responses:
        "200":
          description: Best route, runner-up, and savings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompareFeesResponse"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"
  /api/v1/routing/decide:
    post:
      tags: [Routing]
      summary: Make a routing decision
      description: Records metrics used by rails status / analytics.
      operationId: routingDecide
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RoutingRequest"
            example:
              amount: 100
              currency: USD
      responses:
        "200":
          description: Selected rail and alternatives
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoutingDecisionResponse"
        "400":
          $ref: "#/components/responses/Error"
        "401":
          $ref: "#/components/responses/Error"

  /api/v1/routing/simulate:
    post:
      tags: [Routing]
      summary: Simulate routing (no metrics)
      operationId: routingSimulate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RoutingRequest"
      responses:
        "200":
          description: Simulated decision
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoutingDecisionResponse"

  /api/v1/routing/rails/status:
    get:
      tags: [Routing]
      summary: List rail status and success rates
      operationId: getRailsStatus
      responses:
        "200":
          description: Per-rail status
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      rails:
                        type: array
                        items:
                          $ref: "#/components/schemas/RailStatus"
                      checked_at:
                        type: string
                        format: date-time

  /api/v1/routing/rails/available:
    get:
      tags: [Routing]
      summary: Rails available for amount/currency
      operationId: getAvailableRails
      parameters:
        - name: amount
          in: query
          required: true
          schema:
            type: number
            format: double
        - name: currency
          in: query
          required: true
          schema:
            type: string
            example: USD
      responses:
        "200":
          description: Available rails
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /api/v1/routing/fees/{rail}:
    get:
      tags: [Routing]
      summary: Fee schedule for one rail
      operationId: getRailFees
      parameters:
        - $ref: "#/components/parameters/RailPath"
        - name: amount
          in: query
          schema:
            type: number
            format: double
      responses:
        "200":
          description: Fee schedule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "404":
          $ref: "#/components/responses/Error"

  /api/v1/routing/preferences:
    get:
      tags: [Routing]
      summary: Get merchant routing preferences
      operationId: getRoutingPreferences
      responses:
        "200":
          description: Preferences (defaults if unset)
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      preferences:
                        $ref: "#/components/schemas/MerchantPreferences"
        "401":
          $ref: "#/components/responses/Error"
    put:
      tags: [Routing]
      summary: Update merchant routing preferences
      operationId: updateRoutingPreferences
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MerchantPreferences"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "401":
          $ref: "#/components/responses/Error"

  /api/v1/routing/metrics:
    get:
      tags: [Routing]
      summary: Routing engine metrics
      operationId: getRoutingMetrics
      responses:
        "200":
          description: Aggregate metrics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /api/v1/payments/initiate:
    post:
      tags: [Payments]
      summary: Initiate a payment
      operationId: initiatePayment
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InitiatePaymentRequest"
      responses:
        "200":
          description: Payment created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "400":
          $ref: "#/components/responses/Error"

  /api/v1/payments/{id}:
    get:
      tags: [Payments]
      summary: Get payment by ID
      operationId: getPayment
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "404":
          $ref: "#/components/responses/Error"

  /api/v1/payments/history:
    get:
      tags: [Payments]
      summary: Payment history
      operationId: getPaymentHistory
      responses:
        "200":
          description: History list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /api/v1/payments/{id}/cancel:
    post:
      tags: [Payments]
      summary: Cancel payment
      operationId: cancelPayment
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Cancelled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /api/v1/payments/{id}/refund:
    post:
      tags: [Payments]
      summary: Refund payment
      operationId: refundPayment
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Refunded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /api/v1/bank-accounts/link-session:
    post:
      tags: [Bank accounts]
      summary: Start a bank-account link session
      description: |
        Returns a short-lived `session_token` for the client link UI.
        Vega calls the bank-link provider (currently Plaid) internally — merchants do not call Plaid.
      operationId: createBankLinkSession
      responses:
        "200":
          description: Link session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      session_token: { type: string }
                      expires_at: { type: string, format: date-time }

  /api/v1/bank-accounts/complete-link:
    post:
      tags: [Bank accounts]
      summary: Complete bank-account linking
      description: |
        Exchanges the opaque result from the link UI for stored bank account references.
        Prefer `session_result`. `public_token` is accepted only as a compat alias when
        embedding the stock Plaid Link widget (onSuccess).
      operationId: completeBankLink
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                session_result:
                  type: string
                  description: Opaque one-time code from the link UI
                public_token:
                  type: string
                  description: Compat alias for Plaid Link onSuccess.public_token
      responses:
        "200":
          description: Bank accounts linked
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data:
                    type: object
                    properties:
                      bank_account_ids:
                        type: array
                        items: { type: string }

  /api/v1/bank-accounts:
    get:
      tags: [Bank accounts]
      summary: List linked bank accounts
      operationId: listBankAccounts
      responses:
        "200":
          description: Linked accounts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  # Stack Spec VACP (/vacp/v1) — DID + Ed25519. Legacy: /api/v1/protocol/*
  /vacp/v1/handshake:
    post:
      tags: [VACP]
      summary: Start an agentic payment session
      description: |
        Authenticate with `X-Vega-Agent-DID` + `X-Vega-Signature` (Ed25519 over raw body).
        Returns `session_id`, challenge, accepted rails, and risk/budget snapshot.
      operationId: vacpHandshake
      security:
        - vegaAgentDID: []
      parameters:
        - in: header
          name: X-Vega-Agent-DID
          required: true
          schema: { type: string, example: "did:vega:a1b2c3d4..." }
        - in: header
          name: X-Vega-Signature
          required: true
          schema: { type: string, description: Base64 Ed25519 signature over raw body }
        - in: header
          name: X-Vega-Timestamp
          required: false
          schema: { type: string, format: date-time }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VacpHandshakeRequest'
      responses:
        "200":
          description: Challenge required; session persisted in vacp_sessions
        "401":
          description: Missing/invalid DID or forged signature

  /vacp/v1/execute:
    post:
      tags: [VACP]
      summary: Execute an agentic payment
      description: |
        Bind challenge response to session and settle via router-selected rail.
        STRIPE_CARD creates a Stripe PaymentIntent when STRIPE_SECRET_KEY is set;
        otherwise sandbox stub. FedNow/RTP stub unless VEGA_RAIL_LIVE_*=1.
      operationId: vacpExecute
      security:
        - vegaAgentDID: []
      parameters:
        - in: header
          name: X-Vega-Agent-DID
          required: true
          schema: { type: string }
        - in: header
          name: X-Vega-Signature
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VacpExecuteRequest'
      responses:
        "200":
          description: Payment executed (or sandbox stub)
        "400":
          description: Session/challenge/budget failure
        "401":
          description: Invalid signature

  /api/v1/merchants/agents:
    post:
      tags: [Agents]
      summary: Mint agent (DID + Ed25519; private key shown once)
      operationId: mintMerchantAgent
      security:
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MintAgentRequest'
      responses:
        "201":
          description: Agent minted with did + private_key_base64 + vega-agent.json manifest
        "401":
          description: Merchant API key required

  /api/v1/merchants/agents/{id}/manifest:
    get:
      tags: [Agents]
      summary: Download vega-agent.json manifest
      operationId: getAgentManifest
      security:
        - apiKeyAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Manifest JSON

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT from `/api/v1/auth/login`, or Circinus Auth0 SPA ID token.
        Soft ops fallback may accept any Bearer in non-prod for routing prefs.
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Merchant key from signup (`vega_test_…` sandbox or `vega_live_…` after paid).
        `Authorization: Bearer vega_test_…` is also accepted.
    merchantSessionAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Merchant session JWT from `POST /api/v1/merchants/login` (role=merchant).
        Distinct from Circinus ops Auth0 / `/api/v1/auth/login` user JWTs.
        Use for `/merchants/me`, keys, business-profile, agent mint — not as a payment API key.
    vegaAgentDID:
      type: apiKey
      in: header
      name: X-Vega-Agent-DID
      description: |
        Agent DID (`did:vega:…`). Pair with `X-Vega-Signature` (Ed25519 over raw body).
        Optional `X-Vega-Timestamp` (RFC3339, ±5m skew).

  parameters:
    RailPath:
      name: rail
      in: path
      required: true
      schema:
        type: string
        enum:
          - ach
          - fednow
          - rtp
          - wire
          - visa_direct
          - mastercard_send
          - usdc
          - usdt

  responses:
    Error:
      description: Error envelope
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"

  schemas:
    SuccessEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          additionalProperties: true
        message:
          type: string

    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
              example: VALIDATION_ERROR
            message:
              type: string

    RegisterRequest:
      type: object
      required: [email, password, first_name, last_name]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password

    AuthSuccessResponse:
      allOf:
        - $ref: "#/components/schemas/SuccessEnvelope"

    CompareFeesRequest:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: number
          format: double
          exclusiveMinimum: 0
        currency:
          type: string
          example: USD

    RailQuote:
      type: object
      properties:
        rail:
          type: string
        total_fee:
          type: number
        fee_percentage:
          type: number
        flat_component:
          type: number
        percent_component:
          type: number
        net_amount:
          type: number
        settlement_time:
          type: string
        is_available:
          type: boolean
        unavailable_reason:
          type: string

    RouteOption:
      type: object
      properties:
        rank:
          type: integer
          example: 1
        rail:
          type: string
          example: fednow
        fee:
          type: number
          example: 0.4
        fee_percentage:
          type: number
        settlement_time:
          type: string
        score:
          type: number
        reasoning:
          type: string
        confidence:
          type: number

    CompareFeesResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            amount:
              type: number
            currency:
              type: string
            best_route:
              $ref: "#/components/schemas/RouteOption"
            routes:
              type: array
              maxItems: 2
              items:
                $ref: "#/components/schemas/RouteOption"
            savings:
              type: object
              properties:
                amount:
                  type: number
                  description: Fee saved vs runner-up (0 if best costs more)
                currency:
                  type: string
                compared_to_rail:
                  type: string
                fee_delta:
                  type: number
                  description: runner_up.fee - best.fee (positive = best is cheaper)
                note:
                  type: string
            optimization_goal:
              type: string
            reasoning:
              type: string
            request_id:
              type: string
            decided_at:
              type: string
              format: date-time

    RoutingRequest:
      type: object
      required: [amount, currency]
      properties:
        request_id:
          type: string
        merchant_id:
          type: string
        amount:
          type: number
          format: double
        currency:
          type: string
        is_urgent:
          type: boolean
        source_type:
          type: string
          description: bank_account, card, wallet
        destination_type:
          type: string
        metadata:
          type: object
          additionalProperties: true

    RoutingDecisionResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            request_id:
              type: string
            selected_rail:
              type: string
            estimated_fee:
              type: number
            fee_percentage:
              type: number
            settlement_time:
              type: string
            confidence:
              type: number
            reasoning:
              type: string
            optimization_goal:
              type: string
            score:
              type: number
            alternatives:
              type: array
              items:
                type: object
            decided_at:
              type: string
              format: date-time

    MerchantPreferences:
      type: object
      properties:
        merchant_id:
          type: string
        optimization_goal:
          type: string
          enum: [cost, speed, reliability, balance]
        preferred_rails:
          type: array
          items:
            type: string
        excluded_rails:
          type: array
          items:
            type: string
        max_fee_percentage:
          type: number
        max_settlement_hours:
          type: number
        allow_crypto:
          type: boolean
        allow_card_networks:
          type: boolean
        cash_flow_priority:
          type: string
        fallback_enabled:
          type: boolean
        retry_on_failure:
          type: boolean
        max_retries:
          type: integer

    RailStatus:
      type: object
      properties:
        rail:
          type: string
        status:
          type: string
          example: available
        success_rate:
          type: number
        settlement_time:
          type: string
        fee_type:
          type: string
        base_fee:
          type: number
        is_24x7:
          type: boolean

    InitiatePaymentRequest:
      type: object
      required: [amount, currency]
      properties:
        amount:
          type: number
          format: double
        currency:
          type: string
          example: USD
        description:
          type: string
        payment_method_id:
          type: string
        community_id:
          type: string

    VacpHandshakeRequest:
      type: object
      required: [merchant_id, intent]
      properties:
        merchant_id:
          type: string
          example: mer_550e8400-e29b-41d4-a716-446655440000
        intent:
          type: object
          required: [amount, currency]
          properties:
            action: { type: string, example: purchase }
            amount: { type: number, example: 129.99 }
            currency: { type: string, example: USD }
            mcc: { type: string, example: "5411" }
            description: { type: string }
            idempotency_key: { type: string }
        routing_preference:
          type: string
          description: Stack Spec alias or Go goal
          enum: [LOWEST_COST, FASTEST, HIGHEST_SUCCESS, cost, speed, reliability, balance]
        timestamp:
          type: string
          format: date-time

    VacpExecuteRequest:
      type: object
      required: [session_id, challenge_response]
      properties:
        session_id: { type: string, example: vacp_sess_abcd1234 }
        challenge_response:
          type: string
          description: Base64 (or hex) echo of handshake challenge
        payment_method_hint:
          type: string
          description: Optional Stripe payment_method id for STRIPE_CARD confirm

    MintAgentRequest:
      type: object
      required: [name]
      properties:
        name: { type: string }
        description: { type: string }
        agent_type: { type: string, example: purchasing }
        allowed_mccs:
          type: array
          items: { type: string }
        spending_limits:
          type: array
          items:
            type: object
            properties:
              currency: { type: string }
              transaction_max: { type: number }
              daily_limit: { type: number }
              monthly_limit: { type: number }
        persona:
          type: object
          additionalProperties: true

security:
  - bearerAuth: []
