openapi: 3.1.0
info:
  title: Finvis Public API
  version: 2026-01-10
  description: |
    Finvis Public API for treasury, payments, and reporting.
    OAuth2 client credentials only. Tenant is derived from the token.
    For all protected /public endpoints, clients must send:
    - Authorization: Bearer <access_token>
    - X-Tenant-ID: <tenant_uuid>
    The X-Tenant-ID value must exactly match the tenant bound to the token.
servers:
  - url: https://api.finvis.se/public
    description: Production
  - url: https://sandbox.api.finvis.se/public
    description: Sandbox
  - url: http://localhost:8000/public
    description: Local
security:
  - OAuth2: []

paths:
  /auth/tenant-token:
    post:
      summary: Issue tenant-scoped token from shared partner client
      description: |
        Uses OAuth2 client credentials + tenant_id to issue a tenant-scoped access token.
        No refresh token is returned; request a new token on expiry.
      parameters:
        - name: Authorization
          in: header
          required: false
          schema:
            type: string
          description: HTTP Basic base64(client_id:client_secret)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tenant_id]
              properties:
                grant_type:
                  type: string
                  default: client_credentials
                tenant_id:
                  type: string
                  format: uuid
                scope:
                  type: string
                  description: Optional space-separated subset of allowed scopes
      responses:
        "200":
          description: Access token issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                  token_type:
                    type: string
                    example: Bearer
                  expires_in:
                    type: integer
                    example: 3600
                  scope:
                    type: string
                  tenant_id:
                    type: string
                    format: uuid
        "401":
          description: Invalid client credentials
        "403":
          description: Tenant not allowed for this partner client

  /accounts:
    get:
      summary: List accounts
      security:
        - OAuth2: [read:ledger]
      responses:
        "200":
          description: Accounts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Account"
  /accounts/{id}:
    get:
      summary: Retrieve account
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"
  /accounts/{id}/transactions:
    get:
      summary: List account transactions
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - $ref: "#/components/parameters/Limit"
        - name: direction
          in: query
          schema:
            type: string
            enum: [credit, debit]
      responses:
        "200":
          description: Transactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Transaction"
  /accounts/{id}/balances:
    get:
      summary: List account balances
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - name: balance_type
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Balance snapshots
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BalanceSnapshot"
  /accounts/{id}/coverage:
    get:
      summary: List statement coverage days
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200":
          description: Coverage days
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CoverageDay"
  /accounts/{id}/intraday:
    get:
      summary: List intraday balance points
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - name: date
          in: query
          schema:
            type: string
            format: date
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - $ref: "#/components/parameters/Limit"
        - name: balance_type
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Intraday points
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/IntradayBalancePoint"
  /transactions:
    get:
      summary: List transactions
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Transactions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Transaction"
  /transactions/{id}:
    get:
      summary: Retrieve transaction
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Transaction
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transaction"

  /payments/batches:
    get:
      summary: List payment batches
      security:
        - OAuth2: [read:payments]
      responses:
        "200":
          description: Payment batches
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PaymentBatch"
    post:
      summary: Create payment batch
      security:
        - OAuth2: [write:payments]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentBatchCreate"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentBatch"
  /payments/batches/{id}:
    get:
      summary: Retrieve payment batch
      security:
        - OAuth2: [read:payments]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Payment batch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentBatch"
  /payments/batches/{id}/submit:
    post:
      summary: Submit payment batch
      security:
        - OAuth2: [write:payments]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          description: Submission accepted
  /payments/instructions/{id}:
    get:
      summary: Retrieve payment instruction
      security:
        - OAuth2: [read:payments]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Payment instruction
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentInstruction"
  /payments/status-events:
    get:
      summary: List payment status events
      security:
        - OAuth2: [read:payments]
      responses:
        "200":
          description: Status events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PaymentStatusEvent"

  /approvals/pending:
    get:
      summary: List pending approvals
      security:
        - OAuth2: [approve:payments]
      responses:
        "200":
          description: Approval requests
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ApprovalRequest"
  /approvals/{id}/approve:
    post:
      summary: Approve a request
      security:
        - OAuth2: [approve:payments]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Approved
  /approvals/{id}/reject:
    post:
      summary: Reject a request
      security:
        - OAuth2: [approve:payments]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Rejected

  /reconciliation/matches:
    get:
      summary: List reconciliation matches
      security:
        - OAuth2: [read:reconciliation]
      responses:
        "200":
          description: Matches
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ReconciliationMatch"
  /reconciliation/matches/{id}/confirm:
    post:
      summary: Confirm match
      security:
        - OAuth2: [write:reconciliation]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Confirmed
  /reconciliation/matches/{id}/reject:
    post:
      summary: Reject match
      security:
        - OAuth2: [write:reconciliation]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Rejected

  /reporting/cashflow/daily:
    get:
      summary: Daily cashflow
      security:
        - OAuth2: [read:reporting]
      parameters:
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200":
          description: Cashflow days
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CashflowDay"
  /reporting/positions/daily:
    get:
      summary: Daily cash positions
      security:
        - OAuth2: [read:reporting]
      parameters:
        - $ref: "#/components/parameters/FromDate"
        - $ref: "#/components/parameters/ToDate"
      responses:
        "200":
          description: Position days
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PositionDay"
  /reporting/forecast:
    get:
      summary: Forecast
      security:
        - OAuth2: [read:forecast]
      responses:
        "200":
          description: Forecast entries
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ForecastEntry"

  /companies:
    get:
      summary: List companies
      security:
        - OAuth2: [read:ledger]
      responses:
        "200":
          description: Companies
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Company"
  /companies/{id}:
    get:
      summary: Retrieve company
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Company
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Company"
  /koncerner:
    get:
      summary: List koncerner
      security:
        - OAuth2: [read:ledger]
      responses:
        "200":
          description: Koncerner
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Koncern"
  /koncerner/{id}:
    get:
      summary: Retrieve koncern
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Koncern
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Koncern"
  /koncerner/{id}/breakdown:
    get:
      summary: Koncern breakdown
      security:
        - OAuth2: [read:ledger]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Breakdown
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /webhooks:
    get:
      summary: List webhook endpoints
      security:
        - OAuth2: [admin:public_api]
      responses:
        "200":
          description: Webhook endpoints
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookEndpoint"
    post:
      summary: Create webhook endpoint
      security:
        - OAuth2: [admin:public_api]
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpoint"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
  /webhooks/{id}:
    get:
      summary: Retrieve webhook endpoint
      security:
        - OAuth2: [admin:public_api]
      parameters:
        - $ref: "#/components/parameters/IdPath"
      responses:
        "200":
          description: Webhook endpoint
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
    patch:
      summary: Update webhook endpoint
      security:
        - OAuth2: [admin:public_api]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEndpoint"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEndpoint"
    delete:
      summary: Delete webhook endpoint
      security:
        - OAuth2: [admin:public_api]
      parameters:
        - $ref: "#/components/parameters/IdPath"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "204":
          description: Deleted

components:
  parameters:
    IdPath:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    FromDate:
      name: from_date
      in: query
      schema:
        type: string
        format: date
    ToDate:
      name: to_date
      in: query
      schema:
        type: string
        format: date
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        default: 100
        maximum: 1000
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
    TenantIdHeader:
      name: X-Tenant-ID
      in: header
      required: true
      schema:
        type: string
        format: uuid
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: /public/auth/token/
          scopes:
            read:ledger: Read accounts, balances, coverage, transactions
            read:reporting: Read cashflow and positions
            read:forecast: Read forecast data
            read:payments: Read payments and statuses
            write:payments: Create and submit payments
            approve:payments: Approve or reject payments
            read:reconciliation: Read reconciliation matches
            write:reconciliation: Confirm or reject matches
            admin:public_api: Manage webhook endpoints
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: object
          additionalProperties: true
    Account:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        currency:
          type: string
        iban_masked:
          type: string
        bank_name:
          type: string
    Transaction:
      type: object
      properties:
        id:
          type: string
          format: uuid
        account_id:
          type: string
          format: uuid
        amount:
          type: string
        currency:
          type: string
        direction:
          type: string
        booked_at:
          type: string
          format: date-time
        value_at:
          type: string
          format: date-time
        counterparty_name:
          type: string
        remittance:
          type: string
    BalanceSnapshot:
      type: object
      properties:
        id:
          type: string
          format: uuid
        account_id:
          type: string
          format: uuid
        balance_type:
          type: string
        as_of_date:
          type: string
          format: date
        balance:
          type: string
        currency:
          type: string
    CoverageDay:
      type: object
      properties:
        id:
          type: string
          format: uuid
        account_id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        source:
          type: string
        completeness:
          type: string
    IntradayBalancePoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        account_id:
          type: string
          format: uuid
        timestamp:
          type: string
          format: date-time
        balance:
          type: string
        currency:
          type: string
        balance_type:
          type: string
    PaymentBatchCreate:
      type: object
      properties:
        company_id:
          type: string
          format: uuid
        currency:
          type: string
        requested_execution_date:
          type: string
          format: date
        instructions:
          type: array
          items:
            $ref: "#/components/schemas/PaymentInstruction"
      required: [company_id, currency, instructions]
    PaymentBatch:
      type: object
      properties:
        id:
          type: string
          format: uuid
        company_id:
          type: string
          format: uuid
        currency:
          type: string
        status:
          type: string
        total_amount:
          type: string
        created_at:
          type: string
          format: date-time
    PaymentInstruction:
      type: object
      properties:
        id:
          type: string
          format: uuid
        amount:
          type: string
        currency:
          type: string
        creditor_name:
          type: string
        creditor_iban:
          type: string
        end_to_end_id:
          type: string
    PaymentStatusEvent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        instruction_id:
          type: string
          format: uuid
        status:
          type: string
        occurred_at:
          type: string
          format: date-time
    ApprovalRequest:
      type: object
      properties:
        id:
          type: string
          format: uuid
        object_id:
          type: string
          format: uuid
        state:
          type: string
        required_count:
          type: integer
    ReconciliationMatch:
      type: object
      properties:
        id:
          type: string
          format: uuid
        payment_instruction_id:
          type: string
          format: uuid
        transaction_id:
          type: string
          format: uuid
        state:
          type: string
        score:
          type: integer
    Company:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        orgnr:
          type: string
        country:
          type: string
    Koncern:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
        active:
          type: boolean
        events:
          type: array
          items:
            type: string
    CashflowDay:
      type: object
      properties:
        date:
          type: string
          format: date
        currency:
          type: string
        inflow:
          type: string
        outflow:
          type: string
        net:
          type: string
    PositionDay:
      type: object
      properties:
        date:
          type: string
          format: date
        currency:
          type: string
        balance:
          type: string
    ForecastEntry:
      type: object
      properties:
        date:
          type: string
          format: date
        currency:
          type: string
        amount:
          type: string
