# BankConnector OpenAPI Specification
# Auto-generated — do not edit by hand.
# Regenerate with: npx tsx scripts/generate-openapi.ts
openapi: 3.1.0
info:
  title: BankConnector API
  version: 0.1.0
  description: |
      ## Quick start
      
      BankConnector converts canonical JSON payments to bank-specific ISO 20022 files and
      normalises incoming bank statements into a single format. You host it; you own the data.
      
      ### Step 1: Get your API key
      
      Generate a key in the Workspace admin **API Keys** section, or via the API:
      
      ```http
      POST /platforms/{platformId}/api-keys
      { "label": "My ERP integration" }
      ```
      
      The raw key is shown once in the 201 response: store it securely. Pass it as the
      `X-API-Key` header on every subsequent request.
      
      ### Step 2: Create a company
      
      Each legal entity that makes payments is a "company". This call requires a **session**
      (workspace-admin login), not an API key:
      
      ```http
      POST /platforms/{platformId}/companies
      Authorization: session cookie (workspace admin)
      { "name": "Acme Payments Ltd" }
      → 201 { "company": { "id": "comp_…", "name": "Acme Payments Ltd" } }
      ```
      
      ### Step 3: Add users
      
      Create users in your company via the API or the web app. Each user must have at least one
      role: `admin`, `approver`, and/or `viewer`. Users who will approve payments need the
      `approver` role.
      
      ```http
      POST /users
      { "platformId": "…", "companyId": "…", "email": "approver@acme.com", "name": "Alice", "roles": ["approver"] }
      ```
      
      - **With `password`:** user is created immediately (active).
      - **Without `password`:** an invite email is sent. The user clicks the link and sets their
        own password via `POST /auth/set-password`.
      
      At least one approver must exist before a connection can go live.
      
      ### Step 4: Connect to a bank
      
      > **Platform-admin note:** If you authenticated as a platform admin (step 1–2 above) and
      > now want to make company-scoped calls (connections, payments, etc.), you must first select
      > a company context. Without it you will receive: *"select a company context first."*
      > ```http
      > POST /auth/company-context
      > { "companyId": "comp_…" }
      > ```
      > After this call your session acts as an admin of that company. Use
      > `DELETE /auth/company-context` to return to workspace-admin scope.
      
      ```http
      GET /profiles             → list all supported banks + payment types
      POST /connections         → set up an EBICS / SFTP / web-service channel
      ```
      
      **Approvers and approval policy** are separate concepts. The policy (under
      `POST /approval-policies`) defines the rule: how many approvers are required and any
      amount thresholds. Approvers are assigned per-connection via
      `POST /connections/{id}/approvers` using user IDs. Both must be configured; creating a
      policy alone is not sufficient for a connection to reach go-live.
      
      ### Step 5: Submit a payment
      
      ```http
      POST /journal/payments
      {
        "platformId": "plat_…", "companyId": "comp_…", "bankKey": "nordea-dk",
        "payment": {
          "messageId": "MSG-20260619-0001",
          "initiatingParty": { "name": "Acme Corp", "organisationId": { "id": "ACME-1", "scheme": "CUST" } },
          "payments": [{
            "paymentId": "PMT-1", "paymentType": "sepa", "executionDate": "2026-06-20",
            "debtor": { "name": "Acme Corp", "country": "DK", "account": { "iban": "DK5000400440116243", "currency": "EUR" }, "agent": { "bic": "NDEADKKK" } },
            "transactions": [{ "endToEndId": "E2E-1", "amount": "1000.00", "currency": "EUR",
              "creditor": { "name": "Supplier GmbH", "account": { "iban": "DE89370400440532013000" }, "agent": { "bic": "DEUTDEFF" } } }]
          }]
        }
      }
      ```
      
      The `payment` object IS the canonical instruction (amounts are exact decimal **strings**); on this route it
      is WRAPPED under `payment` alongside the tenant scope + `bankKey`. Pass an optional `"environment": "test"`
      to dispatch over a test connection (default `production`). NOTE the envelope difference: `POST /validate/{bank}`
      and `POST /convert/{bank}` take that SAME canonical object as the raw top-level body (no `payment` wrapper, no
      platformId/companyId). See CANONICAL_INPUT.md for the full payment shape.
      
      ### Step 6: Read incoming data
      
      ```http
      POST /inbound/payment-status   → import a pain.002 status report
      POST /inbound/statement        → import a camt.053 / 052 / 054 statement
      GET  /journal/reconciliation   → matched payments and bank confirmations
      ```
      
      ---
      
      > **Sandbox vs production:** BankConnector has no separate sandbox host. Use
      > `environment: "TEST"` on a connection to point it at your bank's test environment.
      > `environment: "PRODUCTION"` sends real payments. The same API key works for both.
  contact:
    email: johan@laymanadvisory.com
servers:
  - 
    url: 'https://{host}'
    description: Your BankConnector instance
    variables:
      host:
        default: your-bankconnector.example.com
        description: Hostname of your deployed BankConnector instance
tags:
  - 
    name: Discovery
    description: Explore what banks and payment types are available
  - 
    name: Conversion
    description: Validate and convert canonical payment JSON
  - 
    name: Connections
    description: 'Bank connection setup, channel activation, and EBICS onboarding (session-authenticated, Admin only)'
  - 
    name: Journal
    description: 'Submit payments + read the journal: the ERP integration surface (X-API-Key)'
  - 
    name: Approvals
    description: 'Maker-checker approval policies + the pending-approval queue (session, Admin/Approver)'
  - 
    name: Accounts
    description: Bank accounts registry + imported statements (session)
  - 
    name: Webhooks
    description: 'Outbound event subscriptions (session, Admin)'
  - 
    name: Auth
    description: 'Login, session, and 2FA. Login + demo are PUBLIC; the rest need a session.'
  - 
    name: User Management
    description: 'Create, invite, and manage users within a company. Also covers workspace (platform) admin accounts.'
  - 
    name: Platform
    description: Tenant dashboard + per-bank test payments
  - 
    name: System
    description: Health and version
x-tagGroups:
  - 
    name: Quick Start
    tags:
      - Auth
      - User Management
      - Platform
      - System
  - 
    name: Banks
    tags:
      - Discovery
      - Connections
  - 
    name: Payments
    tags:
      - Conversion
      - Approvals
  - 
    name: Incoming Data
    tags:
      - Journal
      - Accounts
  - 
    name: Configuration
    tags:
      - Webhooks
paths:
  /:
    get:
      tags:
        - System
      summary: Web UI
      description: The localhost single-page web interface for manual testing.
      responses:
        '200':
          description: HTML page
          content:
            text/html: {}
  /health:
    get:
      tags:
        - System
      summary: Liveness check
      responses:
        '200':
          description: Server is running
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
  /version:
    get:
      tags:
        - System
      summary: API version
      responses:
        '200':
          description: Current version
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: string
                    example: 0.1.0
  /profiles:
    get:
      tags:
        - Discovery
      summary: List all banks with their offered payment types
      description: 'Returns every registered bank, its ISO 20022 version, and the payment types available for its country (with full metadata).'
      responses:
        '200':
          description: List of bank profiles
          content:
            application/json:
              schema:
                type: object
                properties:
                  profiles:
                    type: array
                    items:
                      $ref: '#/components/schemas/BankProfile'
      x-sandbox: true
      x-codeSamples:
        - 
          lang: Shell
          label: cURL
          source: |
              curl -X GET https://your-host/profiles \
                -H 'X-API-Key: YOUR_KEY'
        - 
          lang: JavaScript
          label: Fetch
          source: |
              const res = await fetch("https://your-host/profiles", {
                method: "GET",
                headers: {
                  "X-API-Key": "YOUR_KEY",
                },
              });
              const data = await res.json();
        - 
          lang: Python
          label: Python
          source: |
              import requests
              
              resp = requests.request(
                "GET", "https://your-host/profiles",
                headers={"X-API-Key": "YOUR_KEY"},
              )
              resp.raise_for_status()
              data = resp.json()
  /payment-types:
    get:
      tags:
        - Discovery
      summary: Full payment-type catalog
      description: 'All payment types in the shared catalog with ISO 20022 encoding, descriptions, and requirements.'
      responses:
        '200':
          description: Full catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  paymentTypes:
                    type: array
                    items:
                      $ref: '#/components/schemas/PaymentTypeEntry'
      x-sandbox: true
  /dashboard:
    get:
      tags:
        - Platform
      summary: 'Home dashboard: setup checklist + go-live readiness score'
      description: 'Returns the per-company setup checklist (users / approver / approval policy / bank connection / imported statement / test payment), per-bank connection progress, the banks with direct integration configured, a go-live readiness score (0–100) for the most-advanced single bank, and headline counts. Requires platformId + companyId query params.'
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Dashboard snapshot
          content:
            application/json:
              schema:
                type: object
  /connectivity/profiles:
    get:
      tags:
        - Connections
      summary: List connectivity profiles (channel info per bank)
      description: 'Read-only metadata: which channel each bank uses (sftp / danske-ws / nordea-ca / bankconnect / ebics), its pre-known server/endpoint details, and SFTP wizard config. No auth required.'
      responses:
        '200':
          description: Connectivity profiles
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
      x-sandbox: true
  /admin/test-email:
    post:
      tags:
        - Workspace Admin
      summary: Send a test email to verify the configured transport
      description: 'Admin only (a platform/workspace admin, or a company admin). Sends a fixed test message to the requesting admin''s own email address so an operator can confirm the email transport (SMTP) is wired and delivering. Returns 503 when no transport is configured (dev fail-closed) or the SMTP send fails.'
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: Test email sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  sent:
                    type: boolean
                  to:
                    type: string
                    format: email
        '403':
          description: Not an admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Email transport not configured or send failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/platforms/{platformId}/companies':
    get:
      tags:
        - Workspace Admin
      summary: List the companies under a platform
      description: 'Workspace (platform) admin only. The company list a workspace admin manages: drives the GUI company list + company-switcher (POST /auth/company-context).'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Companies
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        createdAt:
                          type: string
                          format: date-time
                  nextCursor:
                    type: string
                    nullable: true
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Workspace Admin
      summary: Create a company under a platform
      description: 'Workspace (platform) admin only. Creates a new company (tenant) under the platform. Requires a **session** (workspace-admin login): not an API key. After creation, use `POST /auth/company-context` to enter the company and make company-scoped calls.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  maxLength: 200
                  example: Acme Payments Ltd
                  description: Display name for the company.
      responses:
        '201':
          description: Company created
          content:
            application/json:
              schema:
                type: object
                properties:
                  company:
                    type: object
                    properties:
                      id:
                        type: string
                        example: comp_…
                      name:
                        type: string
                      createdAt:
                        type: string
                        format: date-time
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/company-context:
    post:
      tags:
        - Workspace Admin
      summary: Enter a company context (platform admin acts AS a company)
      description: Workspace (platform) admin only. Sets the session's active company so the admin acts as an ADMIN of that company. The company must belong to the admin's platform. Cleared with DELETE.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - companyId
              properties:
                companyId:
                  type: string
      responses:
        '200':
          description: Context set
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown company in your platform
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Workspace Admin
      summary: Exit the company context (back to pure platform-admin scope)
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: Context cleared
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/platforms/{platformId}/api-keys':
    get:
      tags:
        - Workspace Admin
      summary: 'List a platform''s API keys (metadata only: never the secret)'
      description: 'Workspace (platform) admin only. Returns active (non-revoked) keys with id, label, createdAt and lastUsedAt. The raw key is NEVER returned here: it is shown once, at creation.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: API keys (no secret)
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        label:
                          type: string
                        createdAt:
                          type: string
                          format: date-time
                        lastUsedAt:
                          type: string
                          format: date-time
                  nextCursor:
                    type: string
                    nullable: true
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Workspace Admin
      summary: 'Create an API key: the raw key is returned ONCE'
      description: Workspace (platform) admin only. Mints a new platform API key; the raw `key` value appears ONLY in this 201 response and is never stored or retrievable again. Store it securely.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - label
              properties:
                label:
                  type: string
                  description: 'Human name, e.g. "Production ERP".'
      responses:
        '201':
          description: 'Key created: raw key shown once'
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  key:
                    type: string
                    description: The raw API key. Shown ONCE. Never returned again.
                  label:
                    type: string
                  createdAt:
                    type: string
                    format: date-time
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/platforms/{platformId}/api-keys/{keyId}':
    delete:
      tags:
        - Workspace Admin
      summary: Revoke an API key
      description: Workspace (platform) admin only. Soft-revokes the key (it stays for audit but stops authenticating immediately).
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
        - 
          name: keyId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Revoked
          content:
            application/json:
              schema:
                type: object
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown key for this platform
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/platforms/{platformId}/admins':
    get:
      tags:
        - User Management
      summary: List workspace admins for a platform
      description: Returns all workspace (platform) admin accounts. Workspace-admin session required.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Workspace admins
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        email:
                          type: string
                        name:
                          type: string
                  nextCursor:
                    type: string
                    nullable: true
        '403':
          description: Not a workspace admin
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - User Management
      summary: Create a workspace admin
      description: 'Creates a new workspace (platform) admin account with a password. **Bootstrap note:** when the server is running in open/unauthenticated mode (no `BANKCONNECTOR_API_KEYS` set, e.g. first-time setup), this endpoint is accessible without a session: use it to create the very first admin. Once auth is enabled, an existing workspace-admin session is required.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - name
                - password
              properties:
                email:
                  type: string
                  format: email
                  example: admin@acme.com
                name:
                  type: string
                  example: Alice Admin
                password:
                  type: string
                  description: Initial password. Minimum 8 characters.
      responses:
        '201':
          description: Admin created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  email:
                    type: string
                  name:
                    type: string
        '400':
          description: Invalid request or duplicate email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Not a workspace admin (when auth is enabled)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /users:
    get:
      tags:
        - User Management
      summary: List users in a company
      description: 'Returns all users in the company (name, email, roles, status). Readable by any authenticated company member.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Company users
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        email:
                          type: string
                        name:
                          type: string
                        roles:
                          type: array
                          items:
                            type: string
                            enum:
                              - admin
                              - approver
                              - viewer
                        status:
                          type: string
                          enum:
                            - active
                            - invited
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - User Management
      summary: Create or invite a company user
      description: |
          **Company admin** only. Two modes:
          
          - **With `password`:** creates the user immediately (active). Use for programmatic provisioning.
          - **Without `password`:** sends an invite email. The user receives a link to `/auth/set-password` where they set their own password. In dev mode the `inviteLink` is also returned in the response body.
          
          The user must be given at least one role (`admin`, `approver`, and/or `viewer`). Users who will be assigned as payment approvers need the `approver` role.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - email
                - name
                - roles
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                email:
                  type: string
                  format: email
                  example: approver@acme.com
                name:
                  type: string
                  example: Bob Approver
                roles:
                  type: array
                  items:
                    type: string
                    enum:
                      - admin
                      - approver
                      - viewer
                  minItems: 1
                  example:
                    - approver
                password:
                  type: string
                  description: 'If omitted, an invite email is sent and the user sets their own password.'
      responses:
        '201':
          description: User created or invite sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  email:
                    type: string
                  name:
                    type: string
                  roles:
                    type: array
                    items:
                      type: string
                  invited:
                    type: boolean
                    description: true when an invite email was sent (no password provided)
                  inviteLink:
                    type: string
                    description: 'Dev mode only: the invite URL (not returned in production)'
        '400':
          description: 'Invalid request, duplicate email, or no valid roles'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Company admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/set-password:
    post:
      tags:
        - User Management
      summary: 'Complete an invite: set password from email token (PUBLIC)'
      description: PUBLIC. Called when an invited user clicks their invite link. Validates the one-time token and sets the user's password. Any existing sessions for the user are revoked. The token comes from the invite email (or `inviteLink` in dev mode).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - token
                - password
              properties:
                token:
                  type: string
                  description: The one-time invite token from the email link.
                password:
                  type: string
                  description: The user's chosen password.
      responses:
        '200':
          description: Password set
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  email:
                    type: string
        '400':
          description: Invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: 'Too many attempts: IP rate-limited'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /connections:
    get:
      tags:
        - Connections
      summary: List bank connections for a company
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Connection list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/BankConnection'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Connections
      summary: 'Create (or find existing) bank connection: starts the setup wizard'
      description: 'Creates a bank connection for the given company, or returns the existing one if already set up. **Platform-admin note:** if you are authenticated as a workspace (platform) admin, you must enter a company context first via `POST /auth/company-context` before calling this endpoint. Without it you will receive: *"select a company context first."*'
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - bankKey
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                bankKey:
                  $ref: '#/components/schemas/BankKey'
                environment:
                  type: string
                  enum:
                    - test
                    - production
      responses:
        '201':
          description: Connection created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}':
    get:
      tags:
        - Connections
      summary: Get a single connection + readiness
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Connection + readiness
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/signing-cert':
    put:
      tags:
        - Connections
      summary: Pin the bank's published signing certificate
      description: 'Stores the bank''s PUBLISHED X.509 signing certificate (PEM or base64-DER) on the connection so inbound Web Services signatures are verified against it (fail-closed on any mismatch). Admin only. Validates the cert parses (400 on bad input). Returns the cert subject + expiry so the operator can confirm they pinned the right cert; the raw PEM is never returned. GET /connections/{id} then surfaces signingCertSubject / signingCertExpiry.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - cert
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                cert:
                  type: string
                  description: The bank's published signing certificate — PEM (-----BEGIN CERTIFICATE-----) or base64-DER.
      responses:
        '200':
          description: Pinned
          content:
            application/json:
              schema:
                type: object
                properties:
                  connectionId:
                    type: string
                  certSubject:
                    type: string
                  certExpiry:
                    type: string
                    format: date-time
        '400':
          description: Bad certificate or non-WS connection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/activate-danske':
    post:
      tags:
        - Connections
      summary: Activate Danske EDI Web Services (automated cert enrolment)
      description: 'Calls the Danske bxd.fi activation endpoint with the one-time PIN, self-issues the signing + encryption certificates, and marks the connection active. Admin only.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - userId
                - pin
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                userId:
                  type: string
                  description: Agreement number (User ID) from Danske.
                pin:
                  type: string
                  description: One-time transfer key from Danske.
      responses:
        '200':
          description: Activated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '400':
          description: 'Activation failed (wrong PIN, network, etc.)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/activate-nordea':
    post:
      tags:
        - Connections
      summary: Activate Nordea Corporate Access (HMAC cert enrolment via SMS code)
      description: 'Generates a Nordea-format signing CSR, sends the HMAC-signed enrolment request to Nordea''s Corporate Access endpoint using the SMS activation code, and stores the issued certificate. Admin only.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - holderName
                - signerId
                - country
                - activationCode
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                holderName:
                  type: string
                  description: Certificate-holder name (CN) as printed in the Nordea agreement.
                signerId:
                  type: string
                  description: Signer ID from the agreement.
                senderId:
                  type: string
                country:
                  type: string
                  minLength: 2
                  maxLength: 2
                activationCode:
                  type: string
                  description: 10-digit SMS activation code from Nordea.
      responses:
        '200':
          description: Activated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '400':
          description: Activation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/activate-bankconnect':
    post:
      tags:
        - Connections
      summary: 'Activate Bank Connect (DK gateway: automated cert enrolment)'
      description: 'Enrols the signing certificate with the Bank Connect data central (BD/BEC/SDC), stores the issued certificate and bank public key, and marks the connection active. Data central and country are pre-filled from the bank profile. Admin only.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - organisationId
                - functionId
                - activationCode
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                organisationId:
                  type: string
                  description: Bank registration number.
                functionId:
                  type: string
                  description: Per-agreement routing code from the bank.
                activationCode:
                  type: string
                  description: One-time activation code from the bank.
      responses:
        '200':
          description: Activated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '400':
          description: Activation failed or bank has no Bank Connect data central
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Connection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/generate-pgp':
    post:
      tags:
        - Connections
      summary: Generate our PGP key pair (SFTP channel)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: PGP key pair generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/bank-key':
    post:
      tags:
        - Connections
      summary: Save the bank's PGP public key (SFTP channel)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - armoredKey
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                armoredKey:
                  type: string
                  description: ASCII-armored PGP public key block.
      responses:
        '200':
          description: Bank key saved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/server-info':
    post:
      tags:
        - Connections
      summary: 'Save SFTP server info (host, port, paths, fingerprint)'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - host
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                host:
                  type: string
                port:
                  type: integer
                  default: 22
                uploadPath:
                  type: string
                downloadPath:
                  type: string
                hostFingerprint:
                  type: string
      responses:
        '200':
          description: Server info saved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/sftp-user':
    post:
      tags:
        - Connections
      summary: Set SFTP username and generate SSH key pair (single combined call)
      description: 'Sets the SFTP username AND generates the SSH key pair in one call: you cannot pre-generate the key before you have the username. The `username` must be obtained from the bank first; only then can you call this endpoint. The response includes the connection + readiness state, from which you can retrieve the generated SSH public key to send to the bank for whitelisting.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - username
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                username:
                  type: string
                  description: 'SFTP username assigned by the bank. Required: the SSH key generation and username registration are a single atomic operation.'
      responses:
        '200':
          description: Username set + SSH key generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/approvers':
    post:
      tags:
        - Connections
      summary: Assign approver users to a connection
      description: 'Assigns which users may approve payments on this connection. **Approvers and approval policy are separate:** the policy (under `POST /approval-policies`) defines the rule (how many approvers, any amount thresholds); this endpoint assigns the eligible users per connection. Both must be in place for a connection to reach go-live. Note: users must first exist in the company: create them with `POST /users` before assigning them as approvers.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - userIds
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                userIds:
                  type: array
                  items:
                    type: string
                  description: User IDs of company members with the Approver role. Must already exist in the company.
      responses:
        '200':
          description: Approvers assigned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '422':
          description: One or more user IDs are not valid approvers in this company
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/connections/{id}/activate':
    post:
      tags:
        - Connections
      summary: Mark SFTP connection active (after the bank has whitelisted the SSH key)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Connection activated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/intro-email-sent':
    post:
      tags:
        - Connections
      summary: Mark intro email as sent (tracks wizard progress)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/ws-environment':
    post:
      tags:
        - Connections
      summary: Toggle a Web Services connection between TEST and PRODUCTION
      description: Requires an already-activated connection (certificates issued); does not re-run PKI.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - environment
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                environment:
                  type: string
                  enum:
                    - TEST
                    - PRODUCTION
      responses:
        '200':
          description: Environment updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/covered-banks':
    post:
      tags:
        - Connections
      summary: Set which member banks a shared connection covers
      description: For a shared (group) connection. The store clamps the set to the bank's group and always includes the connection's own bank.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - coveredBankKeys
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                coveredBankKeys:
                  type: array
                  items:
                    type: string
                  description: Bank keys this connection serves (clamped to the bank's group).
      responses:
        '200':
          description: Covered banks updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/ebics-params':
    post:
      tags:
        - Connections
      summary: 'Save EBICS connection parameters (Host ID, Partner ID, User ID, …)'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - url
                - hostId
                - partnerId
                - userId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                url:
                  type: string
                  format: uri
                  description: EBICS server URL.
                hostId:
                  type: string
                  description: Bank-assigned Host ID.
                partnerId:
                  type: string
                  description: Customer ID (PartnerID) from the bank.
                userId:
                  type: string
                  description: Subscriber ID (UserID) from the bank.
                protocolVersion:
                  type: string
                  example: H005
                signatureVersion:
                  type: string
                  example: A006
                subscriberMode:
                  type: string
                  enum:
                    - single
                    - multi
      responses:
        '200':
          description: EBICS params saved
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/ebics-generate-keys':
    post:
      tags:
        - Connections
      summary: 'Generate EBICS subscriber keys (authentication, encryption, signature)'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Keys generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectionWithReadiness'
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/connections/{id}/intro-email':
    get:
      tags:
        - Connections
      summary: Render the bank intro email (draft to send to the bank contact)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Rendered email (subject + body)
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Not found
  '/connections/{id}/ebics-letter':
    get:
      tags:
        - Connections
      summary: 'Download the EBICS initialisation letter (PDF, base64-encoded)'
      description: 'Returns the signed INI/HIA initialisation letter as a base64-encoded PDF. Print, sign, and send to the bank to complete EBICS subscriber activation.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: PDF as base64
          content:
            application/json:
              schema:
                type: object
                properties:
                  pdfBase64:
                    type: string
                  filename:
                    type: string
        '404':
          description: Not found or keys not yet generated
  '/banks/{bankKey}/setup-info':
    get:
      tags:
        - Connections
      summary: Per-bank setup descriptor for the wizard UI
      description: 'Returns the channel kind, requirements text, field definitions, pre-known server/endpoint details, and SFTP wizard screens for a bank. Used by the setup wizard to render the correct flow.'
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
      responses:
        '200':
          description: Setup descriptor
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Unknown bank
      x-sandbox: true
  '/banks/{bankKey}/settings':
    get:
      tags:
        - Connections
      summary: Get per-company settings for a specific bank
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Bank settings (null if none saved)
          content:
            application/json:
              schema:
                type: object
    post:
      tags:
        - Connections
      summary: Update per-company settings for a specific bank
      description: 'Payment-affecting fields (agreementId, chargeBearer, executionDateOffsetDays, approvalPolicyId) require Admin. The `selected` pin is open to any member.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                agreementId:
                  type: string
                chargeBearer:
                  type: string
                  enum:
                    - SLEV
                    - SHAR
                    - CRED
                    - DEBT
                executionDateOffsetDays:
                  type: integer
                approvalPolicyId:
                  type: string
                selected:
                  type: boolean
      responses:
        '200':
          description: Settings saved
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required for payment-affecting fields
  /bank-settings:
    get:
      tags:
        - Connections
      summary: List all per-company bank settings in one call
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: All saved bank settings for this company
          content:
            application/json:
              schema:
                type: object
                properties:
                  settings:
                    type: array
                    items:
                      type: object
  /bank-requests:
    post:
      tags:
        - Platform
      summary: Request a bank we don't support yet
      description: Any signed-in user (or anonymous) can submit a BIC or bank name. The request is queued for the development team.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: BIC code or bank name.
                email:
                  type: string
                  format: email
      responses:
        '201':
          description: Request recorded
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  request:
                    type: object
                    properties:
                      id:
                        type: string
                      query:
                        type: string
                      createdAt:
                        type: string
                        format: date-time
        '400':
          description: Empty query
    get:
      tags:
        - Platform
      summary: List all bank requests (Admin only)
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: All requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/banks/{bankKey}/test-scenarios':
    get:
      tags:
        - Platform
      summary: Available one-click Test Payment scenarios for a bank
      description: 'Lists the canned test-payment scenarios this bank can send (domestic, sepa, international, urgent), tailored to its country and offered payment types: each with its label, amount, currency and the chosen payment type.'
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Offered scenarios
          content:
            application/json:
              schema:
                type: object
                properties:
                  scenarios:
                    type: array
                    items:
                      type: object
      x-sandbox: true
  '/banks/{bankKey}/test-payment':
    post:
      tags:
        - Platform
      summary: Send a one-click test payment
      description: 'Builds a schema-valid canonical payment for the chosen scenario from the debtor account supplied, then runs it through the normal convert → approval → deliver path over the chosen environment (defaults to the test channel). Requires a signed-in session.'
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - scenario
                - account
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                scenario:
                  type: string
                  enum:
                    - domestic
                    - sepa
                    - international
                    - urgent
                environment:
                  type: string
                  enum:
                    - test
                    - production
                account:
                  type: object
                  properties:
                    name:
                      type: string
                    iban:
                      type: string
                    other:
                      type: object
                      properties:
                        id:
                          type: string
                        scheme:
                          type: string
                    currency:
                      type: string
                    country:
                      type: string
      responses:
        '201':
          description: Test payment journaled
          content:
            application/json:
              schema:
                type: object
        '400':
          description: Invalid request
        '401':
          description: Sign in required
  '/banks/{bankKey}/payment-types':
    get:
      tags:
        - Discovery
      summary: Payment types offered by a specific bank
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
          example: nordea-dk
      responses:
        '200':
          description: Bank key/name + offered payment types with full metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  bankKey:
                    type: string
                    description: 'The bank''s registry key (consistent with /banks/{bankKey}/status).'
                  bank:
                    type: string
                    deprecated: true
                    description: Deprecated alias of bankKey — kept one release.
                  bankName:
                    type: string
                  countryCode:
                    type: string
                  paymentTypes:
                    type: array
                    items:
                      type: object
        '404':
          description: Unknown bank
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-sandbox: true
  '/validate/{bankKey}':
    post:
      tags:
        - Conversion
      summary: Validate a payment instruction (no conversion)
      description: 'Runs all three validation levels (generic → bank → payment-type) and returns every issue at once. Always returns HTTP 200: check `valid` in the response body. Use this to check a payment before sending it.'
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
          example: nordea-dk
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentInstruction'
      responses:
        '200':
          description: 'Validation outcome (valid or not: check `valid`)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationOutcome'
        '400':
          description: Malformed JSON body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown bank key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-sandbox: true
      x-codeSamples:
        - 
          lang: Shell
          label: cURL
          source: |
              curl -X POST https://your-host/validate/nordea-dk \
                -H 'X-API-Key: YOUR_KEY' \
                -H 'Content-Type: application/json' \
                -d '{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}'
        - 
          lang: JavaScript
          label: Fetch
          source: |
              const res = await fetch("https://your-host/validate/nordea-dk", {
                method: "POST",
                headers: {
                  "X-API-Key": "YOUR_KEY",
                  "Content-Type": "application/json",
                },
                body: JSON.stringify({"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}),
              });
              const data = await res.json();
        - 
          lang: Python
          label: Python
          source: |
              import requests
              
              resp = requests.request(
                "POST", "https://your-host/validate/nordea-dk",
                headers={"X-API-Key": "YOUR_KEY"},
                json={"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]},
              )
              resp.raise_for_status()
              data = resp.json()
  '/convert/{bankKey}':
    post:
      tags:
        - Conversion
      summary: Validate then convert to bank-specific pain.001 XML
      description: 'Validates the input (all three levels) then converts it to the bank''s specific ISO 20022 `pain.001` format. Returns XML on success. Add `?validate=true` to additionally validate the generated XML against the official ISO 20022 XSD. NOTE (QA#24): this is the STATELESS engine — it does NOT apply per-company bank settings (charge bearer, execution-date offset). Pass `?painVersion=` to choose the output version here; per-company settings are applied only on the delivery path, `POST /journal/payments`. Use that route to see a payment exactly as it will be submitted.'
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
          example: nordea-dk
        - 
          name: validate
          in: query
          required: false
          schema:
            type: boolean
          description: 'If true, XSD-validate the generated XML before returning it.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PaymentInstruction'
      responses:
        '200':
          description: Bank-specific pain.001 XML
          content:
            application/xml:
              schema:
                type: string
        '400':
          description: Malformed JSON body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown bank key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: 'Validation failed: returns all issues at once'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-sandbox: true
      x-codeSamples:
        - 
          lang: Shell
          label: cURL
          source: |
              curl -X POST https://your-host/convert/nordea-dk \
                -H 'X-API-Key: YOUR_KEY' \
                -H 'Content-Type: application/json' \
                -d '{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}'
        - 
          lang: JavaScript
          label: Fetch
          source: |
              const res = await fetch("https://your-host/convert/nordea-dk", {
                method: "POST",
                headers: {
                  "X-API-Key": "YOUR_KEY",
                  "Content-Type": "application/json",
                },
                body: JSON.stringify({"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}),
              });
              const data = await res.text()  // pain.001 XML;
        - 
          lang: Python
          label: Python
          source: |
              import requests
              
              resp = requests.request(
                "POST", "https://your-host/convert/nordea-dk",
                headers={"X-API-Key": "YOUR_KEY"},
                json={"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]},
              )
              resp.raise_for_status()
              data = resp.text  # pain.001 XML
  /journal/payments:
    post:
      tags:
        - Journal
      summary: 'Submit a payment: convert + journal (+ deliver if host-to-host)'
      description: 'The primary ERP integration call. Validates + converts the canonical payment for `bankKey`, records it in the journal, and (when the bank has an active host-to-host connection) routes it through approval + delivery. Send a unique `Idempotency-Key` per logical payment so a network-timeout retry can never double-submit. Requires authentication (X-API-Key for ERP integrations).'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 8
            maxLength: 255
          description: Unique per company per logical payment. Same key + same body replays the original response; same key + different body → 409.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JournalPaymentRequest'
      responses:
        '200':
          description: 'Payment recorded (replay of a prior idempotent submit carries `Idempotency-Replayed: true`)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalDocument'
        '201':
          description: Payment recorded + journaled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalDocument'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Scope mismatch (cross-tenant)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: 'Idempotency conflict (same key, different body) or an in-flight retry'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: 'Validation failed: all issues at once'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: 'Rate limited (production): see Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-codeSamples:
        - 
          lang: Shell
          label: cURL
          source: |
              curl -X POST https://your-host/journal/payments \
                -H 'X-API-Key: YOUR_KEY' \
                -H 'Content-Type: application/json' \
                -d '{"platformId":"YOUR_PLATFORM_ID","companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}}'
        - 
          lang: JavaScript
          label: Fetch
          source: |
              const res = await fetch("https://your-host/journal/payments", {
                method: "POST",
                headers: {
                  "X-API-Key": "YOUR_KEY",
                  "Content-Type": "application/json",
                },
                body: JSON.stringify({"platformId":"YOUR_PLATFORM_ID","companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}}),
              });
              const data = await res.json();
        - 
          lang: Python
          label: Python
          source: |
              import requests
              
              resp = requests.request(
                "POST", "https://your-host/journal/payments",
                headers={"X-API-Key": "YOUR_KEY"},
                json={"platformId":"YOUR_PLATFORM_ID","companyId":"YOUR_COMPANY_ID","bankKey":"nordea-dk","payment":{"messageId":"M-1","creationDateTime":"2026-06-11T10:00:00Z","initiatingParty":{"name":"Acme"},"payments":[{"paymentId":"P-1","paymentType":"sepa","executionDate":"2026-06-12","debtor":{"name":"Acme","account":{"iban":"DK5000400440116243"}},"transactions":[{"endToEndId":"E2E-1","amount":"100.00","currency":"EUR","creditor":{"name":"Beta","country":"DE","account":{"iban":"DE89370400440532013000"}}}]}]}},
              )
              resp.raise_for_status()
              data = resp.json()
  /journal/payments/preview:
    post:
      tags:
        - Journal
      summary: 'Preview a payment: convert + validate, return XML (no journal, no send)'
      description: 'Read-only. Runs the same settings-fill → validate → convert (+ XSD) pipeline as `POST /journal/payments` and returns the generated bank file, but does NOT journal, approve, or dispatch — and needs no live host-to-host connection. This is the legitimate "see the file" path that replaces the removed file-return mode. Requires authentication.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JournalPaymentRequest'
      responses:
        '200':
          description: Preview generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  xml:
                    type: string
                  painVersion:
                    type: string
                  preview:
                    type: boolean
        '400':
          description: Missing fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Validation / XSD failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/ingest:
    post:
      tags:
        - Journal
      summary: 'Ingest a bank file (camt.053/054, pain.002, MT940) → normalised JSON'
      description: Upload a raw bank statement/status file; it is parsed to the one normalised shape and journaled. Requires authentication.
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                bankKey:
                  type: string
                content:
                  type: string
                  description: Raw file contents
              required:
                - platformId
                - companyId
                - content
      responses:
        '201':
          description: File ingested + normalised
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalDocument'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Unrecognised / unparseable file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/documents:
    get:
      tags:
        - Journal
      summary: List journal document summaries (newest first)
      description: 'Tenant-scoped list of payments + statements (metadata only: no decrypted payload). Filter by direction/type.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
        - 
          name: direction
          in: query
          required: false
          schema:
            type: string
            enum:
              - inbound
              - outbound
        - 
          name: type
          in: query
          required: false
          schema:
            type: string
            example: camt.053
        - 
          name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
          description: Page size (enables keyset pagination via nextCursor).
        - 
          name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Opaque cursor from a prior page's `nextCursor`. (`before` is accepted as a deprecated alias.)
      responses:
        '200':
          description: Document summaries
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/JournalDocument'
                  nextCursor:
                    type: string
                    nullable: true
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/export:
    post:
      tags:
        - Journal
      summary: Export full document payloads by journal number (admin / API key)
      description: 'Bulk extraction of full pain.001/camt payloads (account numbers + amounts), optionally filtered within a statement. Admin-only for human sessions; the ERP API key is the intended consumer.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                journalNos:
                  type: array
                  items:
                    type: string
              required:
                - platformId
                - companyId
                - journalNos
      responses:
        '200':
          description: Exported documents + any notFound journal numbers
          content:
            application/json:
              schema:
                type: object
                properties:
                  exported:
                    type: array
                    items:
                      type: object
                  notFound:
                    type: array
                    items:
                      type: string
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin required (human session)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /accounts:
    get:
      tags:
        - Accounts
      summary: List bank accounts for a company (balances masked)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Accounts with latest balances
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /accounts/statements:
    get:
      tags:
        - Accounts
      summary: List imported camt.053 statements for an account
      description: Per-account statement history (the parsed NormalisedStatement summaries) used by the Statements screen.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
        - 
          name: account
          in: query
          required: false
          schema:
            type: string
            description: IBAN or account id to filter to.
      responses:
        '200':
          description: Statements + any detected sequence gaps
          content:
            application/json:
              schema:
                type: object
                properties:
                  account:
                    type: string
                    nullable: true
                  statements:
                    type: array
                    items:
                      type: object
                  gaps:
                    type: array
                    items:
                      type: object
                    description: Possible missing statements detected by the continuity check.
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /approvals/pending:
    get:
      tags:
        - Approvals
      summary: List payments awaiting approval
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Pending approval requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  requests:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/approvals/{id}/approve':
    post:
      tags:
        - Approvals
      summary: Approve a pending payment (maker ≠ checker enforced)
      description: An approver other than the maker approves the request; once the policy's required approvals are met the payment proceeds to delivery. May require a 2FA code/backup code if step-up is configured.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                reason:
                  type: string
                code:
                  type: string
                  description: 2FA TOTP code (if step-up required).
                backupCode:
                  type: string
      responses:
        '200':
          description: Approval recorded (and payment dispatched if fully approved)
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Maker cannot approve own payment / not an approver
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/approvals/{id}/reject':
    post:
      tags:
        - Approvals
      summary: Reject a pending payment
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                reason:
                  type: string
      responses:
        '200':
          description: Rejection recorded
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /approvals/policies:
    get:
      tags:
        - Approvals
      summary: List approval policies for a company
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Policies
          content:
            application/json:
              schema:
                type: object
                properties:
                  policies:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
    post:
      tags:
        - Approvals
      summary: Create an approval policy (Admin)
      description: Defines required approver count + amount thresholds. Admin only.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - name
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                name:
                  type: string
                requiredApprovals:
                  type: integer
                  minimum: 1
                thresholdAmount:
                  type: string
                currency:
                  type: string
      responses:
        '201':
          description: Policy created
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/approvals/policies/{id}':
    post:
      tags:
        - Approvals
      summary: Edit a DRAFT approval policy (Admin)
      description: 'Edit a policy''s name / required approver count / thresholds. Works only on a DRAFT version — an Active version is immutable (409); change it via /approvals/policies/{id}/propose then co-sign.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                name:
                  type: string
                requiredApprovals:
                  type: integer
                  minimum: 1
                thresholdAmount:
                  type: string
                currency:
                  type: string
      responses:
        '200':
          description: Updated draft policy
          content:
            application/json:
              schema:
                type: object
                properties:
                  policy:
                    type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '409':
          description: Version is not a draft / conflict
  '/approvals/policies/{id}/lock':
    post:
      tags:
        - Approvals
      summary: Activate (lock) a draft approval policy (Admin)
      description: Locks a DRAFT version → Active and immutable. Runs the quorum check (a dual policy needs ≥2 named approvers); fails 409 if it can't be met. An Active version is changed only via propose + co-sign.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Policy activated
          content:
            application/json:
              schema:
                type: object
                properties:
                  policy:
                    type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '409':
          description: Quorum not met / conflict
  '/approvals/policies/{id}/propose':
    post:
      tags:
        - Approvals
      summary: Propose a change to an Active policy (Admin)
      description: 'Clones the Active version to an editable draft v(n+1). Edit the draft (POST /approvals/policies/{draftId}), then submit it for activation (/approvals/changes/{draftId-derived}/submit) and have a second Admin co-sign. The old version is retained forever once superseded.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '201':
          description: Draft version created
          content:
            application/json:
              schema:
                type: object
                properties:
                  policy:
                    type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '409':
          description: Base not Active / change already in progress
  /approvals/changes:
    get:
      tags:
        - Approvals
      summary: List approval-policy change requests (Admin)
      description: The dual-co-sign change requests for the company. `?status=pending` (default) | activated | cancelled.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - pending
              - activated
              - cancelled
      responses:
        '200':
          description: Change requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  changes:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/approvals/changes/{changeId}/{action}':
    post:
      tags:
        - Approvals
      summary: Submit / co-sign / cancel a policy change (Admin)
      description: 'action=submit — the proposer submits the draft for activation (counts as the FIRST of 2 co-signs). action=cosign — a SECOND, distinct Admin co-signs: the new version goes Active, the base is Superseded, and attached banks re-point to the new version. action=cancel — any Admin cancels a pending change (the draft is discarded; the base stays Active). A 2nd co-sign by the same Admin is refused (409).'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: changeId
          in: path
          required: true
          schema:
            type: string
        - 
          name: action
          in: path
          required: true
          schema:
            type: string
            enum:
              - submit
              - cosign
              - cancel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Change updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  change:
                    type: object
                  policy:
                    type: object
                  supersededId:
                    type: string
                  affectedBanks:
                    type: array
                    items:
                      type: string
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '409':
          description: Same-admin co-sign / conflict
  /journal/list:
    get:
      tags:
        - Journal
      summary: 'Paginated journal feed (cursor): the ERP read surface'
      description: 'Tenant-scoped, newest-first list of journal documents with a stable opaque `cursor` for forward pagination. Metadata only (no decrypted payload).'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
        - 
          name: cursor
          in: query
          required: false
          schema:
            type: string
            description: Opaque cursor from a prior page's `nextCursor`.
        - 
          name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
        - 
          name: direction
          in: query
          required: false
          schema:
            type: string
            enum:
              - inbound
              - outbound
      responses:
        '200':
          description: A page of documents + nextCursor
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/JournalDocument'
                  nextCursor:
                    type: string
                    nullable: true
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/journal/documents/{id}':
    get:
      tags:
        - Journal
      summary: Fetch one journal document with its full (decrypted) payload
      description: 'Returns the document including its canonical/normalised payload and, for seeded/generated docs, the raw `xml` for download. Tenant-scoped.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Document + payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JournalDocument'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not found (or not in this tenant's scope)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/journal/documents/{id}/payments':
    get:
      tags:
        - Journal
      summary: Per-payment lines within an outbound batch (the drawer)
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 'Per-line payments (payee, amount, endToEndId, status)'
          content:
            application/json:
              schema:
                type: object
                properties:
                  payments:
                    type: array
                    items:
                      type: object
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/journal/documents/{id}/file':
    get:
      tags:
        - Journal
      summary: Download the generated pain.001 XML for an outbound document
      description: 'Returns the stored payment file as an `application/xml` attachment. 404 if the document isn''t in scope, isn''t outbound, or has no generated XML. Downloading does NOT mark the document retrieved.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: The pain.001 XML (attachment)
          content:
            application/xml:
              schema:
                type: string
        '404':
          description: No downloadable file for this document
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/payment-types:
    get:
      tags:
        - Journal
      summary: Tally of outbound payments grouped by payment type
      description: 'Lightweight GROUP BY over the plaintext summary column (no payload decrypt), weighted by transaction count: powers the dashboard payments-by-type view.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Per-type counts + total
          content:
            application/json:
              schema:
                type: object
                properties:
                  counts:
                    type: object
                    additionalProperties:
                      type: integer
                  total:
                    type: integer
                  sampled:
                    type: integer
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/reconciliation:
    get:
      tags:
        - Journal
      summary: 'Reconciliation view: correlate outbound payments with pain.002 + camt evidence'
      description: 'For each outbound payment, returns its real-world state (cleared / rejected / pending) and the matching evidence, by correlating inbound pain.002 status reports and camt.053/054 statement entries 1:1.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Per-payment reconciliation + evidence
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        documentId:
                          type: string
                        journalNo:
                          type: string
                        messageId:
                          type: string
                        state:
                          type: string
                          enum:
                            - cleared
                            - rejected
                            - cancelled
                            - pending
                        evidence:
                          type: object
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /journal/events:
    get:
      tags:
        - Journal
      summary: Activity-log events (audit trail) for a company
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Events newest-first
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /inbound/payment-status:
    post:
      tags:
        - Journal
      summary: Push a pain.002 status report → reconcile the outbound payment
      description: Accepts a raw pain.002 (or normalised status); matches it to the original outbound by MsgId/endToEndId and advances the payment status. Authenticated.
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - content
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                bankKey:
                  type: string
                content:
                  type: string
                  description: Raw pain.002 XML.
      responses:
        '200':
          description: Reconciled (status advanced)
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Unparseable / unmatched status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /inbound/statement:
    post:
      tags:
        - Journal
      summary: 'Parse a camt.053/054 / MT940 statement → normalised JSON (preview only, not stored)'
      description: 'Stateless parse-only preview: the raw file body is normalised and returned but NOT journaled (use POST /journal/ingest to store). A camt.053 may carry several <Stmt> (one per account); ALL are returned in the `items` list envelope.'
      security:
        - 
          ApiKeyAuth: []
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          text/plain:
            schema:
              type: string
              description: Raw camt.053/054 XML or MT940 text.
      responses:
        '200':
          description: Normalised statement(s)
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
        '400':
          description: Empty body or unrecognised format
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Unparseable file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhook subscriptions
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Subscriptions
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
        '401':
          description: Sign in required
    post:
      tags:
        - Webhooks
      summary: Create a webhook subscription (Admin)
      description: Registers an HTTPS endpoint to receive signed event callbacks. Admin only.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
                - url
                - events
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  items:
                    type: string
      responses:
        '201':
          description: Subscription created (returns the signing secret once)
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
  '/webhooks/{id}':
    delete:
      tags:
        - Webhooks
      summary: Delete a webhook subscription (Admin)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Not found
  '/webhooks/{id}/enable':
    post:
      tags:
        - Webhooks
      summary: Enable a disabled webhook (Admin)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Webhook enabled
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Not found
  '/webhooks/{id}/disable':
    post:
      tags:
        - Webhooks
      summary: Disable a webhook without deleting it (Admin)
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Webhook disabled
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Not found
  '/webhooks/{id}/test':
    post:
      tags:
        - Webhooks
      summary: Fire a signed test event to the webhook URL (Admin)
      description: Sends a signed ping to the endpoint and returns the delivery result.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - platformId
                - companyId
              properties:
                platformId:
                  type: string
                companyId:
                  type: string
      responses:
        '200':
          description: Delivery result of the test event
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Not found
  '/webhooks/{id}/deliveries':
    get:
      tags:
        - Webhooks
      summary: List delivery attempts for a webhook (Admin)
      description: 'Per-attempt delivery log (reliability phase 1) so no event is silently lost. Newest first, keyset-paginated: pass `cursor` = the previous page''s `nextCursor` to continue. `limit` defaults to 50, max 200.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
        - 
          name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Opaque cursor from the previous page's `nextCursor`. (`after` is accepted as a deprecated alias.)
        - 
          name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        '200':
          description: A page of delivery attempts
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookDelivery'
                  nextCursor:
                    type: string
                    nullable: true
                    description: Last delivery id in this page when a full page was returned; null when no more.
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Webhook not found
  '/webhooks/{id}/deliveries/{deliveryId}/redeliver':
    post:
      tags:
        - Webhooks
      summary: Replay a single delivery (Admin)
      description: Re-enqueues the original event payload as a NEW delivery attempt (a new row — the original failure record is preserved). A fresh signature is computed at send time. 404 if the delivery is unknown or not in a failed/delivered state.
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: deliveryId
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Re-enqueued
          content:
            application/json:
              schema:
                type: object
                properties:
                  redelivered:
                    type: boolean
                  deliveryId:
                    type: string
                    description: Id of the NEW delivery row.
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Delivery not found / not redeliverable
  '/webhooks/{id}/redeliver-failed':
    post:
      tags:
        - Webhooks
      summary: Bulk-replay failed deliveries since a timestamp (Admin)
      description: 'Re-enqueues `failed` deliveries for the webhook created at/after `since`. BOUNDED: a single call replays at most a fixed cap (500); when a full page is returned, `nextCursor` is non-null — pass it back as `cursor` to drain the rest. Idempotent: a replay id is derived from the source delivery, so a repeated/overlapping window cannot double-enqueue.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
        - 
          name: since
          in: query
          required: true
          schema:
            type: string
            format: date-time
          description: ISO timestamp — only failed deliveries created at/after this are replayed.
        - 
          name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Opaque cursor from a prior page's `nextCursor` to continue draining.
      responses:
        '200':
          description: Re-enqueued count + drain cursor
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  nextCursor:
                    type: string
                    nullable: true
        '400':
          description: Missing/invalid since
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Webhook not found
  '/webhooks/{id}/re-enable':
    post:
      tags:
        - Webhooks
      summary: Re-enable an auto-disabled webhook (Admin)
      description: 'Clears an auto-disabled state (reliability phase 2): resets `consecutiveFailures` to 0 and clears `disabledAt`/`disabledReason`. 400 if the webhook is not currently disabled.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Re-enabled
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhookId:
                    type: string
                  enabledAt:
                    type: string
                    format: date-time
        '400':
          description: Webhook is not disabled
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Webhook not found
  '/webhooks/{id}/rotate-secret':
    post:
      tags:
        - Webhooks
      summary: Rotate the signing secret with an overlap window (Admin)
      description: 'Generates a new signing secret and returns it ONCE. The previous secret is retained as a ''pending'' secret and BOTH signatures verify during the overlap window (`overlapHours`, default 24, max 72), so the receiver can update its key without dropping in-flight deliveries. Outbound deliveries are immediately signed with the NEW secret. After the window (housekeeping) the old secret is dropped.'
      security:
        - 
          SessionToken: []
      parameters:
        - 
          name: id
          in: path
          required: true
          schema:
            type: string
        - 
          name: platformId
          in: query
          required: true
          schema:
            type: string
        - 
          name: companyId
          in: query
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                overlapHours:
                  type: integer
                  default: 24
                  minimum: 1
                  maximum: 72
                  description: How long the old secret stays valid.
      responses:
        '200':
          description: Rotated — newSecret shown once
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhookId:
                    type: string
                  newSecret:
                    type: string
                    description: The new signing secret (whsec_…). Distribute to the receiver; not retrievable again.
                  overlapUntil:
                    type: string
                    format: date-time
        '400':
          description: Invalid body
        '401':
          description: Sign in required
        '403':
          description: Admin required
        '404':
          description: Webhook not found
  /auth/login:
    post:
      tags:
        - Auth
      summary: 'Log in (PUBLIC): returns a session token + sets the session cookie'
      description: 'PUBLIC. Exchanges subdomain + email + password for a signed session. If 2FA is enabled, returns a step-up challenge instead (complete it at POST /auth/2fa).'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - subdomain
                - email
                - password
              properties:
                subdomain:
                  type: string
                  example: demo
                email:
                  type: string
                  format: email
                password:
                  type: string
      responses:
        '200':
          description: Logged in (token) OR a 2FA step-up challenge
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                  twoFactorRequired:
                    type: boolean
        '401':
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-codeSamples:
        - 
          lang: Shell
          label: cURL
          source: |
              curl -X POST https://your-host/auth/login \
                -H 'Content-Type: application/json' \
                -d '{"subdomain":"demo","email":"you@acme.com","password":"…"}'
        - 
          lang: JavaScript
          label: Fetch
          source: |
              const res = await fetch("https://your-host/auth/login", {
                method: "POST",
                headers: {
                  "Content-Type": "application/json",
                },
                body: JSON.stringify({"subdomain":"demo","email":"you@acme.com","password":"…"}),
              });
              const data = await res.json();
        - 
          lang: Python
          label: Python
          source: |
              import requests
              
              resp = requests.request(
                "POST", "https://your-host/auth/login",
                json={"subdomain":"demo","email":"you@acme.com","password":"…"},
              )
              resp.raise_for_status()
              data = resp.json()
  /2fa/setup:
    post:
      tags:
        - Auth
      summary: Begin 2FA enrollment (signed-in user) — returns a TOTP secret + otpauth URI
      description: QA#23. Generates a NOT-yet-active TOTP secret for the signed-in user and returns its otpauth:// URI (render as a QR). Activate it with POST /2fa/confirm. Session-only.
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: Pending secret + otpauth URI
          content:
            application/json:
              schema:
                type: object
                properties:
                  secret:
                    type: string
                  otpauthUri:
                    type: string
                  issuer:
                    type: string
        '401':
          description: Sign in first
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /2fa/confirm:
    post:
      tags:
        - Auth
      summary: Confirm 2FA enrollment with a TOTP code — returns one-time backup codes
      description: Verifies a code against the pending secret and activates 2FA. The backup codes are returned EXACTLY ONCE — surface + warn. Session-only; throttled per user.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
                  description: 6-digit TOTP code
      responses:
        '200':
          description: Enabled + one-time backup codes
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
                  backupCodes:
                    type: array
                    items:
                      type: string
        '400':
          description: Setup not started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Already enabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Code didn't match
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too many attempts (Retry-After)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /2fa/disable:
    post:
      tags:
        - Auth
      summary: Disable 2FA (requires a fresh code; blocked when the company forces 2FA)
      description: Turns off the signed-in user's 2FA after verifying a fresh TOTP or backup code; refused (403) when the company requires 2FA. Revokes the user's OTHER sessions. Session-only; throttled.
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                code:
                  type: string
                backupCode:
                  type: string
      responses:
        '200':
          description: Disabled
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
        '403':
          description: Company requires 2FA
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Not enabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: A valid code is required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too many attempts (Retry-After)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /company/require-2fa:
    post:
      tags:
        - Auth
      summary: 'Admin: set the company-wide 2FA requirement'
      description: 'Admin-only. When enabled, every user in the company must enroll in 2FA. Distinct from the per-user `enabled` state — the response field is `require2fa` (company policy).'
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - enabled
              properties:
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated company policy
          content:
            application/json:
              schema:
                type: object
                properties:
                  require2fa:
                    type: boolean
        '403':
          description: Admin role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/2fa-reset:
    post:
      tags:
        - Auth
      summary: 'Admin: reset a locked-out user''s 2FA enrollment'
      description: 'Admin-only. Clears the target user''s 2FA so they can re-enroll, and revokes ALL of that user''s sessions. Use when someone has lost their authenticator.'
      security:
        - 
          SessionToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - userId
              properties:
                userId:
                  type: string
      responses:
        '200':
          description: Reset
          content:
            application/json:
              schema:
                type: object
                properties:
                  reset:
                    type: boolean
                  userId:
                    type: string
        '400':
          description: userId required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Admin role required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/me:
    get:
      tags:
        - Auth
      summary: Current principal (who am I)
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: The signed-in user + roles + company
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Not signed in
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/logout:
    post:
      tags:
        - Auth
      summary: Log out (invalidates the session)
      security:
        - 
          SessionToken: []
      responses:
        '200':
          description: Logged out
  /auth/platform-info:
    get:
      tags:
        - Auth
      summary: Public platform branding for the login page
      description: 'Returns the branding for the platform resolved from the request''s subdomain (no auth — called before login). On a known subdomain: the platform''s display name + subdomain. Otherwise the generic BankConnector branding. `logoUrl` is reserved for a later phase (null for now).'
      responses:
        '200':
          description: Platform branding
          content:
            application/json:
              schema:
                type: object
                required:
                  - platformId
                  - displayName
                  - subdomain
                  - logoUrl
                properties:
                  platformId:
                    type: string
                    nullable: true
                  displayName:
                    type: string
                  subdomain:
                    type: string
                    nullable: true
                  logoUrl:
                    type: string
                    nullable: true
  /auth/2fa:
    post:
      tags:
        - Auth
      summary: Complete a 2FA login step-up (PUBLIC)
      description: PUBLIC. Submits the TOTP (or backup) code against the challenge from POST /auth/login to obtain the session.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - challenge
                - code
              properties:
                challenge:
                  type: string
                  description: The challenge id from /auth/login.
                code:
                  type: string
                backupCode:
                  type: string
      responses:
        '200':
          description: Session granted
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
        '401':
          description: Invalid code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/banks/{bankKey}/status':
    get:
      tags:
        - Discovery
      summary: Whether a bank is supported + its channel/payment readiness (PUBLIC)
      parameters:
        - 
          name: bankKey
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/BankKey'
      responses:
        '200':
          description: Bank support status
          content:
            application/json:
              schema:
                type: object
        '404':
          description: Unknown bank
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      x-sandbox: true
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Machine / ERP integration credential. Tenant scope is derived from the key: never from the request body.'
    SessionToken:
      type: apiKey
      in: header
      name: X-Session-Token
      description: Signed-in human session (the web app). The browser uses an httpOnly cookie + CSRF instead.
  schemas:
    BankKey:
      type: string
      enum:
        - bnp-paribas-bh
        - bnp-paribas-kw
        - bnp-paribas-my
        - bnp-paribas-qa
        - bnp-paribas-sa
        - bnp-paribas-vn
        - bnz-nz
        - bpi-ph
        - banca-intensa-rs
        - cba-au
        - cba-nz
        - citibank-in
        - citibank-my
        - citibank-ph
        - citibank-th
        - citibank-tw
        - citibank-vn
        - deutsche-bank-in
        - deutsche-bank-pt
        - hsbc-ae
        - hsbc-au
        - hsbc-cn
        - hsbc-eg
        - hsbc-hk
        - hsbc-id
        - hsbc-in
        - hsbc-jp
        - hsbc-kr
        - hsbc-mx
        - hsbc-my
        - hsbc-nz
        - hsbc-ph
        - hsbc-sg
        - hsbc-tr
        - hsbc-tw
        - handelsbanken-cn
        - handelsbanken-hk
        - handelsbanken-sg
        - jp-morgan-chase-au
        - otp-bank-me
        - seb-sg
        - standard-bank-za
        - standard-chartered-my
        - standard-chartered-sg
        - unicredit-ba
        - unicredit-rs
        - bnp-paribas-br
        - citibank-us
        - deutsche-bank-us
        - hsbc-br
        - hsbc-us
        - handelsbanken-us
        - jp-morgan-chase-ca
        - scotia-ca
        - tdcanadatrust-ca
        - abn-amro-be
        - alior-bank-pl
        - bnp-paribas-bg
        - bnp-paribas-cz
        - bnp-paribas-es
        - bnp-paribas-gb
        - bnp-paribas-hu
        - bank-of-america-it
        - csob-cz
        - ceska-sporitelna-cz
        - citibank-fr
        - citibank-gb
        - commerzbank-cz
        - commerzbank-hu
        - commerzbank-sk
        - credit-lyonnaise-fr
        - dsk-bank-bg
        - danske-bank-be
        - danske-bank-ie
        - deutsche-bank-be
        - deutsche-bank-fr
        - deutsche-bank-hu
        - dnb-gb
        - dnb-se
        - eik-banki-fo
        - eika-alliance-no
        - groenbank-gl
        - hsbc-be
        - hsbc-es
        - hsbc-fr
        - hsbc-ie
        - hsbc-it
        - handelsbanken-ee
        - handelsbanken-fi
        - handelsbanken-gb
        - handelsbanken-lt
        - handelsbanken-lu
        - handelsbanken-lv
        - ing-fr
        - jp-morgan-chase-be
        - jp-morgan-chase-gb
        - jp-morgan-chase-lu
        - natwest-gb
        - nordoya-spar-fo
        - otp-bank-hr
        - pbz-hr
        - bank-pekao-pl
        - raiffeisen-bank-cz
        - seb-fi
        - seb-gb
        - seb-no
        - societe-generale-lu
        - sparoresund-se
        - sparebank-1-alliance-no
        - swedbank-fi
        - swedbank-no
        - unicredit-bg
        - unicredit-it
        - unicredit-ro
        - unicredit-sk
        - werhahn-bank-de
        - zagrebacka-hr
        - danske-dk
        - deutsche-bank-de
        - nordea-dk
        - bbva-es
        - caixabank-es
        - pko-bank-polski-pl
        - vub-banka-sk
        - danske-fi
        - danske-gb
        - danske-pl
        - seb-ee
        - seb-lv
        - seb-lt
        - swedbank-ee
        - swedbank-lv
        - swedbank-lt
        - sr-bank-no
        - ing-nl
        - swedbank-se
        - svb-us
        - seb-se
        - dnb-no
        - hsbc-gb
        - jpmorgan-us
        - nordea-se
        - nordea-no
        - nordea-fi
        - danske-se
        - danske-no
        - abn-amro-nl
        - nord-lb-de
        - aareal-bank-de
        - apobank-de
        - bank-fuer-sozialwirtschaft-de
        - carl-plump-de
        - loebbecke-de
        - bayernlb-de
        - berliner-sparkasse-de
        - sparkasse-bremen-de
        - hsbc-de
        - haspa-de
        - hauck-aufhaeuser-de
        - berenberg-de
        - jyske-bank-de
        - helaba-de
        - naspa-de
        - nordea-de
        - olb-de
        - postbank-de
        - procredit-de
        - saarlb-de
        - santander-de
        - societe-generale-de
        - sparkasse-darmstadt-de
        - sparkasse-fuerth-de
        - sparkasse-heidelberg-de
        - sparkasse-koelnbonn-de
        - suedwestbank-de
        - wells-fargo-us
        - sparkasse-en-de
        - barclays-gb
        - bnp-paribas-fr
        - bnp-be
        - bnp-pl
        - bnp-it
        - bnp-lu
        - berliner-volksbank-de
        - landesbank-bw-de
        - ing-de
        - erste-group-at
        - raiffeisen-bank-international-at
        - bank-austria-at
        - oberbank-at
        - postfinance-ch
        - raiffeisen-ch
        - zurcher-kantonalbank-ch
        - abn-amro-de
        - bnp-de
        - danske-de
        - handelsbanken-de
        - seb-de
        - deutsche-at
        - commerzbank-nl
        - danske-nl
        - deutsche-nl
        - hsbc-nl
        - handelsbanken-nl
        - volksbank-raiffeisenbank-de
        - ubs-ch
        - rbc-ca
        - deutsche-bank-br
        - sydbank-dk
        - bmo-ca
        - jyske-bank-dk
        - spar-nord-dk
        - arbejdernes-dk
        - ringkjobing-dk
        - sjaelland-fyn-dk
        - commerzbank-de
        - credit-agricole-fr
        - handelsbanken-se
        - rabobank-nl
        - de-volksbank-nl
        - triodos-nl
        - van-lanschot-nl
        - nibc-nl
        - bng-nl
        - nwb-nl
        - bunq-nl
        - bank-mendes-gans-nl
        - credit-mutuel-fr
        - credit-suisse-ch
        - cic-fr
        - arkea-fr
        - cibc-ca
        - unicredit-de
        - unicredit-cz
        - societe-generale-fr
        - lloyds-gb
        - standard-chartered-gb
        - op-financial-group-fi
        - ing-be
        - ing-pl
        - ing-ro
        - ing-hu
        - ing-cz
        - ing-bg
        - bank-of-america-us
        - us-bank-us
        - truist-us
        - pnc-us
        - mbank-pl
        - aib-ie
        - eurobank-gr
        - splitska-hr
        - andelskassen-faelleskassen-dk
        - danske-andelskassers-bank-dk
        - den-jyske-sparekasse-dk
        - faster-andelskasse-dk
        - frorup-andelskasse-dk
        - froslev-mollerup-sparekasse-dk
        - fynske-bank-dk
        - gronlandsbanken-gl
        - hvidbjerg-bank-dk
        - lollands-bank-dk
        - laegernes-bank-dk
        - merkur-andelskasse-dk
        - mons-bank-dk
        - nykredit-bank-dk
        - vestjyskbank-dk
        - foroya-banki-fo
        - banknordik-dk
        - betri-banki-fo
        - borbjerg-sparekasse-dk
        - dragsholm-sparekasse-dk
        - sydjysk-sparekasse-dk
        - klim-sparekasse-dk
        - lan-og-spar-bank-dk
        - middelfart-sparekasse-dk
        - nordoya-sparikassi-fo
        - rise-sparekasse-dk
        - ronde-sparekasse-dk
        - sparekassen-balling-dk
        - sparekassen-bredebro-dk
        - sparekassen-danmark-dk
        - sparekassen-kronjylland-dk
        - spks-for-nr-nebel-og-omegn-dk
        - sparekassen-thy-dk
        - suduroyar-sparikassi-fo
        - sonderha-horsted-sparekasse-dk
        - djurslands-bank-dk
        - kreditbanken-dk
        - nordfyns-bank-dk
        - skjern-bank-dk
      example: nordea-dk
      description: 'A registered bank profile key, `<bank>-<country>` (e.g. danske-dk).'
    JournalPaymentRequest:
      type: object
      required:
        - platformId
        - companyId
        - bankKey
        - payment
      properties:
        platformId:
          type: string
        companyId:
          type: string
        bankKey:
          $ref: '#/components/schemas/BankKey'
        payment:
          $ref: '#/components/schemas/PaymentInstruction'
        environment:
          type: string
          enum:
            - test
            - production
          description: Which connection delivers the payment. Defaults to production; pass "test" to dispatch over the bank's test channel.
        validate:
          type: boolean
          description: Also XSD-validate the generated XML.
        idempotencyKey:
          type: string
          description: Alternative to the Idempotency-Key header.
    JournalDocument:
      type: object
      properties:
        id:
          type: string
        journalNo:
          type: string
          example: OUT-000123
        direction:
          type: string
          enum:
            - inbound
            - outbound
        type:
          type: string
          example: pain.001
        bankKey:
          type: string
        environment:
          type: string
          enum:
            - test
            - production
        status:
          type: string
          example: converted
        summary:
          type: string
        transactionCount:
          type: integer
        totals:
          type: array
          items:
            type: object
            properties:
              currency:
                type: string
              amount:
                type: string
        createdAt:
          type: string
          format: date-time
    BankConnection:
      type: object
      properties:
        id:
          type: string
        bankKey:
          $ref: '#/components/schemas/BankKey'
        bankName:
          type: string
          example: Nordea Danmark
        environment:
          type: string
          enum:
            - test
            - production
        channelType:
          type: string
          enum:
            - sftp
            - webservice
            - ebics
        active:
          type: boolean
        steps:
          type: object
          description: Setup wizard progress flags.
          properties:
            introEmailSent:
              type: boolean
            pgpGenerated:
              type: boolean
            bankPublicKeyAdded:
              type: boolean
            serverInfoAdded:
              type: boolean
            sftpUserAdded:
              type: boolean
              description: 'true once both the SFTP username and SSH key pair have been set. These are a single combined operation via POST /connections/{id}/sftp-user.'
            approversAssigned:
              type: boolean
            certsIssued:
              type: boolean
            bankCertAdded:
              type: boolean
            ebicsKeysGenerated:
              type: boolean
            ebicsBankKeysFetched:
              type: boolean
            wsConfigAdded:
              type: boolean
        sshPublicKey:
          type: string
          description: Public half of the SSH key (for SFTP).
        pgpPublicKey:
          type: string
          description: Public half of our PGP key (for SFTP).
        approverUserIds:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
    ConnectionWithReadiness:
      type: object
      properties:
        connection:
          $ref: '#/components/schemas/BankConnection'
        readiness:
          type: object
          description: 'Current go-live checklist: which prerequisites are met and which are missing.'
          properties:
            ready:
              type: boolean
            missing:
              type: array
              items:
                type: string
            score:
              type: integer
              minimum: 0
              maximum: 100
    ValidationIssue:
      type: object
      required:
        - code
        - level
        - message
      properties:
        code:
          type: string
          example: BANK_BIC_REQUIRED
        level:
          type: string
          enum:
            - generic
            - bank
            - payment-type
        severity:
          type: string
          enum:
            - error
            - warning
          description: Defaults to "error" when absent. "warning" issues don't block the payment.
        message:
          type: string
          example: Bank "nordea-dk" requires a BIC for the debtor.
        path:
          type: string
          example: 'payments[0].debtor.agent.bic'
        bank:
          type: string
          example: nordea-dk
        paymentType:
          type: string
          example: kid
    ValidationOutcome:
      type: object
      required:
        - valid
        - issues
      properties:
        valid:
          type: boolean
        issues:
          type: array
          items:
            $ref: '#/components/schemas/ValidationIssue'
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: 'Stable, snake_case, non-localised error identifier. Branch on this.'
              example: validation_failed
            message:
              type: string
              description: Human-readable text. May change; do not branch on it.
            details:
              type: object
              description: 'Optional structured context. For validation errors, contains `issues`.'
              properties:
                issues:
                  type: array
                  items:
                    $ref: '#/components/schemas/ValidationIssue'
    WebhookDelivery:
      type: object
      description: One webhook delivery attempt-record (reliability phase 1).
      properties:
        id:
          type: string
          example: whd_…
        webhookId:
          type: string
          example: wh_…
        platformId:
          type: string
        companyId:
          type: string
        eventType:
          type: string
          example: payment.sent
        payload:
          type: object
          description: The original event envelope (type/message/at/data).
        status:
          type: string
          enum:
            - delivered
            - failed
            - pending
        httpStatus:
          type: integer
          nullable: true
        lastError:
          type: string
          nullable: true
        attempts:
          type: integer
        deliveredAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    OrganisationId:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          maxLength: 35
          example: DK12345678
        scheme:
          type: string
          maxLength: 35
          example: CUST
        issuer:
          type: string
          maxLength: 35
    Account:
      type: object
      properties:
        iban:
          type: string
          example: DK5000400440116243
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: EUR
        other:
          type: object
          required:
            - id
            - scheme
          properties:
            id:
              type: string
              maxLength: 34
              example: 5050-1055
            scheme:
              type: string
              maxLength: 35
              example: BGNR
      description: Exactly one of `iban` or `other` must be provided.
    Agent:
      type: object
      properties:
        bic:
          type: string
          example: DEUTDEFF
        clearing:
          type: object
          required:
            - system
            - memberId
          properties:
            system:
              type: string
              maxLength: 35
              example: GBDSC
            memberId:
              type: string
              maxLength: 35
              example: '401234'
        name:
          type: string
          maxLength: 140
    Party:
      type: object
      required:
        - name
        - account
      properties:
        name:
          type: string
          maxLength: 140
          example: Acme Corp ApS
        country:
          type: string
          minLength: 2
          maxLength: 2
          example: DK
        account:
          type: object
          properties:
            iban:
              type: string
              example: DK5000400440116243
            currency:
              type: string
              minLength: 3
              maxLength: 3
              example: EUR
            other:
              type: object
              required:
                - id
                - scheme
              properties:
                id:
                  type: string
                  maxLength: 34
                  example: 5050-1055
                scheme:
                  type: string
                  maxLength: 35
                  example: BGNR
          description: Exactly one of `iban` or `other` must be provided.
        agent:
          type: object
          properties:
            bic:
              type: string
              example: DEUTDEFF
            clearing:
              type: object
              required:
                - system
                - memberId
              properties:
                system:
                  type: string
                  maxLength: 35
                  example: GBDSC
                memberId:
                  type: string
                  maxLength: 35
                  example: '401234'
            name:
              type: string
              maxLength: 140
        organisationId:
          type: object
          required:
            - id
          properties:
            id:
              type: string
              maxLength: 35
              example: DK12345678
            scheme:
              type: string
              maxLength: 35
              example: CUST
            issuer:
              type: string
              maxLength: 35
    CreditorReference:
      type: object
      required:
        - value
      properties:
        type:
          type: string
          enum:
            - scor
            - kid
            - ocr
            - fik
        value:
          type: string
          maxLength: 35
          example: RF18539007547034
    ReferredDocument:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - invoice
            - credit-note
        number:
          type: string
          maxLength: 35
          example: INV-2026-588
        date:
          type: string
          format: date
          example: 2026-05-01
        amount:
          type: string
          example: '1600.00'
          pattern: '^\d{1,15}\.\d{2}$'
    Remittance:
      type: object
      properties:
        unstructured:
          type: array
          maxItems: 10
          items:
            type: string
            maxLength: 140
          description: Free-text advice lines (Ustrd). Auto-derived from documents if omitted.
        documents:
          type: array
          items:
            $ref: '#/components/schemas/ReferredDocument'
          description: 'Invoices and credit notes: each emitted as a structured Strd block.'
        creditorReference:
          type: object
          required:
            - value
          properties:
            type:
              type: string
              enum:
                - scor
                - kid
                - ocr
                - fik
            value:
              type: string
              maxLength: 35
              example: RF18539007547034
        additionalInfo:
          type: array
          items:
            type: string
            maxLength: 35
    Transaction:
      type: object
      required:
        - endToEndId
        - amount
        - currency
        - creditor
      properties:
        endToEndId:
          type: string
          maxLength: 35
          example: INV-2026-588
        instructionId:
          type: string
          maxLength: 35
        amount:
          type: string
          example: '1500.00'
          pattern: '^\d{1,15}\.\d{2}$'
        currency:
          type: string
          minLength: 3
          maxLength: 3
          example: EUR
        creditor:
          $ref: '#/components/schemas/Party'
        remittance:
          type: object
          properties:
            unstructured:
              type: array
              maxItems: 10
              items:
                type: string
                maxLength: 140
              description: Free-text advice lines (Ustrd). Auto-derived from documents if omitted.
            documents:
              type: array
              items:
                $ref: '#/components/schemas/ReferredDocument'
              description: 'Invoices and credit notes: each emitted as a structured Strd block.'
            creditorReference:
              type: object
              required:
                - value
              properties:
                type:
                  type: string
                  enum:
                    - scor
                    - kid
                    - ocr
                    - fik
                value:
                  type: string
                  maxLength: 35
                  example: RF18539007547034
            additionalInfo:
              type: array
              items:
                type: string
                maxLength: 35
    Payment:
      type: object
      required:
        - paymentId
        - executionDate
        - debtor
        - transactions
      properties:
        paymentId:
          type: string
          maxLength: 35
          example: PMT-0001
        paymentType:
          type: string
          enum:
            - sepa
            - sepa-instant
            - domestic
            - international
            - split
            - fik
            - kid
            - bankgiro
            - plusgiro
            - bacs
            - chaps
            - faster-payment
            - ach
            - wire
            - ch-domestic
            - ch-qr
            - eft
            - ca-wire
            - br-domestic
            - boleto
            - pix
          example: sepa
        executionDate:
          type: string
          format: date
          example: 2026-06-04
        debtor:
          $ref: '#/components/schemas/Party'
        transactions:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Transaction'
    PaymentInstruction:
      type: object
      required:
        - messageId
        - initiatingParty
        - payments
      properties:
        messageId:
          type: string
          maxLength: 35
          example: MSG-20260601-0001
        creationDateTime:
          type: string
          format: date-time
          example: 2026-06-01T10:30:00Z
        initiatingParty:
          type: object
          required:
            - name
          properties:
            name:
              type: string
              maxLength: 140
              example: Acme Corp ApS
            organisationId:
              type: object
              required:
                - id
              properties:
                id:
                  type: string
                  maxLength: 35
                  example: DK12345678
                scheme:
                  type: string
                  maxLength: 35
                  example: CUST
                issuer:
                  type: string
                  maxLength: 35
        payments:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/Payment'
    PaymentTypeEntry:
      type: object
      required:
        - code
        - label
        - description
        - requires
        - channel
      properties:
        code:
          type: string
          example: sepa
        label:
          type: string
          example: SEPA Credit Transfer
        description:
          type: string
        requires:
          type: array
          items:
            type: string
        channel:
          type: string
          example: SEPA / TARGET2
        isoEncoding:
          type: object
          properties:
            serviceLevel:
              type: string
              example: SEPA
            localInstrument:
              type: string
              example: INST
            categoryPurpose:
              type: string
              example: TAXS
    BankProfile:
      type: object
      required:
        - key
        - bankName
        - countryCode
        - painVersion
        - paymentTypes
      properties:
        key:
          $ref: '#/components/schemas/BankKey'
        bankName:
          type: string
          example: Nordea Danmark
        countryCode:
          type: string
          example: DK
        painVersion:
          type: string
          enum:
            - pain.001.001.03
            - pain.001.001.09
        bankBic:
          type: string
          example: NDEADKKK
        paymentTypes:
          type: array
          items:
            type: object
            required:
              - code
              - label
              - description
              - requires
              - channel
            properties:
              code:
                type: string
                example: sepa
              label:
                type: string
                example: SEPA Credit Transfer
              description:
                type: string
              requires:
                type: array
                items:
                  type: string
              channel:
                type: string
                example: SEPA / TARGET2
              isoEncoding:
                type: object
                properties:
                  serviceLevel:
                    type: string
                    example: SEPA
                  localInstrument:
                    type: string
                    example: INST
                  categoryPurpose:
                    type: string
                    example: TAXS
x-demo-api-key: demo_sandbox_public_key_readonly