openapi: 3.1.0
info:
  title: Tsavra API
  version: 0.1.0
  description: |
    Agent-facing HTTP API for Tsavra payment instructions (EPC/ZBP QR).
    Prefer `mix reconcile pay-link` on the same host when shell access is available.

servers:
  - url: https://tsavra.ilyazub.com
  - url: https://pay.65-21-140-113.sslip.io
  - url: http://127.0.0.1:8000

paths:
  /snippets:
    post:
      summary: Create a Tsavra instruction via form/JSON (operator bearer required)
      description: |
        CSRF-free programmatic entry point. Accepts `text` (JSON body or form-encoded).
        On success redirects (303) to `/pay/{token}`. Use `POST /api/snippets` for a
        JSON response instead of a redirect.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text:
                  type: string
                  description: Plaintext bill or payment details
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                text:
                  type: string
      responses:
        "303":
          description: Instruction created — redirect to /pay/{token}
          headers:
            Location:
              schema:
                type: string
                example: /pay/abc123
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"

  /pay/{token}/qr.svg:
    get:
      summary: EPC/ZBP QR code as SVG (public)
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: QR code image
          content:
            image/svg+xml:
              schema:
                type: string
                format: binary
        "404":
          $ref: "#/components/responses/NotFound"

  /pay/{token}/qr.png:
    get:
      summary: EPC/ZBP QR code as PNG (public)
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: QR code image
          content:
            image/png:
              schema:
                type: string
                format: binary
        "404":
          $ref: "#/components/responses/NotFound"

  /api/health:
    get:
      summary: Health check
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok

  /api/openapi.yaml:
    get:
      summary: This OpenAPI document
      responses:
        "200":
          description: YAML spec

  /api/snippets:
    post:
      summary: Create a Tsavra instruction (operator bearer required)
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePayLinkRequest"
          multipart/form-data:
            schema:
              type: object
              properties:
                text:
                  type: string
                file:
                  type: string
                  format: binary
                payee:
                  type: string
                allow_tracking_only:
                  type: boolean
                pis:
                  type: boolean
                  description: Request a TrueLayer payment-link URL in the response.
      responses:
        "200":
          description: Tsavra instruction created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PayLinkResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/status/{token}:
    get:
      summary: Public Tsavra status
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusResponse"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/pay-links/{token}/mark-paid:
    post:
      summary: Mark Tsavra instruction paid (operator bearer required)
      security:
        - bearerAuth: []
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Marked paid
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: paid
                  status_label:
                    type: string
                    example: Paid
                  payment_key:
                    type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/pay-links/{token}/confirm-crypto:
    post:
      summary: Confirm a pending-crypto payment by Tron transaction link or hash (public)
      description: |
        For payments with `status: pending_crypto`. Supply either `tx_link`
        (a Tronscan URL) or `tx_hash` (a 64-character hex transaction hash).
        Idempotent by transaction hash.
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tx_link:
                  type: string
                  description: Tronscan transaction URL
                  example: https://tronscan.org/#/transaction/abc123...
                tx_hash:
                  type: string
                  description: 64-character hex transaction hash
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                tx_link:
                  type: string
                tx_hash:
                  type: string
      responses:
        "200":
          description: Crypto payment confirmed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: paid
                  status_label:
                    type: string
                    example: Paid
                  payment_key:
                    type: string
                  tx_hash:
                    type: string
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /api/ledger:
    get:
      summary: Query ledger entries (bearer required)
      description: Returns an array of projected ledger entries. Auth via API_TOKEN or OPERATOR_TOKEN.
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            maximum: 1000
            default: 100
          description: Maximum number of entries to return (capped at 1000)
        - name: match_status
          in: query
          schema:
            type: string
          description: Filter by match_status value (e.g. MATCHED, UNMATCHED)
        - name: category
          in: query
          schema:
            type: string
          description: Filter by category
      responses:
        "200":
          description: Array of ledger entries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/LedgerEntry"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /api/analytics/summary:
    get:
      summary: Analytics summary (bearer required)
      description: Aggregated P&L summary over a rolling window. Auth via API_TOKEN or OPERATOR_TOKEN.
      security:
        - bearerAuth: []
      parameters:
        - name: days
          in: query
          schema:
            type: integer
            default: 730
          description: Lookback window in days (default 730)
        - name: group_by
          in: query
          schema:
            type: string
            default: class
          description: Grouping dimension (e.g. class, source, category)
        - name: source
          in: query
          schema:
            type: string
          description: Filter to a single source (e.g. wise, revolut)
      responses:
        "200":
          description: Analytics summary rows
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: OPERATOR_TOKEN or API_TOKEN

  schemas:
    CreatePayLinkRequest:
      type: object
      properties:
        text:
          type: string
          description: Plaintext bill or payment details
        payee:
          type: string
          description: Payee book alias (without @)
        allow_tracking_only:
          type: boolean
          description: Create tracking page when IBAN/phone missing

    PayLinkResponse:
      type: object
      required:
        - payment_key
        - token
        - pay_url
        - status_url
        - mark_paid_url
        - qr_svg_url
        - qr_png_url
        - duplicate
        - extracted
      properties:
        payment_key:
          type: string
        token:
          type: string
        pay_url:
          type: string
        status_url:
          type: string
        mark_paid_url:
          type: string
        qr_svg_url:
          type: string
        qr_png_url:
          type: string
        pis_url:
          type: string
          nullable: true
          description: TrueLayer payment-link URL when `pis=true` was requested and link creation succeeded.
        duplicate:
          type: boolean
        extracted:
          $ref: "#/components/schemas/ExtractedPayment"

    ExtractedPayment:
      type: object
      properties:
        iban:
          type: string
        phone:
          type: string
        address:
          type: string
        token:
          type: string
        chain:
          type: string
        amount:
          type: string
          description: Decimal amount as a string (e.g. "1250.50") to avoid floating-point precision issues.
        currency:
          type: string
        counterparty:
          type: string
        reference:
          type: string
        reference_policy:
          type: string
        bic:
          type: string

    StatusResponse:
      type: object
      properties:
        status:
          type: string
          enum: [pending, paid, excluded, pending_crypto, underpaid, overpaid]
        status_label:
          type: string
        amount_expected:
          type: string
          description: Decimal amount as a string.
        amount_received:
          type: string
          description: Decimal amount as a string.
        currency_mismatch:
          type: boolean
        payment_key:
          type: string

    LedgerEntry:
      type: object
      properties:
        event_id:
          type: integer
        date:
          type: string
          format: date
        amount:
          type: string
          description: Decimal amount as a string.
        currency:
          type: string
        direction:
          type: string
        source:
          type: string
        counterparty:
          type: string
        reference:
          type: string
        external_id:
          type: string
        amount_eur:
          type: string
          description: Decimal amount as a string.
        fx_rate:
          type: string
          description: Decimal FX rate as a string.
        category:
          type: string
        match_status:
          type: string
        txn_class:
          type: string

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        code:
          type: string

  responses:
    Unauthorized:
      description: Missing or invalid bearer token
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Unknown token
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ValidationError:
      description: Could not parse payment details
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
