openapi: 3.1.0

info:
  title: Minka Ledger API — Colombia
  version: 2.0.0
  description: |
    REST API for the **Minka Ledger** — the settlement rail powering
    instant-payment ecosystems. This spec covers the **Colombian market**
    (Bre-B rail · COP currency · EMVCo QR v1.4 · Llaves aliases) and is
    structured for extension to other markets (BR, MX, …).

    ## Base URL

    Every ledger instance has its own hostname:

    ```
    https://{ledger}.ldg-stg.one/api/v2   # staging
    https://{ledger}.ldg.one/api/v2        # production
    ```

    ## Authentication

    All requests require:
    1. **Bearer token** — `Authorization: Bearer {access_token}`
    2. **Ledger header** — `x-ledger: {ledger-handle}`

    Obtain a token via `POST /oauth/token` using the `client_credentials` grant.

    Write operations additionally require a **cryptographic proof** embedded
    in the request body. See the `Proof` schema for details.

    ## Amounts

    All monetary amounts are **integers in the smallest currency unit**.
    For COP (factor 100): `18500000` = **$185,000.00 COP**.

    ## Handles

    A handle is a human-readable unique identifier:

    | Example | Meaning |
    |---|---|
    | `svgs:10000001@bancorojo.com.co` | Savings account at Banco Rojo |
    | `main@comercio-ejemplo.com.co` | Merchant hub wallet |
    | `mailto:pagos@comercio-ejemplo.com.co` | Llave by email (Bre-B alias) |
    | `nidn:900100001` | Llave by NIT (Bre-B alias) |
    | `key:order-001@comercio-ejemplo.com.co` | Dynamic Llave (per-transaction) |
    | `qr:TERMINAL01@comercio-ejemplo.com.co` | Static QR anchor |

    ## Signatures

    Write operations embed an **Ed25519 proof** in the request body:

    1. Serialise `data` as RFC 8785 canonical JSON
    2. SHA-256 hash → `hash` (hex string)
    3. Ed25519-sign hash → `proofs[0].result` (base64 string)

    ## Pagination

    List endpoints support `?limit=25&offset=0` (max limit: 100).

    ## Filtering

    Use dot-path filter query params:

    ```
    GET /intents?filter[meta.status]=completed
    GET /intents?filter[data.claims.target.handle]=main@comercio-ejemplo.com.co
    ```

    ## Rate limiting

    Exceeding the rate limit returns `429 Too Many Requests` with a
    `Retry-After` header (seconds). Use exponential backoff: 1 s → 2 s → 4 s … 60 s max.

  contact:
    name: Minka
    url: https://hub.minka.io

servers:
  - url: https://{ledger}.ldg-stg.one/api/v2
    description: Staging
    variables:
      ledger:
        default: demo-co
        description: Ledger handle assigned to your hub
  - url: https://{ledger}.ldg.one/api/v2
    description: Production
    variables:
      ledger:
        default: demo-co
        description: Ledger handle assigned to your hub

security:
  - BearerAuth: []
    LedgerHeader: []

tags:
  - name: Auth
    description: OAuth 2.0 client-credentials token exchange — obtain a Bearer token to authenticate all subsequent requests.
  - name: Wallets
    description: Ledger accounts identified by a handle (e.g. `svgs:10000001@bancorojo.com.co`), typed by schema and optionally bound to an external bank account via a bridge.
  - name: Intents
    description: Signed payment instructions composed of one or more claims (`transfer`, `issue`, `destroy`, `limit`) that progress through states — `pending` → `prepared` → `committed` → `completed` or `rejected`.
  - name: Anchors
    description: External payment identifiers mapped to wallets — EMVCo QR v1.4 codes (`emvco-v14-co`) and Bre-B Llaves (`alias`) including `mailto:`, `tel:`, `nidn:`, `merchid:`, and dynamic `key:` types.
  - name: Effects
    description: Subscriptions that react to ledger signals — deliver the event payload to a webhook endpoint or a bridge. Supports all ledger signals (`balance-received`, `intent-updated`, `wallet-created`, `signer-updated`, and 50+ more) with optional dot-path filters for fine-grained targeting.
  - name: Schemas
    description: JSON Schema record-type definitions that govern the allowed `custom` fields on wallets, anchors, intents, signers, and policies.
  - name: Symbols
    description: Currency and asset definitions — `cop` (factor 100, centavos) is the settlement symbol for the Colombian ecosystem; all amounts are integers in the smallest unit.
  - name: Circles
    description: Named signer groups (`owner`, `admin`, `operations`, `security`, `support`, `services`) used in access-control policies to grant permissions to sets of Ed25519 keys.
  - name: Policies
    description: Ledger-wide access rules that define which signers (via circles) can perform which operations on which record types.
  - name: Bridges
    description: Configurations that connect the ledger to external financial systems — a bridge declares its capabilities (`debits`, `credits`, `anchors`, `events`) and the URL of your bridge server.
  - name: Signers
    description: Ed25519 public keys registered on the ledger and assigned to circles — used to verify the cryptographic proof attached to every write request.
  - name: Bridge Server
    description: Endpoints your bridge server must implement to participate in the two-phase commit protocol — the ledger calls these to prepare and commit debits and credits on external bank accounts.

paths:

  # ─────────────────────────────────────────────────────────────────────────
  # Auth
  # ─────────────────────────────────────────────────────────────────────────

  /oauth/token:
    post:
      operationId: createToken
      tags: [Auth]
      summary: Request access token
      description: |
        Exchange client credentials for a short-lived access token using the
        OAuth 2.0 `client_credentials` grant. Tokens are scoped to a single
        ledger and expire after `expires_in` seconds.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
            examples:
              clientCredentials:
                $ref: '#/components/examples/TokenRequestExample'
      responses:
        '200':
          description: Token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
              examples:
                success:
                  $ref: '#/components/examples/TokenResponseExample'
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ─────────────────────────────────────────────────────────────────────────
  # Wallets
  # ─────────────────────────────────────────────────────────────────────────

  /wallets:
    post:
      operationId: createWallet
      tags: [Wallets]
      summary: Create wallet
      description: |
        Create a new wallet on the ledger. The `data.schema` field selects
        the JSON Schema used to validate `data.custom`. Optionally bind a
        `data.bridge` to link the wallet to an external account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WalletCreateRequest'
            examples:
              bankWallet:
                summary: ACH participant bank wallet (Banco Rojo)
                $ref: '#/components/examples/WalletBankCreate'
              merchantWallet:
                summary: Merchant hub wallet
                $ref: '#/components/examples/WalletMerchantCreate'
              treasuryWallet:
                summary: Hub treasury wallet
                $ref: '#/components/examples/WalletTreasuryCreate'
              achWallet:
                summary: ACH network connectivity wallet (Banco Azul)
                $ref: '#/components/examples/WalletAchCreate'
      responses:
        '201':
          description: Wallet created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
              examples:
                bankWallet:
                  $ref: '#/components/examples/WalletBankResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      operationId: listWallets
      tags: [Wallets]
      summary: List wallets
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: filter[data.schema]
          in: query
          schema:
            type: string
          example: banks
          description: Filter by schema handle
        - name: filter[data.custom.country]
          in: query
          schema:
            type: string
          example: co
      responses:
        '200':
          description: Paginated wallet list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletListResponse'
              examples:
                list:
                  $ref: '#/components/examples/WalletListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /wallets/{handle}:
    get:
      operationId: getWallet
      tags: [Wallets]
      summary: Get wallet
      parameters:
        - $ref: '#/components/parameters/walletHandle'
      responses:
        '200':
          description: Wallet found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletResponse'
              examples:
                bankWallet:
                  $ref: '#/components/examples/WalletBankResponse'
                merchantWallet:
                  $ref: '#/components/examples/WalletMerchantResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /wallets/{handle}/balances:
    get:
      operationId: getWalletBalances
      tags: [Wallets]
      summary: Get wallet balances
      description: Returns the balance per symbol for the given wallet.
      parameters:
        - $ref: '#/components/parameters/walletHandle'
      responses:
        '200':
          description: Balance list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BalanceListResponse'
              examples:
                balances:
                  $ref: '#/components/examples/WalletBalancesResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Intents
  # ─────────────────────────────────────────────────────────────────────────

  /intents:
    post:
      operationId: createIntent
      tags: [Intents]
      summary: Create intent
      description: |
        Submit a signed payment intent. The `claims` array defines what should
        happen. The `hash` and `proofs` fields carry the Ed25519 signature over
        the canonical JSON of `data`.

        **`config.commit` values**

        | Value | Behaviour |
        |---|---|
        | `auto` | Commit immediately after all proofs are collected |
        | `manual` | Wait for an explicit commit call |
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntentCreateRequest'
            examples:
              inboundPayment:
                summary: Inbound Bre-B payment — payer (Banco Rojo) → hub aggregate account
                $ref: '#/components/examples/IntentInboundPayment'
              merchantAllocation:
                summary: Hub allocation — aggregate account → merchant wallet
                $ref: '#/components/examples/IntentMerchantAllocation'
              treasuryFunding:
                summary: Treasury advance — hub treasury → merchant
                $ref: '#/components/examples/IntentTreasuryFunding'
              feeCollection:
                summary: Acquiring fee — merchant → hub fees wallet
                $ref: '#/components/examples/IntentFeeCollection'
              minBalance:
                summary: Minimum-balance limit for a wallet
                $ref: '#/components/examples/IntentMinBalance'
      responses:
        '201':
          description: Intent created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntentResponse'
              examples:
                completed:
                  summary: Intent completed
                  $ref: '#/components/examples/IntentResponseCompleted'
                rejected:
                  summary: Intent rejected (insufficient funds)
                  $ref: '#/components/examples/IntentResponseRejected'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

    get:
      operationId: listIntents
      tags: [Intents]
      summary: List intents
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: filter[meta.status]
          in: query
          schema:
            type: string
            enum: [pending, prepared, committed, completed, rejected]
          example: completed
        - name: filter[data.claims.target.handle]
          in: query
          schema:
            type: string
          example: main@comercio-ejemplo.com.co
        - name: filter[data.claims.source.handle]
          in: query
          schema:
            type: string
          example: svgs:10000001@bancorojo.com.co
        - name: filter[data.schema]
          in: query
          schema:
            type: string
          example: payment
      responses:
        '200':
          description: Paginated intent list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntentListResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /intents/{handle}:
    get:
      operationId: getIntent
      tags: [Intents]
      summary: Get intent
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Intent found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntentResponse'
              examples:
                completed:
                  $ref: '#/components/examples/IntentResponseCompleted'
        '404':
          $ref: '#/components/responses/NotFound'

  /intents/{handle}/proofs:
    post:
      operationId: submitIntentProof
      tags: [Intents]
      summary: Submit intent proof
      description: |
        Called by a **bridge** to report the outcome of a debit or credit.
        The `custom.status` field drives the intent state machine:

        | status | Meaning |
        |---|---|
        | `prepared` | Funds reserved / target validated |
        | `committed` | Funds settled / credited |
        | `failed` | Operation failed — intent will be rejected |
        | `aborted` | Operation reversed cleanly |

        The `custom.handle` field must match the debit or credit handle
        originally sent by the hub.
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntentProofRequest'
            examples:
              prepared:
                summary: Debit prepared (funds reserved)
                $ref: '#/components/examples/IntentProofPrepared'
              committed:
                summary: Credit committed (funds deposited)
                $ref: '#/components/examples/IntentProofCommitted'
              failed:
                summary: Debit failed — insufficient funds
                $ref: '#/components/examples/IntentProofFailed'
      responses:
        '202':
          description: Proof accepted
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Anchors
  # ─────────────────────────────────────────────────────────────────────────

  /anchors:
    post:
      operationId: createAnchor
      tags: [Anchors]
      summary: Create anchor
      description: |
        Create a QR code or Llave alias that resolves to a ledger wallet.

        **Anchor schemas in the Colombian ecosystem**

        `emvco-v14-co` — EMVCo QR v1.4:
        - Static QR: omit `amount`; `target` = NIT or merchant code
        - Dynamic QR: include `amount` + `custom.expiry` (seconds)

        `alias` — Bre-B Llave:
        - Static Llave: handle prefix `mailto:`, `tel:`, `nidn:`, or `merchid:`
        - Dynamic Llave: handle prefix `key:` + `amount` + `custom.expiry`
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnchorCreateRequest'
            examples:
              qrStatic:
                summary: Static QR — reusable POS terminal
                $ref: '#/components/examples/AnchorQRStatic'
              qrDynamic:
                summary: Dynamic QR — single-use order (185,000 COP)
                $ref: '#/components/examples/AnchorQRDynamic'
              llaveMerchid:
                summary: Llave Estática — merchant code (merchid)
                $ref: '#/components/examples/AnchorLlaveMerchid'
              llaveNidn:
                summary: Llave Estática — NIT (nidn)
                $ref: '#/components/examples/AnchorLlaveNidn'
              llaveMail:
                summary: Llave Estática — email (mailto)
                $ref: '#/components/examples/AnchorLlaveMail'
              llaveTel:
                summary: Llave Estática — phone (tel)
                $ref: '#/components/examples/AnchorLlaveTel'
              llaveDynamic:
                summary: Llave Dinámica — per-transaction key
                $ref: '#/components/examples/AnchorLlaveDynamic'
      responses:
        '201':
          description: Anchor created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnchorResponse'
              examples:
                qrStatic:
                  $ref: '#/components/examples/AnchorQRStaticResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      operationId: listAnchors
      tags: [Anchors]
      summary: List anchors
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: filter[data.schema]
          in: query
          schema:
            type: string
            enum: [emvco-v14-co, alias]
          example: alias
        - name: filter[data.wallet]
          in: query
          schema:
            type: string
          example: main@comercio-ejemplo.com.co
      responses:
        '200':
          description: Paginated anchor list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnchorListResponse'

  /anchors/{handle}:
    get:
      operationId: getAnchor
      tags: [Anchors]
      summary: Get / resolve anchor
      description: |
        Retrieve anchor metadata or resolve it to its target wallet.

        URL-encode special characters in the handle path segment:
        - `mailto:pagos@comercio.com.co` → `mailto%3Apagos%40comercio.com.co`
        - `tel:+573001234567` → `tel%3A%2B573001234567`
        - `nidn:900100001` → `nidn%3A900100001`
      parameters:
        - name: handle
          in: path
          required: true
          schema:
            type: string
          description: URL-encoded anchor handle
          example: nidn%3A900100001
      responses:
        '200':
          description: Anchor found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnchorResponse'
              examples:
                qrStatic:
                  summary: Static QR anchor
                  $ref: '#/components/examples/AnchorQRStaticResponse'
                llaveNidn:
                  summary: Llave NIT resolved
                  $ref: '#/components/examples/AnchorLlaveNidnResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Effects
  # ─────────────────────────────────────────────────────────────────────────

  /effects:
    post:
      operationId: createEffect
      tags: [Effects]
      summary: Create webhook subscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EffectCreateRequest'
            examples:
              balanceReceived:
                summary: Notify on incoming payment to merchant wallet
                $ref: '#/components/examples/EffectBalanceReceived'
              intentUpdated:
                summary: Notify on intent status change for a domain
                $ref: '#/components/examples/EffectIntentCompleted'
      responses:
        '201':
          description: Effect created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EffectResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

    get:
      operationId: listEffects
      tags: [Effects]
      summary: List effects
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Effect list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EffectListResponse'

  /effects/{handle}:
    get:
      operationId: getEffect
      tags: [Effects]
      summary: Get effect
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Effect found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EffectResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Schemas
  # ─────────────────────────────────────────────────────────────────────────

  /schemas:
    post:
      operationId: createSchema
      tags: [Schemas]
      summary: Create schema
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SchemaCreateRequest'
            examples:
              banksSchema:
                summary: Bank participant wallet schema
                $ref: '#/components/examples/SchemaBanks'
              merchantSchema:
                summary: Merchant wallet schema
                $ref: '#/components/examples/SchemaMerchant'
              aliasSchema:
                summary: Bre-B alias anchor schema
                $ref: '#/components/examples/SchemaAlias'
      responses:
        '201':
          description: Schema created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'

    get:
      operationId: listSchemas
      tags: [Schemas]
      summary: List schemas
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
        - name: filter[data.record]
          in: query
          schema:
            type: string
            enum: [wallet, anchor, intent, signer, policy]
          example: wallet
      responses:
        '200':
          description: Schema list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaListResponse'
              examples:
                coEcosystem:
                  summary: Colombian ecosystem schemas
                  $ref: '#/components/examples/SchemaListResponseCO'

  /schemas/{handle}:
    get:
      operationId: getSchema
      tags: [Schemas]
      summary: Get schema
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Schema found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SchemaResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Symbols
  # ─────────────────────────────────────────────────────────────────────────

  /symbols:
    post:
      operationId: createSymbol
      tags: [Symbols]
      summary: Create symbol
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SymbolCreateRequest'
            examples:
              cop:
                summary: Colombian peso (COP)
                $ref: '#/components/examples/SymbolCOP'
      responses:
        '201':
          description: Symbol created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SymbolResponse'

    get:
      operationId: listSymbols
      tags: [Symbols]
      summary: List symbols
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Symbol list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SymbolListResponse'

  /symbols/{handle}:
    get:
      operationId: getSymbol
      tags: [Symbols]
      summary: Get symbol
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Symbol found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SymbolResponse'
              examples:
                cop:
                  $ref: '#/components/examples/SymbolCOPResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Circles
  # ─────────────────────────────────────────────────────────────────────────

  /circles:
    post:
      operationId: createCircle
      tags: [Circles]
      summary: Create circle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircleCreateRequest'
            examples:
              services:
                $ref: '#/components/examples/CircleServices'
      responses:
        '201':
          description: Circle created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CircleResponse'

    get:
      operationId: listCircles
      tags: [Circles]
      summary: List circles
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Circle list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CircleListResponse'
              examples:
                standard:
                  summary: Standard hub circles
                  $ref: '#/components/examples/CircleListResponse'

  /circles/{handle}:
    get:
      operationId: getCircle
      tags: [Circles]
      summary: Get circle
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Circle found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CircleResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /circles/{handle}/signers:
    post:
      operationId: addSignerToCircle
      tags: [Circles]
      summary: Assign signer to circle
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CircleSignerAssignment'
            examples:
              assign:
                $ref: '#/components/examples/CircleSignerAssignment'
      responses:
        '201':
          description: Signer assigned to circle
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Policies
  # ─────────────────────────────────────────────────────────────────────────

  /policies:
    post:
      operationId: createPolicy
      tags: [Policies]
      summary: Create policy
      description: |
        Policies define who can do what on the ledger. Two policy types are
        common in a payments hub:

        - **Owner access** — full control for the hub operator circle
        - **Service access** — narrow create/read/query rights for bridge signers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PolicyCreateRequest'
            examples:
              ownerAccess:
                summary: Owner full-access policy
                $ref: '#/components/examples/PolicyOwnerAccess'
              serviceAccess:
                summary: Services narrow-access policy
                $ref: '#/components/examples/PolicyServiceAccess'
      responses:
        '201':
          description: Policy created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyResponse'

    get:
      operationId: listPolicies
      tags: [Policies]
      summary: List policies
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Policy list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyListResponse'

  /policies/{handle}:
    get:
      operationId: getPolicy
      tags: [Policies]
      summary: Get policy
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Policy found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PolicyResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Bridges
  # ─────────────────────────────────────────────────────────────────────────

  /bridges:
    post:
      operationId: createBridge
      tags: [Bridges]
      summary: Register bridge
      description: |
        Register a bridge server. The hub will call `url` + `/debits`,
        `/credits`, or `/anchors` paths according to the declared `traits`.

        **Auth options for hub→bridge outbound calls**

        | schema | Use |
        |---|---|
        | `header` | Custom HTTP headers (e.g. `X-Bridge-Key`) |
        | `oauth2` | client_credentials token exchange |
        | `mtls` | Mutual TLS (PEM certificate + key) |
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeCreateRequest'
            examples:
              bankBridge:
                summary: Bank bridge — Banco Rojo (debits + credits)
                $ref: '#/components/examples/BridgeBankCreate'
              achBridge:
                summary: ACH network bridge — Banco Azul
                $ref: '#/components/examples/BridgeAchCreate'
              anchorsBridge:
                summary: Anchors service bridge (QR + Llaves)
                $ref: '#/components/examples/BridgeAnchorsCreate'
      responses:
        '201':
          description: Bridge registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BridgeResponse'

    get:
      operationId: listBridges
      tags: [Bridges]
      summary: List bridges
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Bridge list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BridgeListResponse'

  /bridges/{handle}:
    get:
      operationId: getBridge
      tags: [Bridges]
      summary: Get bridge
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Bridge found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BridgeResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  /bridges/{handle}/activate:
    post:
      operationId: activateBridge
      tags: [Bridges]
      summary: Activate bridge
      description: |
        Signal the hub to re-deliver any pending debit or credit operations
        to this bridge. Use after a bridge restart or connectivity outage.
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '202':
          description: Activation queued
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Signers
  # ─────────────────────────────────────────────────────────────────────────

  /signers:
    post:
      operationId: createSigner
      tags: [Signers]
      summary: Register signer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignerCreateRequest'
            examples:
              serviceSigner:
                summary: Bridge service signer
                $ref: '#/components/examples/SignerService'
      responses:
        '201':
          description: Signer registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignerResponse'

    get:
      operationId: listSigners
      tags: [Signers]
      summary: List signers
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Signer list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignerListResponse'

  /signers/{handle}:
    get:
      operationId: getSigner
      tags: [Signers]
      summary: Get signer
      parameters:
        - $ref: '#/components/parameters/handle'
      responses:
        '200':
          description: Signer found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignerResponse'
              examples:
                service:
                  $ref: '#/components/examples/SignerServiceResponse'
        '404':
          $ref: '#/components/responses/NotFound'

  # ─────────────────────────────────────────────────────────────────────────
  # Bridge Server — endpoints YOUR bridge must implement
  # ─────────────────────────────────────────────────────────────────────────

  /debits:
    post:
      operationId: bridgePrepareDebit
      tags: [Bridge Server]
      summary: Prepare debit
      description: |
        The hub calls this to reserve / debit funds from the source wallet.
        Respond `202 Accepted` immediately, then asynchronously call
        `POST /intents/{handle}/proofs` with `custom.status: "prepared"` or
        `"failed"`.

        The `data.handle` field is the idempotency key — re-delivering the
        same handle must be a no-op if already processed.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeDebitRequest'
            examples:
              prepare:
                summary: Hub requests debit reservation at Banco Rojo
                $ref: '#/components/examples/BridgeDebitPrepare'
      responses:
        '202':
          description: Debit accepted for async processing

  /debits/{handle}/commit:
    post:
      operationId: bridgeCommitDebit
      tags: [Bridge Server]
      summary: Commit debit
      description: |
        The hub calls this after all bridge proofs confirm `prepared`.
        Finalise the debit (make it irrevocable). Respond `202 Accepted`,
        then call `POST /intents/{handle}/proofs` with `custom.status: "committed"`.
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeDebitCommitRequest'
      responses:
        '202':
          description: Commit accepted

  /debits/{handle}/abort:
    post:
      operationId: bridgeAbortDebit
      tags: [Bridge Server]
      summary: Abort debit
      description: |
        The hub calls this to reverse a prepared debit. Return funds to the
        source. Respond `202 Accepted`, then call
        `POST /intents/{handle}/proofs` with `custom.status: "aborted"`.
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeDebitAbortRequest'
      responses:
        '202':
          description: Abort accepted

  /credits:
    post:
      operationId: bridgePrepareCredit
      tags: [Bridge Server]
      summary: Prepare credit
      description: |
        The hub calls this to validate the target account. Verify the target
        wallet exists and can receive funds. Respond `202 Accepted`, then call
        `POST /intents/{handle}/proofs` with `custom.status: "prepared"` or
        `"failed"`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeCreditRequest'
            examples:
              prepare:
                summary: Hub requests credit validation at Banco Amarillo
                $ref: '#/components/examples/BridgeCreditPrepare'
      responses:
        '202':
          description: Credit accepted for async processing

  /credits/{handle}/commit:
    post:
      operationId: bridgeCommitCredit
      tags: [Bridge Server]
      summary: Commit credit
      description: |
        The hub calls this to deposit funds into the target account. Respond
        `202 Accepted`, then call `POST /intents/{handle}/proofs` with
        `custom.status: "committed"`.
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeCreditCommitRequest'
      responses:
        '202':
          description: Commit accepted

  /credits/{handle}/abort:
    post:
      operationId: bridgeAbortCredit
      tags: [Bridge Server]
      summary: Abort credit
      description: |
        The hub calls this to cancel a prepared credit before deposit.
        Respond `202 Accepted`, then call `POST /intents/{handle}/proofs`
        with `custom.status: "aborted"`.
      parameters:
        - $ref: '#/components/parameters/handle'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BridgeCreditAbortRequest'
      responses:
        '202':
          description: Abort accepted

# ═══════════════════════════════════════════════════════════════════════════
# COMPONENTS
# ═══════════════════════════════════════════════════════════════════════════

components:

  # ── Security ───────────────────────────────────────────────────────────

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: Access token obtained from `POST /oauth/token`
    LedgerHeader:
      type: apiKey
      in: header
      name: x-ledger
      description: Handle of the target ledger (e.g. `demo-co`)

  # ── Parameters ─────────────────────────────────────────────────────────

  parameters:
    handle:
      name: handle
      in: path
      required: true
      schema:
        type: string
      description: Record handle
      example: pay-20260608-001

    walletHandle:
      name: handle
      in: path
      required: true
      schema:
        type: string
      description: Wallet handle (URL-encoded if contains special characters)
      example: svgs%3A10000001%40bancorojo.com.co

    limit:
      name: limit
      in: query
      schema:
        type: integer
        default: 25
        maximum: 100
      description: Maximum records to return

    offset:
      name: offset
      in: query
      schema:
        type: integer
        default: 0
      description: Records to skip

  # ── Common responses ───────────────────────────────────────────────────

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: schema-validation-failed
              message: Required field 'schema' is missing from data

    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: unauthorized
              message: Bearer token is missing or expired

    NotFound:
      description: Record not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: not-found
              message: Wallet not found

    Conflict:
      description: Handle already exists
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: handle-conflict
              message: A record with this handle already exists

    TooManyRequests:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds before retry
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  # ── Schemas ────────────────────────────────────────────────────────────

  schemas:

    # Auth

    TokenRequest:
      type: object
      required: [grant_type, client_id, client_secret]
      properties:
        grant_type:
          type: string
          const: client_credentials
        client_id:
          type: string
          example: hub-client-001
        client_secret:
          type: string
          format: password
          example: "••••••••"

    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
          example: eyJhbGciOiJFZERTQSJ9...
        expires_in:
          type: integer
          description: Token lifetime in seconds
          example: 3600

    # Common

    Proof:
      type: object
      required: [method, public, digest, result]
      description: Ed25519 cryptographic proof over the RFC 8785 canonical `data`
      properties:
        method:
          type: string
          const: ed25519-v2
        public:
          type: string
          description: Base64-encoded Ed25519 public key (raw, 32 bytes)
          example: MCowBQYDK2VdAyEAabc123...
        digest:
          type: string
          description: Hex-encoded SHA-256 hash of the canonical `data`
          example: a3f5d9b1c2e4f0...
        result:
          type: string
          description: Base64-encoded Ed25519 signature over `digest`
          example: MEQCIB3x...

    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              example: not-found
            message:
              type: string
              example: Record not found
            detail:
              type: object
              additionalProperties: true

    PaginationMeta:
      type: object
      properties:
        total:
          type: integer
          example: 4
        limit:
          type: integer
          example: 25
        offset:
          type: integer
          example: 0

    SymbolRef:
      description: Symbol reference — object or string shorthand
      oneOf:
        - type: object
          required: [handle]
          properties:
            handle:
              type: string
              example: cop
        - type: string
          example: cop

    WalletRef:
      type: object
      description: Wallet reference with optional contextual metadata
      properties:
        handle:
          type: string
          example: svgs:10000001@bancorojo.com.co
        custom:
          type: object
          additionalProperties: true
          description: Contextual metadata carried in the claim (name, document, etc.)
          example:
            name: Juan Carlos Perez Gomez
            person: natural
            scheme: cc
            number: "1023456789"
            country: co

    AccessRule:
      type: object
      description: Single access-control rule
      properties:
        action:
          description: Action(s) permitted (`any`, `read`, `query`, `create`, or `$in` array)
          oneOf:
            - type: string
              example: any
            - type: object
              properties:
                $in:
                  type: array
                  items:
                    type: string
                  example: [read, query]
        record:
          description: Record type(s) this rule applies to
          oneOf:
            - type: string
              example: any
            - type: object
              properties:
                $in:
                  type: array
                  items:
                    type: string
                  example: [wallet, anchor, intent]
        signer:
          type: object
          description: Signer-based constraint (circle membership check)
          properties:
            $circle:
              type: string
              example: owner
        bearer:
          type: object
          description: Bearer-token-based constraint
          properties:
            $signer:
              type: object
              properties:
                $circle:
                  type: string
                  example: services

    # Wallets

    WalletData:
      type: object
      required: [handle, schema]
      properties:
        handle:
          type: string
          description: |
            Unique wallet identifier. Format: `scheme:number@domain` for
            account wallets, or `name@domain` for hub wallets.
          example: svgs:10000001@bancorojo.com.co
        schema:
          type: string
          description: Schema handle governing `custom` field validation
          example: banks
        bridge:
          type: string
          description: Bridge handle that owns this wallet's external account
          example: bancorojo.com.co/minka
        custom:
          type: object
          additionalProperties: true
          description: Schema-defined metadata
          example:
            name: Banco Rojo
            country: co
            code: "1099"
            scheme: nit
            number: "890100001"
        access:
          type: array
          items:
            $ref: '#/components/schemas/AccessRule'

    WalletCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/WalletData'
        hash:
          type: string
          description: Hex SHA-256 of RFC 8785 canonical `data`
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    WalletResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/WalletData'
        meta:
          type: object
          properties:
            created:
              type: string
              format: date-time
            updated:
              type: string
              format: date-time

    WalletListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WalletData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    Balance:
      type: object
      properties:
        symbol:
          type: string
          example: cop
        amount:
          type: string
          description: Total balance in smallest currency unit (string to avoid overflow)
          example: "18500000"
        available:
          type: string
          description: Spendable balance (excludes pending debits)
          example: "18500000"

    BalanceListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Balance'

    # Intents

    Claim:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [transfer, issue, destroy, limit]
        amount:
          type: integer
          description: Amount in smallest currency unit
          example: 18500000
        symbol:
          $ref: '#/components/schemas/SymbolRef'
        source:
          $ref: '#/components/schemas/WalletRef'
        target:
          $ref: '#/components/schemas/WalletRef'
        metric:
          type: string
          enum: [minBalance, maxBalance]
          description: "Constraint type — only with action: limit"
        wallet:
          $ref: '#/components/schemas/WalletRef'

    IntentConfig:
      type: object
      properties:
        commit:
          type: string
          enum: [auto, manual]
          default: auto
          description: |
            `auto` — commit immediately after proofs collected.
            `manual` — wait for explicit commit.

    IntentData:
      type: object
      required: [handle, claims]
      properties:
        handle:
          type: string
          description: Unique intent identifier (also the idempotency key)
          example: pay-breb-20260608-001
        schema:
          type: string
          description: Intent schema handle (`payment`, `limit`, …)
          example: payment
        claims:
          type: array
          items:
            $ref: '#/components/schemas/Claim'
          minItems: 1
        config:
          $ref: '#/components/schemas/IntentConfig'
        custom:
          type: object
          additionalProperties: true
          example:
            reference: qr:TERMINAL01@comercio-ejemplo.com.co
            description: QR payment at Comercio Ejemplo — 185,000.00 COP
        access:
          type: array
          items:
            $ref: '#/components/schemas/AccessRule'

    IntentCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/IntentData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    IntentMeta:
      type: object
      properties:
        status:
          type: string
          enum: [pending, prepared, committed, completed, rejected]
        reason:
          type: string
          description: Machine-readable rejection reason (when `status = rejected`)
          example: insufficient-funds
        detail:
          type: string
          description: Human-readable rejection message
        created:
          type: string
          format: date-time
        updated:
          type: string
          format: date-time

    IntentResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/IntentData'
        meta:
          $ref: '#/components/schemas/IntentMeta'

    IntentListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/IntentData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    IntentProofRequest:
      type: object
      required: [method, public, digest, result]
      allOf:
        - $ref: '#/components/schemas/Proof'
      properties:
        custom:
          type: object
          required: [moment, handle, status]
          properties:
            moment:
              type: string
              format: date-time
              description: Timestamp of the bridge operation
              example: "2026-06-08T14:30:00.000Z"
            handle:
              type: string
              description: Debit or credit handle this proof refers to
              example: deb_01u9RGCevt4rEkRMV
            status:
              type: string
              enum: [prepared, committed, failed, aborted]
            coreId:
              type: string
              description: Transaction ID in the core banking system
              example: BREB-TXN-20260608-001
            reason:
              type: string
              description: Machine-readable failure code
              enum:
                - bridge.account-insufficient-balance
                - bridge.account-not-found
                - bridge.account-frozen
                - bridge.account-inactive
                - bridge.validation-failed
                - bridge.authorization-failed
            detail:
              type: string
              description: Human-readable failure detail

    # Anchors

    AnchorData:
      type: object
      required: [handle, schema]
      properties:
        handle:
          type: string
          description: |
            Anchor handle. Format by schema:
            - `emvco-v14-co`: `qr:<reference>@<merchant-domain>`
            - `alias` static: `mailto:<email>`, `tel:<E.164>`, `nidn:<digits>`, `merchid:<code>`
            - `alias` dynamic: `key:<reference>@<domain>`
          example: qr:TERMINAL01@comercio-ejemplo.com.co
        schema:
          type: string
          enum: [emvco-v14-co, alias]
        wallet:
          type: string
          description: Target wallet handle (string, not object)
          example: main@comercio-ejemplo.com.co
        target:
          type: string
          description: Bre-B routing target (NIT, merchant code, or alias target)
          example: nit:900100001
        amount:
          type: integer
          description: Fixed amount for dynamic QR / dynamic Llave (smallest currency unit)
          example: 18500000
        symbol:
          $ref: '#/components/schemas/SymbolRef'
        custom:
          type: object
          additionalProperties: true
        access:
          type: array
          items:
            $ref: '#/components/schemas/AccessRule'

    AnchorCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/AnchorData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    AnchorResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/AnchorData'
        meta:
          type: object
          properties:
            created:
              type: string
              format: date-time

    AnchorListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/AnchorData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Effects

    EffectData:
      type: object
      required: [handle, signal, action]
      properties:
        handle:
          type: string
          example: notify-payment-received
        signal:
          type: string
          description: |
            Ledger event that triggers this effect. Full list of supported signals:
            - **Anchor**: `anchor-created`, `anchor-updated`, `anchor-dropped`, `anchor-proofs-added`
            - **Balance**: `balance-received`
            - **Bridge**: `bridge-created`, `bridge-updated`, `bridge-dropped`, `bridge-proofs-added`, `bridge-entry-created`, `bridge-entry-updated`, `bridge-entry-proofs-added`
            - **Circle**: `circle-created`, `circle-updated`, `circle-proofs-added`
            - **Domain**: `domain-created`, `domain-updated`, `domain-proofs-added`
            - **Effect**: `effect-created`, `effect-updated`, `effect-dropped`, `effect-proofs-added`
            - **Intent**: `intent-created`, `intent-updated`, `intent-proofs-added`
            - **Ledger**: `ledger-created`, `ledger-updated`, `ledger-dropped`, `ledger-proofs-added`
            - **Policy**: `policy-created`, `policy-updated`, `policy-dropped`, `policy-proofs-added`
            - **Report**: `report-created`, `report-updated`, `report-dropped`, `report-proofs-added`
            - **Request**: `request-created`, `request-updated`, `request-proofs-added`
            - **Schema**: `schema-created`, `schema-updated`, `schema-proofs-added`
            - **Signer**: `signer-created`, `signer-updated`, `signer-proofs-added`, `signer-factor-created`, `signer-factor-updated`, `signer-factor-dropped`, `signer-factor-proofs-added`
            - **Symbol**: `symbol-created`, `symbol-updated`, `symbol-proofs-added`
            - **Wallet**: `wallet-created`, `wallet-updated`, `wallet-dropped`, `wallet-limited`, `wallet-proofs-added`
          enum:
            - anchor-created
            - anchor-updated
            - anchor-dropped
            - anchor-proofs-added
            - balance-received
            - bridge-created
            - bridge-updated
            - bridge-dropped
            - bridge-proofs-added
            - bridge-entry-created
            - bridge-entry-updated
            - bridge-entry-proofs-added
            - circle-created
            - circle-updated
            - circle-proofs-added
            - domain-created
            - domain-updated
            - domain-proofs-added
            - effect-created
            - effect-updated
            - effect-dropped
            - effect-proofs-added
            - intent-created
            - intent-updated
            - intent-proofs-added
            - ledger-created
            - ledger-updated
            - ledger-dropped
            - ledger-proofs-added
            - policy-created
            - policy-updated
            - policy-dropped
            - policy-proofs-added
            - report-created
            - report-updated
            - report-dropped
            - report-proofs-added
            - request-created
            - request-updated
            - request-proofs-added
            - schema-created
            - schema-updated
            - schema-proofs-added
            - signer-created
            - signer-updated
            - signer-proofs-added
            - signer-factor-created
            - signer-factor-updated
            - signer-factor-dropped
            - signer-factor-proofs-added
            - symbol-created
            - symbol-updated
            - symbol-proofs-added
            - wallet-created
            - wallet-updated
            - wallet-dropped
            - wallet-limited
            - wallet-proofs-added
        filter:
          type: object
          additionalProperties:
            type: string
          description: Dot-path filters — only fire when all match
          example:
            wallet.data.handle: main@comercio-ejemplo.com.co
        action:
          description: |
            How to deliver the notification when a matching signal fires.
            - `schema: webhook` — POST the event payload to your HTTPS endpoint.
            - `schema: bridge` — deliver via a bridge registered on the ledger.
          oneOf:
            - title: Webhook
              type: object
              required: [schema, endpoint]
              properties:
                schema:
                  type: string
                  enum: [webhook]
                endpoint:
                  type: string
                  format: uri
                  description: HTTPS endpoint that receives the event payload
                  example: https://comercio-ejemplo.com.co/api/webhooks/minka
            - title: Bridge
              type: object
              required: [schema, bridge]
              properties:
                schema:
                  type: string
                  enum: [bridge]
                bridge:
                  type: string
                  description: Handle of a bridge registered on this ledger
                  example: studio

    EffectCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/EffectData'

    EffectResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/EffectData'

    EffectListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/EffectData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    EffectDeliveryPayload:
      type: object
      description: |
        JSON body POSTed to your webhook endpoint when an effect fires.
        Respond `202 Accepted`. Any other 2xx is treated as accepted.
        `501` permanently stops retries for this effect.
      properties:
        data:
          type: object
          properties:
            handle:
              type: string
              description: Unique event delivery identifier
            signal:
              type: string
            wallet:
              type: object
              properties:
                data:
                  $ref: '#/components/schemas/WalletData'
            symbol:
              type: object
              properties:
                data:
                  type: object
                  properties:
                    handle:
                      type: string
                      example: cop
            intent:
              type: object
              properties:
                data:
                  $ref: '#/components/schemas/IntentData'
            amount:
              type: string
              description: Amount in smallest currency unit
              example: "18500000"

    # Schemas

    SchemaData:
      type: object
      required: [handle, record, format, schema]
      properties:
        handle:
          type: string
          example: banks
        record:
          type: string
          enum: [wallet, anchor, intent, signer, policy]
        format:
          type: string
          const: json-schema
        schema:
          type: object
          description: JSON Schema definition for the `custom` field of this record type
          additionalProperties: true

    SchemaCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/SchemaData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    SchemaResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/SchemaData'

    SchemaListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SchemaData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Symbols

    SymbolData:
      type: object
      required: [handle, factor]
      properties:
        handle:
          type: string
          example: cop
        factor:
          type: integer
          description: Display unit divisor (100 = 2 decimal places)
          example: 100
        custom:
          type: object
          additionalProperties: true

    SymbolCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/SymbolData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    SymbolResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/SymbolData'

    SymbolListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SymbolData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Circles

    CircleData:
      type: object
      required: [handle]
      properties:
        handle:
          type: string
          example: services

    CircleCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/CircleData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    CircleResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/CircleData'

    CircleListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/CircleData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    CircleSignerAssignment:
      type: object
      required: [data]
      properties:
        data:
          type: object
          required: [handle]
          properties:
            handle:
              type: string
              description: Signer handle to assign to this circle
              example: svc-bridge-rojo

    # Policies

    PolicyData:
      type: object
      required: [handle, schema, record, values]
      properties:
        handle:
          type: string
          example: hub-owner-access
        schema:
          type: string
          const: access
        record:
          type: string
          description: Record type scope (`any` applies to all types)
          example: any
        values:
          type: array
          items:
            $ref: '#/components/schemas/AccessRule'
        custom:
          type: object
          additionalProperties: true

    PolicyCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/PolicyData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    PolicyResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/PolicyData'

    PolicyListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/PolicyData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Bridges

    BridgeAuth:
      type: object
      description: Authentication for hub→bridge outbound calls
      properties:
        schema:
          type: string
          enum: [header, oauth2, mtls]
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom headers (used when schema=header)
        clientId:
          type: string
          description: OAuth2 client ID (schema=oauth2)
        clientSecret:
          type: string
          description: OAuth2 client secret (schema=oauth2)
        tokenUrl:
          type: string
          format: uri
          description: OAuth2 token endpoint (schema=oauth2)
        public:
          type: string
          description: mTLS client certificate PEM (schema=mtls)
        secret:
          type: string
          description: mTLS client private key PEM (schema=mtls)

    BridgeData:
      type: object
      required: [handle, traits]
      properties:
        handle:
          type: string
          description: Bridge handle — typically `<domain>/<slug>`
          example: bancorojo.com.co/minka
        url:
          type: string
          format: uri
          description: Base URL of your bridge server
          example: https://bridge.bancorojo.com.co/v2
        traits:
          type: array
          items:
            type: string
            enum: [debits, credits, statuses, anchors, domains, effects]
          description: |
            Capabilities this bridge provides. Omit to enable all traits.
            - `debits` — bridge handles debit prepare/commit/abort
            - `credits` — bridge handles credit prepare/commit/abort
            - `statuses` — bridge can receive intent status calls
            - `anchors` — bridge handles anchor operations
            - `domains` — bridge handles domain lookups
            - `effects` — bridge receives effect event deliveries
          example: [debits, credits]
        auth:
          $ref: '#/components/schemas/BridgeAuth'
        custom:
          type: object
          additionalProperties: true

    BridgeCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/BridgeData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    BridgeResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeData'

    BridgeListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/BridgeData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Signers

    SignerData:
      type: object
      required: [handle, schema, public]
      properties:
        handle:
          type: string
          example: svc-bridge-rojo
        schema:
          type: string
          example: service
        public:
          type: string
          description: Base64-encoded Ed25519 public key (raw, 32 bytes)
          example: MCowBQYDK2VdAyEAabc123...
        custom:
          type: object
          additionalProperties: true

    SignerCreateRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: '#/components/schemas/SignerData'
        hash:
          type: string
        proofs:
          type: array
          items:
            $ref: '#/components/schemas/Proof'

    SignerResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/SignerData'

    SignerListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SignerData'
        meta:
          $ref: '#/components/schemas/PaginationMeta'

    # Bridge Server (inbound from hub)

    BridgeOperationData:
      type: object
      description: Debit or credit operation payload sent by the hub
      properties:
        handle:
          type: string
          description: Operation handle — use as idempotency key
          example: deb_01u9RGCevt4rEkRMV
        luid:
          type: string
          description: Ledger-unique identifier for correlation
          example: "$deb.7788"
        schema:
          type: string
          enum: [debit, credit]
        amount:
          type: integer
          example: 18500000
        symbol:
          $ref: '#/components/schemas/SymbolRef'
        source:
          $ref: '#/components/schemas/WalletRef'
        target:
          $ref: '#/components/schemas/WalletRef'
        intent:
          type: object
          description: Parent intent reference
          properties:
            data:
              type: object
              properties:
                handle:
                  type: string
                  example: pay-breb-20260608-001
            hash:
              type: string
            luid:
              type: string
              example: "$int.7788"
        inputs:
          type: array
          description: |
            Indices into `data.intent.data.claims` that this debit or credit
            entry corresponds to. Use this to locate the exact claim(s) within
            the intent that the hub is asking you to process.
          items:
            type: integer
          example: [0]

    BridgeDebitRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string
        meta:
          type: object
          properties:
            proofs:
              type: array
              items:
                $ref: '#/components/schemas/Proof'

    BridgeCreditRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string
        meta:
          type: object
          properties:
            proofs:
              type: array
              items:
                $ref: '#/components/schemas/Proof'

    BridgeDebitCommitRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string

    BridgeDebitAbortRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string

    BridgeCreditCommitRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string

    BridgeCreditAbortRequest:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/BridgeOperationData'
        hash:
          type: string

  # ── Examples ───────────────────────────────────────────────────────────

  examples:

    # Auth

    TokenRequestExample:
      value:
        grant_type: client_credentials
        client_id: hub-client-001
        client_secret: "s3cr3t"

    TokenResponseExample:
      value:
        access_token: eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJodWItY2xpZW50LTAwMSJ9.abc123
        expires_in: 3600

    # Wallets

    WalletBankCreate:
      value:
        data:
          handle: svgs:10000001@bancorojo.com.co
          schema: banks
          custom:
            name: Banco Rojo
            country: co
            code: "1099"
            scheme: nit
            number: "890100001"
          access:
            - action: any
              signer:
                $circle: owner
            - action:
                $in: [read, query]
              bearer:
                $signer:
                  $circle: services
        hash: a3f5d9b1c2e4f0a8...
        proofs:
          - method: ed25519-v2
            public: MCowBQYDK2VdAyEAabc123...
            digest: a3f5d9b1c2e4f0a8...
            result: MEQCIB3xdef...

    WalletMerchantCreate:
      value:
        data:
          handle: main@comercio-ejemplo.com.co
          schema: merchant
          custom:
            name: Comercio Ejemplo SAS
            country: co
            code: HUB001
            scheme: nit
            number: "900100001"
            category: "5814"
            city: Bogota D.C.
            postal: "110111"
        hash: b4e6d0c2d3f1...
        proofs:
          - method: ed25519-v2
            public: MCowBQYDK2VdAyEAabc123...
            digest: b4e6d0c2d3f1...
            result: MEQCIB...

    WalletTreasuryCreate:
      value:
        data:
          handle: treasury@hub.co
          schema: treasury
          custom:
            name: Hub Treasury
            country: co
            description: Internal treasury — float for merchant working capital
        hash: c5f7e1d3...
        proofs:
          - method: ed25519-v2
            public: MCowBQYDK2VdAyEAabc123...
            digest: c5f7e1d3...
            result: MEQCIB...

    WalletAchCreate:
      value:
        data:
          handle: ach@bancoazul.com.co
          schema: network
          custom:
            name: Banco Azul ACH
            country: co
            code: BRAZ
            scheme: nit
            number: "890300003"
            description: ACH network connectivity wallet for Banco Azul

    WalletBankResponse:
      value:
        data:
          handle: svgs:10000001@bancorojo.com.co
          schema: banks
          custom:
            name: Banco Rojo
            country: co
            code: "1099"
            scheme: nit
            number: "890100001"
        meta:
          created: "2026-06-08T10:00:00.000Z"
          updated: "2026-06-08T10:00:00.000Z"

    WalletMerchantResponse:
      value:
        data:
          handle: main@comercio-ejemplo.com.co
          schema: merchant
          custom:
            name: Comercio Ejemplo SAS
            country: co
            code: HUB001
            scheme: nit
            number: "900100001"
            category: "5814"
            city: Bogota D.C.
            postal: "110111"
        meta:
          created: "2026-06-08T10:05:00.000Z"
          updated: "2026-06-08T10:05:00.000Z"

    WalletListResponse:
      value:
        data:
          - handle: svgs:10000001@bancorojo.com.co
            schema: banks
            custom:
              name: Banco Rojo
              country: co
          - handle: svgs:20000001@bancoamarillo.com.co
            schema: banks
            custom:
              name: Banco Amarillo
              country: co
          - handle: ach@bancoazul.com.co
            schema: network
            custom:
              name: Banco Azul ACH
              country: co
          - handle: main@comercio-ejemplo.com.co
            schema: merchant
            custom:
              name: Comercio Ejemplo SAS
              country: co
          - handle: treasury@hub.co
            schema: treasury
            custom:
              name: Hub Treasury
              country: co
          - handle: fees@hub.co
            schema: fees
            custom:
              name: Hub Fees
              country: co
        meta:
          total: 6
          limit: 25
          offset: 0

    WalletBalancesResponse:
      value:
        data:
          - symbol: cop
            amount: "18500000"
            available: "18500000"

    # Intents

    IntentInboundPayment:
      value:
        data:
          handle: pay-breb-20260608-001
          schema: payment
          claims:
            - action: transfer
              amount: 18500000
              symbol:
                handle: cop
              source:
                handle: svgs:10000001@bancorojo.com.co
                custom:
                  name: Juan Carlos Perez Gomez
                  person: natural
                  scheme: cc
                  number: "1023456789"
                  country: co
              target:
                handle: svgs:99999999@bancoamarillo.com.co
                custom:
                  name: Hub Aggregate Account
                  person: legal
                  scheme: nit
                  number: "900100001"
                  country: co
          config:
            commit: auto
          custom:
            reference: qr:TERMINAL01@comercio-ejemplo.com.co
            description: "QR payment at Comercio Ejemplo — $185,000.00 COP"
        hash: d6e8f2a4b5c7...
        proofs:
          - method: ed25519-v2
            public: MCowBQYDK2VdAyEAabc123...
            digest: d6e8f2a4b5c7...
            result: MEQCIB...

    IntentMerchantAllocation:
      value:
        data:
          handle: alloc-20260608-001
          schema: payment
          claims:
            - action: transfer
              amount: 18500000
              symbol:
                handle: cop
              source:
                handle: svgs:99999999@bancoamarillo.com.co
                custom:
                  name: Hub Aggregate Account
                  person: legal
                  scheme: nit
                  number: "900100001"
                  country: co
              target:
                handle: main@comercio-ejemplo.com.co
                custom:
                  name: Comercio Ejemplo SAS
                  person: legal
                  scheme: nit
                  number: "900100001"
                  country: co
          config:
            commit: auto
          custom:
            reference: qr:TERMINAL01@comercio-ejemplo.com.co
            description: Merchant allocation after settled QR collection

    IntentTreasuryFunding:
      value:
        data:
          handle: fund-20260608-001
          schema: payment
          claims:
            - action: transfer
              amount: 500000000
              symbol:
                handle: cop
              source:
                handle: treasury@hub.co
                custom:
                  name: Hub Treasury
                  country: co
              target:
                handle: main@comercio-ejemplo.com.co
                custom:
                  name: Comercio Ejemplo SAS
                  person: legal
                  scheme: nit
                  number: "900100001"
                  country: co
          config:
            commit: auto
          custom:
            reference: funding-comercio-20260608
            description: "Working-capital advance — $5,000,000.00 COP"

    IntentFeeCollection:
      value:
        data:
          handle: fee-20260608-001
          schema: payment
          claims:
            - action: transfer
              amount: 353350
              symbol:
                handle: cop
              source:
                handle: main@comercio-ejemplo.com.co
                custom:
                  name: Comercio Ejemplo SAS
                  person: legal
                  scheme: nit
                  number: "900100001"
                  country: co
              target:
                handle: fees@hub.co
                custom:
                  name: Hub Fees
                  country: co
          config:
            commit: auto
          custom:
            reference: fees-comercio-20260608
            description: "Acquiring fee on $185,000 COP at 1.91% = $3,533.50 COP"

    IntentMinBalance:
      value:
        data:
          handle: limit-cop-comercio-min-balance
          schema: limit
          claims:
            - action: limit
              metric: minBalance
              wallet:
                handle: main@comercio-ejemplo.com.co
              symbol:
                handle: cop
              amount: 0
          custom:
            description: No-overdraft floor for merchant wallet

    IntentResponseCompleted:
      value:
        data:
          handle: pay-breb-20260608-001
          schema: payment
          claims:
            - action: transfer
              amount: 18500000
              symbol:
                handle: cop
              source:
                handle: svgs:10000001@bancorojo.com.co
              target:
                handle: svgs:99999999@bancoamarillo.com.co
          config:
            commit: auto
        meta:
          status: completed
          created: "2026-06-08T14:30:00.000Z"
          updated: "2026-06-08T14:30:01.200Z"

    IntentResponseRejected:
      value:
        data:
          handle: pay-breb-20260608-002
          schema: payment
          claims:
            - action: transfer
              amount: 9999999999
              symbol:
                handle: cop
              source:
                handle: svgs:10000001@bancorojo.com.co
              target:
                handle: svgs:99999999@bancoamarillo.com.co
        meta:
          status: rejected
          reason: insufficient-funds
          detail: "Source wallet svgs:10000001@bancorojo.com.co has insufficient COP balance"

    IntentProofPrepared:
      value:
        method: ed25519-v2
        public: MCowBQYDK2VdAyEAabc123...
        digest: f8a0b1c2d3e4...
        result: MEQCIB...
        custom:
          moment: "2026-06-08T14:30:00.500Z"
          handle: deb_01u9RGCevt4rEkRMV
          status: prepared
          coreId: BREB-TXN-20260608-001

    IntentProofCommitted:
      value:
        method: ed25519-v2
        public: MCowBQYDK2VdAyEAabc123...
        digest: g9b1c2d3e4f5...
        result: MEQCIB...
        custom:
          moment: "2026-06-08T14:30:01.000Z"
          handle: cre_baC0LUTVW9lzYA284
          status: committed
          coreId: BREB-CRE-20260608-001

    IntentProofFailed:
      value:
        method: ed25519-v2
        public: MCowBQYDK2VdAyEAabc123...
        digest: h0c2d3e4f5a6...
        result: MEQCIB...
        custom:
          moment: "2026-06-08T14:30:00.800Z"
          handle: deb_02x0SHDevt5sFlSNW
          status: failed
          reason: bridge.account-insufficient-balance
          detail: "svgs:10000001@bancorojo.com.co balance is 0 COP; required 185,000.00 COP"

    # Anchors

    AnchorQRStatic:
      value:
        data:
          handle: qr:TERMINAL01@comercio-ejemplo.com.co
          schema: emvco-v14-co
          wallet: main@comercio-ejemplo.com.co
          target: nit:900100001
          symbol:
            handle: cop
          custom:
            merchant:
              name: Comercio Ejemplo SAS
              city: Bogota D.C.
              postal: "110111"
              category: "5814"
              documentNumber: "900100001"
            reference: COM-TERMINAL-01
            channel: POS
            purpose: "00"
            terminal: COMBOGCTR01

    AnchorQRDynamic:
      value:
        data:
          handle: qr:ORDER20260608001@comercio-ejemplo.com.co
          schema: emvco-v14-co
          wallet: main@comercio-ejemplo.com.co
          target: "@ORDER20260608001"
          amount: 18500000
          symbol:
            handle: cop
          custom:
            merchant:
              name: Comercio Ejemplo SAS
              city: Bogota D.C.
              postal: "110111"
              category: "5814"
              documentNumber: "900100001"
            reference: COM-ORDER-20260608-001
            expiry: 300
            channel: ECOMM
            purpose: "00"
            terminal: COMWEBCHECKOUT

    AnchorQRStaticResponse:
      value:
        data:
          handle: qr:TERMINAL01@comercio-ejemplo.com.co
          schema: emvco-v14-co
          wallet: main@comercio-ejemplo.com.co
          target: nit:900100001
          symbol:
            handle: cop
          custom:
            merchant:
              name: Comercio Ejemplo SAS
              city: Bogota D.C.
              postal: "110111"
              category: "5814"
              documentNumber: "900100001"
            reference: COM-TERMINAL-01
            channel: POS
        meta:
          created: "2026-06-08T10:00:00.000Z"

    AnchorLlaveMerchid:
      value:
        data:
          handle: merchid:COMERCIO01
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: main@comercio-ejemplo.com.co
          symbol: cop
          custom:
            name: Comercio Ejemplo SAS
            person: legal
            country: co
            scheme: nit
            number: "900100001"
            reference: DICE-comercio-merchid-01
            settlementTarget: svgs:99999999@bancoamarillo.com.co
            status: active

    AnchorLlaveNidn:
      value:
        data:
          handle: nidn:900100001
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: nidn:900100001@comercio-ejemplo.com.co
          symbol: cop
          custom:
            name: Comercio Ejemplo SAS
            person: legal
            country: co
            scheme: nit
            number: "900100001"
            reference: DICE-comercio-nit
            settlementTarget: svgs:99999999@bancoamarillo.com.co
            status: active

    AnchorLlaveNidnResponse:
      value:
        data:
          handle: nidn:900100001
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: nidn:900100001@comercio-ejemplo.com.co
          symbol: cop
          custom:
            name: Comercio Ejemplo SAS
            person: legal
            country: co
            scheme: nit
            number: "900100001"
            status: active
        meta:
          created: "2026-06-08T10:00:00.000Z"

    AnchorLlaveMail:
      value:
        data:
          handle: mailto:pagos@comercio-ejemplo.com.co
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: mailto:pagos@comercio-ejemplo.com.co
          symbol: cop
          custom:
            name: Comercio Ejemplo SAS
            person: legal
            country: co
            scheme: nit
            number: "900100001"
            reference: DICE-comercio-mail
            settlementTarget: svgs:99999999@bancoamarillo.com.co
            status: active

    AnchorLlaveTel:
      value:
        data:
          handle: "tel:+573001234567"
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: "tel:+573001234567"
          symbol: cop
          custom:
            name: Comercio Ejemplo SAS
            person: legal
            country: co
            scheme: nit
            number: "900100001"
            reference: DICE-comercio-tel
            settlementTarget: svgs:99999999@bancoamarillo.com.co
            status: active

    AnchorLlaveDynamic:
      value:
        data:
          handle: key:order-20260608-001@comercio-ejemplo.com.co
          schema: alias
          wallet: main@comercio-ejemplo.com.co
          target: order:20260608-001@comercio-ejemplo.com.co
          amount: 18500000
          symbol:
            handle: cop
          custom:
            reference: COM-ORDER-20260608-001
            expiry: 300
            channel: web
            settlementTarget: svgs:99999999@bancoamarillo.com.co
            status: active
            description: "Dynamic Llave for order 001 — $185,000.00 COP. Expires in 5 min."

    # Effects

    EffectBalanceReceived:
      value:
        data:
          handle: notify-balance-received-comercio
          signal: balance-received
          filter:
            wallet.data.handle: main@comercio-ejemplo.com.co
          action:
            schema: webhook
            endpoint: https://comercio-ejemplo.com.co/api/webhooks/minka

    EffectIntentCompleted:
      value:
        data:
          handle: notify-intent-updated-hub
          signal: intent-updated
          filter:
            wallet.data.domain: comercio-ejemplo.com.co
          action:
            schema: webhook
            endpoint: https://comercio-ejemplo.com.co/api/webhooks/minka

    # Schemas

    SchemaBanks:
      value:
        data:
          handle: banks
          record: wallet
          format: json-schema
          schema:
            type: object
            additionalProperties: true
            description: |
              ACH participant bank wallets.
              Typical custom fields: name, country (co), code (ACH participant code),
              scheme (nit), number (NIT digits without check digit).

    SchemaMerchant:
      value:
        data:
          handle: merchant
          record: wallet
          format: json-schema
          schema:
            type: object
            additionalProperties: true
            description: |
              Per-merchant wallet hosted at merchant domain.
              Typical custom fields: name, country, code (hub merchant code),
              scheme (nit), number, category (MCC), city, postal.

    SchemaAlias:
      value:
        data:
          handle: alias
          record: anchor
          format: json-schema
          schema:
            type: object
            additionalProperties: true
            description: |
              Bre-B Llave anchors — static (mailto:, tel:, nidn:, merchid:) and
              dynamic per-transaction (key:).
              Typical custom fields: name, person, country, scheme, number,
              reference, settlementTarget, status, expiry (dynamic only).

    SchemaListResponseCO:
      value:
        data:
          - handle: banks
            record: wallet
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: network
            record: wallet
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: treasury
            record: wallet
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: fees
            record: wallet
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: merchant
            record: wallet
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: emvco-v14-co
            record: anchor
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: alias
            record: anchor
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: payment
            record: intent
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: limit
            record: intent
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: service
            record: signer
            format: json-schema
            schema:
              type: object
              additionalProperties: true
          - handle: access
            record: policy
            format: json-schema
            schema:
              type: object
              additionalProperties: true
        meta:
          total: 11
          limit: 25
          offset: 0

    # Symbols

    SymbolCOP:
      value:
        data:
          handle: cop
          factor: 100
          custom:
            display: COP
            kind: fiat
            country: co
            settlement: true
        hash: i1d3e4f5...
        proofs:
          - method: ed25519-v2
            public: MCowBQYDK2VdAyEAabc123...
            digest: i1d3e4f5...
            result: MEQCIB...

    SymbolCOPResponse:
      value:
        data:
          handle: cop
          factor: 100
          custom:
            display: COP
            kind: fiat
            country: co
            settlement: true

    # Circles

    CircleServices:
      value:
        data:
          handle: services

    CircleListResponse:
      value:
        data:
          - handle: owner
          - handle: admin
          - handle: operations
          - handle: security
          - handle: support
          - handle: services
        meta:
          total: 6
          limit: 25
          offset: 0

    CircleSignerAssignment:
      value:
        data:
          handle: svc-bridge-rojo

    # Policies

    PolicyOwnerAccess:
      value:
        data:
          handle: hub-owner-access
          schema: access
          record: any
          values:
            - action: any
              record: any
              signer:
                $circle: owner
            - action:
                $in: [read, query]
              record: any
              bearer:
                $signer:
                  $circle: owner

    PolicyServiceAccess:
      value:
        data:
          handle: hub-service-access
          schema: access
          record: any
          values:
            - action:
                $in: [create, read, query]
              record:
                $in: [wallet, anchor, intent, anchor-proof, intent-proof]
              signer:
                $circle: services
            - action:
                $in: [create, read, query]
              record:
                $in: [wallet, anchor, intent]
              bearer:
                $signer:
                  $circle: services
          custom:
            auth: Supports OAuth2 bearer-token impersonation for client-to-ledger mutations

    # Bridges

    BridgeBankCreate:
      value:
        data:
          handle: bancorojo.com.co/minka
          url: https://bridge.bancorojo.com.co/v2
          traits: [debits, credits]
          auth:
            schema: oauth2
            clientId: minka-hub-client
            clientSecret: "s3cr3t"
            tokenUrl: https://auth.bancorojo.com.co/oauth/token

    BridgeAchCreate:
      value:
        data:
          handle: bancoazul.com.co/ach
          url: https://ach.bancoazul.com.co/v2
          traits: [debits, credits]
          auth:
            schema: oauth2
            clientId: minka-ach-client
            clientSecret: "s3cr3t"
            tokenUrl: https://ach.bancoazul.com.co/oauth/token

    BridgeAnchorsCreate:
      value:
        data:
          handle: bridge.api/anchors
          url: https://anchors.minka.io/v2
          traits: [anchors]
          auth:
            schema: header
            headers:
              X-Bridge-Key: "s3cr3t"

    # Signers

    SignerService:
      value:
        data:
          handle: svc-bridge-rojo
          schema: service
          public: MCowBQYDK2VdAyEAabc123...
          custom:
            description: Bridge signer for bancorojo.com.co/minka

    SignerServiceResponse:
      value:
        data:
          handle: svc-bridge-rojo
          schema: service
          public: MCowBQYDK2VdAyEAabc123...

    # Bridge Server

    BridgeDebitPrepare:
      value:
        data:
          handle: deb_01u9RGCevt4rEkRMV
          luid: "$deb.7788"
          schema: debit
          amount: 18500000
          symbol:
            handle: cop
          source:
            handle: svgs:10000001@bancorojo.com.co
            custom:
              name: Juan Carlos Perez Gomez
              scheme: cc
              number: "1023456789"
          target:
            handle: svgs:99999999@bancoamarillo.com.co
            custom:
              name: Hub Aggregate Account
              scheme: nit
              number: "900100001"
          inputs: [0]
          intent:
            data:
              handle: pay-breb-20260608-001
            hash: d6e8f2a4b5c7...
            luid: "$int.7788"
        hash: j2e4f5g6...
        meta:
          proofs: []

    BridgeCreditPrepare:
      value:
        data:
          handle: cre_baC0LUTVW9lzYA284
          luid: "$cre.7789"
          schema: credit
          amount: 18500000
          symbol:
            handle: cop
          source:
            handle: svgs:10000001@bancorojo.com.co
          target:
            handle: svgs:99999999@bancoamarillo.com.co
            custom:
              name: Hub Aggregate Account
              scheme: nit
              number: "900100001"
          inputs: [0]
          intent:
            data:
              handle: pay-breb-20260608-001
            hash: d6e8f2a4b5c7...
            luid: "$int.7788"
        hash: k3f5g6h7...
        meta:
          proofs: []
