{
  "openapi": "3.1.0",
  "info": {
    "title": "BankConnector API",
    "version": "0.1.0",
    "description": "## Quick start\n\nBankConnector converts canonical JSON payments to bank-specific ISO 20022 files and\nnormalises incoming bank statements into a single format. You host it; you own the data.\n\n### Step 1: Get your API key\n\nGenerate a key in the Workspace admin **API Keys** section, or via the API:\n\n```http\nPOST /platforms/{platformId}/api-keys\n{ \"label\": \"My ERP integration\" }\n```\n\nThe raw key is shown once in the 201 response: store it securely. Pass it as the\n`X-API-Key` header on every subsequent request.\n\n### Step 2: Create a company\n\nEach legal entity that makes payments is a \"company\". This call requires a **session**\n(workspace-admin login), not an API key:\n\n```http\nPOST /platforms/{platformId}/companies\nAuthorization: session cookie (workspace admin)\n{ \"name\": \"Acme Payments Ltd\" }\n→ 201 { \"company\": { \"id\": \"comp_…\", \"name\": \"Acme Payments Ltd\" } }\n```\n\n### Step 3: Add users\n\nCreate users in your company via the API or the web app. Each user must have at least one\nrole: `admin`, `approver`, and/or `viewer`. Users who will approve payments need the\n`approver` role.\n\n```http\nPOST /users\n{ \"platformId\": \"…\", \"companyId\": \"…\", \"email\": \"approver@acme.com\", \"name\": \"Alice\", \"roles\": [\"approver\"] }\n```\n\n- **With `password`:** user is created immediately (active).\n- **Without `password`:** an invite email is sent. The user clicks the link and sets their\n  own password via `POST /auth/set-password`.\n\nAt least one approver must exist before a connection can go live.\n\n### Step 4: Connect to a bank\n\n> **Platform-admin note:** If you authenticated as a platform admin (step 1–2 above) and\n> now want to make company-scoped calls (connections, payments, etc.), you must first select\n> a company context. Without it you will receive: *\"select a company context first.\"*\n> ```http\n> POST /auth/company-context\n> { \"companyId\": \"comp_…\" }\n> ```\n> After this call your session acts as an admin of that company. Use\n> `DELETE /auth/company-context` to return to workspace-admin scope.\n\n```http\nGET /profiles             → list all supported banks + payment types\nPOST /connections         → set up an EBICS / SFTP / web-service channel\n```\n\n**Approvers and approval policy** are separate concepts. The policy (under\n`POST /approval-policies`) defines the rule: how many approvers are required and any\namount thresholds. Approvers are assigned per-connection via\n`POST /connections/{id}/approvers` using user IDs. Both must be configured; creating a\npolicy alone is not sufficient for a connection to reach go-live.\n\n### Step 5: Submit a payment\n\n```http\nPOST /journal/payments\n{\n  \"platformId\": \"plat_…\", \"companyId\": \"comp_…\", \"bankKey\": \"nordea-dk\",\n  \"payment\": {\n    \"messageId\": \"MSG-20260619-0001\",\n    \"initiatingParty\": { \"name\": \"Acme Corp\", \"organisationId\": { \"id\": \"ACME-1\", \"scheme\": \"CUST\" } },\n    \"payments\": [{\n      \"paymentId\": \"PMT-1\", \"paymentType\": \"sepa\", \"executionDate\": \"2026-06-20\",\n      \"debtor\": { \"name\": \"Acme Corp\", \"country\": \"DK\", \"account\": { \"iban\": \"DK5000400440116243\", \"currency\": \"EUR\" }, \"agent\": { \"bic\": \"NDEADKKK\" } },\n      \"transactions\": [{ \"endToEndId\": \"E2E-1\", \"amount\": \"1000.00\", \"currency\": \"EUR\",\n        \"creditor\": { \"name\": \"Supplier GmbH\", \"account\": { \"iban\": \"DE89370400440532013000\" }, \"agent\": { \"bic\": \"DEUTDEFF\" } } }]\n    }]\n  }\n}\n```\n\nThe `payment` object IS the canonical instruction (amounts are exact decimal **strings**); on this route it\nis WRAPPED under `payment` alongside the tenant scope + `bankKey`. Pass an optional `\"environment\": \"test\"`\nto dispatch over a test connection (default `production`). NOTE the envelope difference: `POST /validate/{bank}`\nand `POST /convert/{bank}` take that SAME canonical object as the raw top-level body (no `payment` wrapper, no\nplatformId/companyId). See CANONICAL_INPUT.md for the full payment shape.\n\n### Step 6: Read incoming data\n\n```http\nPOST /inbound/payment-status   → import a pain.002 status report\nPOST /inbound/statement        → import a camt.053 / 052 / 054 statement\nGET  /journal/reconciliation   → matched payments and bank confirmations\n```\n\n---\n\n> **Sandbox vs production:** BankConnector has no separate sandbox host. Use\n> `environment: \"TEST\"` on a connection to point it at your bank's test environment.\n> `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 \\\n  -H 'X-API-Key: YOUR_KEY'"
          },
          {
            "lang": "JavaScript",
            "label": "Fetch",
            "source": "const res = await fetch(\"https://your-host/profiles\", {\n  method: \"GET\",\n  headers: {\n    \"X-API-Key\": \"YOUR_KEY\",\n  },\n});\nconst data = await res.json();"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.request(\n  \"GET\", \"https://your-host/profiles\",\n  headers={\"X-API-Key\": \"YOUR_KEY\"},\n)\nresp.raise_for_status()\ndata = 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:\n\n- **With `password`:** creates the user immediately (active). Use for programmatic provisioning.\n- **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.\n\nThe 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 \\\n  -H 'X-API-Key: YOUR_KEY' \\\n  -H 'Content-Type: application/json' \\\n  -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\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"YOUR_KEY\",\n    \"Content-Type\": \"application/json\",\n  },\n  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\"}}}]}]}),\n});\nconst data = await res.json();"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.request(\n  \"POST\", \"https://your-host/validate/nordea-dk\",\n  headers={\"X-API-Key\": \"YOUR_KEY\"},\n  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\"}}}]}]},\n)\nresp.raise_for_status()\ndata = 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 \\\n  -H 'X-API-Key: YOUR_KEY' \\\n  -H 'Content-Type: application/json' \\\n  -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\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"YOUR_KEY\",\n    \"Content-Type\": \"application/json\",\n  },\n  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\"}}}]}]}),\n});\nconst data = await res.text()  // pain.001 XML;"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.request(\n  \"POST\", \"https://your-host/convert/nordea-dk\",\n  headers={\"X-API-Key\": \"YOUR_KEY\"},\n  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\"}}}]}]},\n)\nresp.raise_for_status()\ndata = 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 \\\n  -H 'X-API-Key: YOUR_KEY' \\\n  -H 'Content-Type: application/json' \\\n  -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\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": \"YOUR_KEY\",\n    \"Content-Type\": \"application/json\",\n  },\n  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\"}}}]}]}}),\n});\nconst data = await res.json();"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.request(\n  \"POST\", \"https://your-host/journal/payments\",\n  headers={\"X-API-Key\": \"YOUR_KEY\"},\n  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\"}}}]}]}},\n)\nresp.raise_for_status()\ndata = 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 \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"subdomain\":\"demo\",\"email\":\"you@acme.com\",\"password\":\"…\"}'"
          },
          {
            "lang": "JavaScript",
            "label": "Fetch",
            "source": "const res = await fetch(\"https://your-host/auth/login\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\"subdomain\":\"demo\",\"email\":\"you@acme.com\",\"password\":\"…\"}),\n});\nconst data = await res.json();"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.request(\n  \"POST\", \"https://your-host/auth/login\",\n  json={\"subdomain\":\"demo\",\"email\":\"you@acme.com\",\"password\":\"…\"},\n)\nresp.raise_for_status()\ndata = 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"
}