openapi: 3.0.3
info:
  title: SBIX API
  version: "5.0.1"
  description: |
    Sovereign Proof Infrastructure — v5 endpoints (unicorn-ready).

    Modules:
      • Certify™: generate & export Merkle proofs
      • Verify: cryptographic inclusion verification
      • ComplianceOS™: YAML rules, test, replay, alerts
      • Reconcile: transaction ingestion + schema enforcement

    Notes:
      • Fields named `root` in inclusion payloads are the same value as `merkle_root` (kept for backward-compat).
      • Use header `X-API-Key` for Certify endpoints (no query-string api_key in production).
externalDocs:
  description: SBIX Product & Docs
  url: https://www.sbix.io/product.html
servers:
  - url: https://demo.sbix.io
    description: SBIX Demo (Pi / POP1)
  - url: http://127.0.0.1:8080
    description: Local dev

tags:
  - name: Health
    description: Liveness & basic RTT checks
  - name: Certify
    description: Create, list and export Merkle proofs
  - name: Verify
    description: Validate Merkle inclusion proofs
  - name: Compliance
    description: Ruleset engine, single-TX tests, batch, alerts
  - name: Export
    description: File exports (JSON/CSV/XBRL/ZIP/NDJSON)
  - name: Reconcile
    description: Transaction ingestion & schema hints

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

  parameters:
    Limit:
      in: query
      name: limit
      required: false
      description: Page size (cursor pagination)
      schema: { type: integer, minimum: 1, maximum: 200, default: 100 }
    Cursor:
      in: query
      name: cursor
      required: false
      description: Opaque, exclusive cursor
      schema: { type: string }
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      description: Idempotent write protection for safe retries
      schema: { type: string, minLength: 8, maxLength: 80 }

  headers:
    X-RateLimit-Limit:
      description: Request limit per time window
      schema: { type: integer }
    X-RateLimit-Remaining:
      description: Requests remaining in the current window
      schema: { type: integer }
    X-RateLimit-Reset:
      description: UTC epoch seconds until window resets
      schema: { type: integer }
    Retry-After:
      description: Seconds to wait before retrying (429)
      schema: { type: string }
    Content-Disposition:
      description: Suggested filename for downloads
      schema: { type: string }
    Access-Control-Expose-Headers:
      description: Headers exposed to browsers
      schema:
        type: string
        example: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, Content-Disposition

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ApiError" }
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ApiError" }
    NotFound:
      description: Not found
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ApiError" }
    PayloadTooLarge:
      description: Payload too large
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ApiError" }
    TooManyRequests:
      description: Too Many Requests
      headers:
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/json:
          schema: { $ref: "#/components/schemas/ApiError" }

  schemas:

    # ---------- Core / Common ----------
    ApiError:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
          example: E_RATE_LIMIT
        details:
          type: object
          additionalProperties: true

    # ---------- Certify ----------
    CertifyRequest:
      type: object
      required: [leaves]
      properties:
        leaves:
          type: array
          items:
            type: string
          description: List of tx_ids / record_ids (raw strings)
    CertifyResponse:
      type: object
      properties:
        message:
          type: string
          example: Proof successfully generated
        proof_id:
          type: string
          example: sbix-1a2b3c4d
          pattern: '^sbix-[a-z0-9]{6,}$'
        merkle_root:
          type: string
          example: "4f1a2c...e8"
          pattern: '^[0-9a-fA-F]{64}$'
        timestamp:
          type: string
          format: date-time
    ProofEntry:
      type: object
      properties:
        proof_id:
          type: string
          pattern: '^sbix-[a-z0-9]{6,}$'
        timestamp:
          type: string
          format: date-time
        leaves_count:
          type: integer
        merkle_root:
          type: string
          pattern: '^[0-9a-fA-F]{64}$'
        leaves:
          type: array
          items: { type: string }
        anchor_status:
          type: string
          enum: [pending, anchored, failed]
          description: Current anchoring status
    ProofList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ProofEntry"
        next_cursor:
          type: string
          nullable: true
          description: Opaque cursor for next page (null if none)

    InclusionStep:
      type: object
      properties:
        position:
          type: string
          enum: [left, right]
        hash:
          type: string
          pattern: '^[0-9a-fA-F]{64}$'
    InclusionProof:
      type: object
      properties:
        proof_id:
          type: string
        timestamp:
          type: string
          format: date-time
        leaf:
          type: string
        root:
          type: string
          pattern: '^[0-9a-fA-F]{64}$'
          description: Same value as 'merkle_root' in Certify responses
        proof:
          type: array
          items:
            $ref: "#/components/schemas/InclusionStep"

    AnchorLogEntry:
      type: object
      properties:
        batch_id:
          type: string
        timestamp:
          type: string
          format: date-time
        merkle_root:
          type: string
          pattern: '^[0-9a-fA-F]{64}$'
        tx_ids:
          type: array
          items: { type: string }
        anchor_tx:
          type: string
          description: Tezos tx hash (ghostnet / mainnet) or "0xSIM-…" in simulation
        aleph_post_hash:
          type: string
          nullable: true
          description: Aleph.im message hash (if posted)
        anchored:
          type: boolean
        source:
          type: string
          description: Origin of batch (e.g., certify, reconcile)
        proof_id:
          type: string
          description: Attached proof id if relevant

    # ---------- Verify ----------
    VerifyRequest:
      type: object
      required: [proof, target]
      properties:
        proof:
          type: object
          properties:
            proof_id:
              type: string
            timestamp:
              type: string
              format: date-time
            merkle_root:
              type: string
              pattern: '^[0-9a-fA-F]{64}$'
            leaves:
              type: array
              items: { type: string }
            proof_path:
              type: array
              items:
                $ref: "#/components/schemas/InclusionStep"
        target:
          type: string
          description: The leaf to verify inclusion for
    VerifyResponse:
      type: object
      properties:
        valid:
          type: boolean
        error:
          type: string

    # ---------- Compliance ----------
    RulesetPayload:
      type: object
      properties:
        mode:
          type: string
          enum: [validate, save]
        ruleset:
          type: string
          description: YAML content
    RulesetGetResponse:
      type: object
      properties:
        ruleset:
          type: string
        parsed:
          type: object
        rules_count:
          type: integer
        updated_at:
          type: string
          format: date-time
    TxObject:
      type: object
      additionalProperties: true
      description: Arbitrary transaction object used by the rules
    TestTxResponse:
      type: object
      properties:
        ok:
          type: boolean
        tx_id:
          type: string
        recon_result:
          type: string
          example: OK
        reasons:
          type: array
          items: { type: string }
    AlertsList:
      type: object
      properties:
        items:
          type: array
          items:
            type: object
            properties:
              tx_id: { type: string }
              reasons:
                type: array
                items: { type: string }
              timestamp: { type: string }
        total: { type: integer }
        limit: { type: integer }
        offset: { type: integer }

    # ---------- Reconcile ----------
    ReconcileTx:
      type: object
      required: [tx_id, timestamp, amount, status]
      properties:
        tx_id: { type: string }
        institution_id: { type: string }
        timestamp: { type: string, format: date-time }
        source_jurisdiction: { type: string, description: ISO 3166-1 alpha-2 }
        destination_jurisdiction: { type: string, description: ISO 3166-1 alpha-2 }
        asset_type: { type: string, example: "USDC" }
        amount: { type: string, example: "2500.00" }
        sender_id: { type: string }
        receiver_id: { type: string }
        status: { type: string, enum: [Confirmed, Pending, Rejected] }
        encryption_status: { type: string, enum: [Plain, Encrypted] }

paths:

  # =======================
  # Health
  # =======================
  /certify/health:
    get:
      tags: [Health]
      summary: Health — Certify
      operationId: health_certify
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  service: { type: string, example: sbix-certify }
                  status: { type: string, example: ok }
                  utc: { type: string, format: date-time }

  /verify/health:
    get:
      tags: [Health]
      summary: Health — Verify
      operationId: health_verify
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  service: { type: string, example: sbix-verify }
                  status: { type: string, example: ok }
                  utc: { type: string, format: date-time }

  /compliance/health:
    get:
      tags: [Health]
      summary: Health — ComplianceOS
      operationId: health_compliance
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  service: { type: string, example: sbix-compliance }
                  status: { type: string, example: ok }
                  utc: { type: string, format: date-time }

  # =======================
  # Certify
  # =======================
  /certify/:
    post:
      tags: [Certify]
      summary: Create a new Merkle proof from leaves
      operationId: certify_create
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CertifyRequest"
            example:
              leaves: ["TX-AB12", "TX-34CD", "TX-EEFF"]
      responses:
        "200":
          description: Proof created
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
            Access-Control-Expose-Headers: { $ref: "#/components/headers/Access-Control-Expose-Headers" }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CertifyResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /certify/proofs:
    get:
      tags: [Certify]
      summary: List proofs (cursor-based pagination)
      operationId: certify_list
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Proof page
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
            Access-Control-Expose-Headers: { $ref: "#/components/headers/Access-Control-Expose-Headers" }
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProofList"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /certify/proof/{proof_id}/path:
    get:
      tags: [Certify]
      summary: Compute inclusion path for a leaf (server-side)
      operationId: certify_path
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - in: path
          name: proof_id
          required: true
          schema: { type: string }
        - in: query
          name: target
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Inclusion path
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InclusionProof"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /certify/export/proof/{proof_id}.{fmt}:
    get:
      tags: [Export]
      summary: Export a proof (json/csv/xbrl.xml/zip)
      operationId: certify_export_proof
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - in: path
          name: proof_id
          required: true
          schema: { type: string }
        - in: path
          name: fmt
          required: true
          schema:
            type: string
            enum: ["json", "csv", "xbrl.xml", "zip"]
      responses:
        "200":
          description: File
          headers:
            Content-Disposition: { $ref: "#/components/headers/Content-Disposition" }
            Access-Control-Expose-Headers: { $ref: "#/components/headers/Access-Control-Expose-Headers" }
          content:
            application/json: {}
            text/csv: {}
            application/xbrl+xml: {}
            application/zip: {}
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /certify/export/inclusion/{proof_id}.{fmt}:
    get:
      tags: [Export]
      summary: Export inclusion proof for a leaf
      operationId: certify_export_inclusion
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - in: path
          name: proof_id
          required: true
          schema: { type: string }
        - in: path
          name: fmt
          required: true
          schema:
            type: string
            enum: ["json", "proof"]
        - in: query
          name: target
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Inclusion proof file
          headers:
            Content-Disposition: { $ref: "#/components/headers/Content-Disposition" }
            Access-Control-Expose-Headers: { $ref: "#/components/headers/Access-Control-Expose-Headers" }
          content:
            application/json: {}
            application/octet-stream: {}
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /certify/export/anchor_log.{fmt}:
    get:
      tags: [Export]
      summary: Export anchor log (json/jsonl/csv)
      operationId: certify_export_anchor_log
      security: [{ ApiKeyAuth: [] }]
      parameters:
        - in: path
          name: fmt
          required: true
          schema:
            type: string
            enum: ["json", "jsonl", "csv"]
      responses:
        "200":
          description: Anchor log
          headers:
            Content-Disposition: { $ref: "#/components/headers/Content-Disposition" }
            Access-Control-Expose-Headers: { $ref: "#/components/headers/Access-Control-Expose-Headers" }
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/AnchorLogEntry" }
            application/x-ndjson: {}
            text/csv: {}
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  # =======================
  # Verify
  # =======================
  /verify/:
    post:
      tags: [Verify]
      summary: Verify inclusion of a leaf in a Merkle root
      operationId: verify_inclusion
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/VerifyRequest" }
            examples:
              wrapped:
                value:
                  target: "TX-AB12"
                  proof:
                    proof_id: "sbix-1a2b3c4d"
                    merkle_root: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
                    leaves: ["TX-AB12", "TX-34CD"]
                    proof_path:
                      - { position: right, hash: "aa00..." }
              direct_proof:
                value:
                  target: "TX-AB12"
                  proof:
                    merkle_root: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
                    proof_path:
                      - { position: right, hash: "aa00..." }
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/VerifyResponse" }
              examples:
                valid:   { value: { valid: true } }
                invalid: { value: { valid: false, error: "path mismatch at step 2" } }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  # =======================
  # ComplianceOS
  # =======================
  /compliance/ruleset:
    get:
      tags: [Compliance]
      summary: Get current ruleset (raw YAML + parsed)
      operationId: compliance_ruleset_get
      responses:
        "200":
          description: Current ruleset
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RulesetGetResponse" }
    post:
      tags: [Compliance]
      summary: Validate or save ruleset
      operationId: compliance_ruleset_post
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/RulesetPayload" }
            examples:
              validate:
                value: { mode: "validate", ruleset: "version: '1.0'\nrules:\n  - field: amount\n    condition: greater_than\n    value: 10000\n    reason: Large amount" }
              save:
                value: { mode: "save", ruleset: "version: '1.0'\nrules:\n  - field: aml_risk\n    condition: greater_than\n    value: 0.8\n    reason: High AML score" }
      responses:
        "200":
          description: OK / Valid
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400": { $ref: "#/components/responses/BadRequest" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /compliance/test:
    post:
      tags: [Compliance]
      summary: Test a single transaction
      operationId: compliance_test
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tx:
                  $ref: "#/components/schemas/TxObject"
            example:
              tx:
                tx_id: "TX-DEMO-1"
                amount: 20000
                jurisdiction: "IR"
                aml_risk: 0.9
                tx_type: "DEX"
      responses:
        "200":
          description: Verdict
          content:
            application/json:
              schema: { $ref: "#/components/schemas/TestTxResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /compliance/replay:
    post:
      tags: [Compliance]
      summary: Replay ledger.json to produce alerts.json
      operationId: compliance_replay
      responses:
        "200":
          description: Replayed
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  alerts: { type: integer }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /compliance/alerts:
    get:
      tags: [Compliance]
      summary: List alerts
      operationId: compliance_alerts
      parameters:
        - in: query
          name: limit
          schema: { type: integer, default: 200, minimum: 0 }
        - in: query
          name: offset
          schema: { type: integer, default: 0, minimum: 0 }
      responses:
        "200":
          description: Alerts
          content:
            application/json:
              schema: { $ref: "#/components/schemas/AlertsList" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /compliance/batch:
    post:
      tags: [Compliance]
      summary: Batch test transactions (JSONL or JSON array)
      operationId: compliance_batch
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "200":
          description: Batch results
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        tx_id: { type: string }
                        verdict: { type: string, enum: [OK, FLAGGED] }
                        reasons:
                          type: array
                          items: { type: string }
                  count: { type: integer }
                  skipped: { type: integer }
        "413": { $ref: "#/components/responses/PayloadTooLarge" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  # =======================
  # Reconcile
  # =======================
  /reconcile:
    get:
      tags: [Reconcile]
      summary: Health / schema hint for reconcile endpoint
      operationId: reconcile_get
      responses:
        "200":
          description: Schema hint
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean, example: true }
                  schema:
                    $ref: "#/components/schemas/ReconcileTx"
    post:
      tags: [Reconcile]
      summary: Ingest a transaction (schema-enforced)
      operationId: reconcile_post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReconcileTx"
            example:
              tx_id: "TX-DEMO"
              institution_id: "BANK-AE-01"
              timestamp: "2025-06-03T12:34:56Z"
              source_jurisdiction: "FR"
              destination_jurisdiction: "DE"
              asset_type: "XRP"
              amount: "2500.00"
              sender_id: "u1"
              receiver_id: "u2"
              status: "Confirmed"
              encryption_status: "Plain"
      responses:
        "200":
          description: Accepted
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/X-RateLimit-Reset" }
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  id: { type: string, example: "ing-01HXYZ..." }
        "400": { $ref: "#/components/responses/BadRequest" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
