openapi: 3.0.3
info:
  title: VaultAI Immutable Ledger API
  version: 1.0.0
  description: >
    Institutional API surface for the VaultAI immutable double-entry ledger.
    All monetary amounts are represented as integer minor units encoded as
    strings. Floating-point amounts are rejected.
servers:
  - url: https://vaultai.network
    description: Production
  - url: http://localhost:3000
    description: Local development
security:
  - SupabaseBearerAuth: []
paths:
  /api/ledger/transactions:
    post:
      operationId: postLedgerTransaction
      summary: Initiate an idempotent multi-leg ledger transaction
      description: >
        Posts a balanced set of debit and credit journal entries through the
        database-level double-entry posting function. The request is serialized
        by Idempotency-Key and row-level account locks. Each currency in the
        entries array must independently balance to zero.
      tags:
        - Ledger Transactions
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >
            Required for every state-changing ledger request. Reusing the same
            key with an identical request returns the original response. Reusing
            it with a different body returns 409 Conflict.
          schema:
            type: string
            minLength: 16
            maxLength: 200
            pattern: "^[A-Za-z0-9:_./=-]{16,200}$"
        - name: X-VaultAI-Jurisdiction
          in: header
          required: false
          description: >
            Sovereign jurisdiction boundary for the posting. All account legs
            must belong to this jurisdiction or the transaction fails closed.
          schema:
            type: string
            example: bank_private
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LedgerTransactionRequest"
            examples:
              simpleTransfer:
                summary: Balanced two-leg transfer
                value:
                  transaction_reference: TRX-2026-000001
                  transaction_type: customer_transfer
                  jurisdiction_code: bank_private
                  entries:
                    - account_id: 7c72a352-45f1-40f9-9e90-b52c59a3f901
                      entry_type: debit
                      amount_minor: "100000"
                      currency_code: USD
                      memo: Debit customer asset account
                    - account_id: 9c0923f2-51fb-459c-a30a-23ea99ec6d31
                      entry_type: credit
                      amount_minor: "100000"
                      currency_code: USD
                      memo: Credit settlement liability account
      responses:
        "201":
          description: Transaction created and cleared.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LedgerTransactionResponse"
        "200":
          description: Idempotent replay of a previously completed transaction.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LedgerTransactionResponse"
        "400":
          description: Invalid JSON, invalid header shape, or schema validation failure.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Authentication required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Organization membership or jurisdiction boundary failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Idempotency-Key reused with a different request body.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Unbalanced journal entries, inactive account, insufficient balance, or currency mismatch.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "428":
          description: Missing Idempotency-Key header.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Ledger API rate limit exceeded.
          headers:
            X-RateLimit-Limit:
              schema:
                type: string
            X-RateLimit-Remaining:
              schema:
                type: string
            X-RateLimit-Reset:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/ledger/webhook-subscriptions:
    get:
      operationId: listLedgerWebhookSubscriptions
      summary: List signed webhook subscriptions for a tenant institution
      tags:
        - Webhooks
      security:
        - SupabaseBearerAuth: []
      responses:
        "200":
          description: Subscription list with secrets redacted.
          content:
            application/json:
              schema:
                type: object
                required:
                  - subscriptions
                properties:
                  subscriptions:
                    type: array
                    items:
                      $ref: "#/components/schemas/WebhookSubscription"
    post:
      operationId: createLedgerWebhookSubscription
      summary: Create an HMAC-signed outbound webhook subscription
      tags:
        - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookSubscriptionRequest"
      responses:
        "201":
          description: Subscription created. The signing secret is shown once.
          content:
            application/json:
              schema:
                type: object
                required:
                  - subscription
                  - signing_secret
                properties:
                  subscription:
                    $ref: "#/components/schemas/WebhookSubscription"
                  signing_secret:
                    type: string
                    example: whsec_Fn6Dg...
                  warning:
                    type: string
  /api/bank-admin/ledger:
    get:
      operationId: getBankAdminLedger
      summary: Load ledger transactions with mapped journal entries
      tags:
        - Bank Admin
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Ledger control-plane data.
          content:
            application/json:
              schema:
                type: object
  /api/bank-admin/accounts/freeze:
    post:
      operationId: freezeLedgerAccount
      summary: Freeze an account and append WORM audit evidence
      tags:
        - Bank Admin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - account_id
                - reason
              properties:
                account_id:
                  type: string
                  format: uuid
                reason:
                  type: string
                  minLength: 8
                  maxLength: 500
      responses:
        "200":
          description: Account frozen and audit logged.
          content:
            application/json:
              schema:
                type: object
        "403":
          description: Owner/admin role required.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
components:
  securitySchemes:
    SupabaseBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Supabase JWT
      description: Authenticated Supabase JWT scoped to a VaultAI tenant organization.
  schemas:
    LedgerTransactionRequest:
      type: object
      required:
        - entries
      additionalProperties: false
      properties:
        transaction_reference:
          type: string
          minLength: 1
          maxLength: 160
        transaction_type:
          type: string
          default: transfer
          minLength: 1
          maxLength: 80
        jurisdiction_code:
          type: string
          example: bank_private
        metadata:
          type: object
          additionalProperties: true
        entries:
          type: array
          minItems: 2
          maxItems: 200
          items:
            $ref: "#/components/schemas/JournalEntryInput"
    JournalEntryInput:
      type: object
      required:
        - account_id
        - entry_type
        - amount_minor
        - currency_code
      additionalProperties: false
      properties:
        account_id:
          type: string
          format: uuid
        entry_type:
          type: string
          enum:
            - debit
            - credit
        amount_minor:
          type: string
          description: Integer minor units only. Never send decimals or floats.
          pattern: "^[1-9][0-9]*$"
          example: "100000"
        currency_code:
          type: string
          pattern: "^[A-Z]{3}$"
          example: USD
        memo:
          type: string
          maxLength: 500
        metadata:
          type: object
          additionalProperties: true
    LedgerTransactionResponse:
      type: object
      required:
        - ok
        - transaction
      properties:
        ok:
          type: boolean
          example: true
        transaction:
          type: object
          required:
            - transaction_id
            - transaction_reference
            - state
            - organization_id
            - journal_entry_count
            - request_hash
            - idempotent_replay
          properties:
            transaction_id:
              type: string
              format: uuid
            transaction_reference:
              type: string
            state:
              type: string
              enum:
                - PENDING
                - CLEARED
                - SETTLED
                - FAILED
                - REJECTED
            organization_id:
              type: string
              format: uuid
            journal_entry_count:
              type: integer
            request_hash:
              type: string
              pattern: "^[a-f0-9]{64}$"
            idempotent_replay:
              type: boolean
    CreateWebhookSubscriptionRequest:
      type: object
      required:
        - endpoint_url
        - event_types
      properties:
        endpoint_url:
          type: string
          format: uri
          example: https://institution.example/webhooks/vaultai
        description:
          type: string
          maxLength: 300
        event_types:
          type: array
          minItems: 1
          maxItems: 10
          items:
            type: string
            enum:
              - transaction.cleared
              - transaction.settled
              - transaction.failed
              - transaction.rejected
              - account.frozen
              - balance.updated
        metadata:
          type: object
          additionalProperties: true
    WebhookSubscription:
      type: object
      properties:
        id:
          type: string
          format: uuid
        endpoint_url:
          type: string
          format: uri
        description:
          type: string
          nullable: true
        event_types:
          type: array
          items:
            type: string
        signing_secret_last_four:
          type: string
        status:
          type: string
        failure_count:
          type: integer
        last_delivery_at:
          type: string
          format: date-time
          nullable: true
        last_failure_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        code:
          type: string
        issues:
          type: array
          items:
            type: object
            additionalProperties: true
