{
  "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 one format. Try the whole flow in the hosted\nsandbox at **https://sandbox.bankconnector.com** — no signup, and no real money moves.\n\n### Step 1: Get a sandbox API key\n\nOne unauthenticated call provisions a complete, isolated sandbox — your own platform, a\ncompany, an admin user, an API key, and an active demo bank connection:\n\n```http\nPOST /sandbox/provision\n{ \"bankKey\": \"danske-dk\" }\n→ 201 { \"apiKey\": \"key_…\", \"companyId\": \"comp_…\", \"baseUrl\": \"https://sandbox.bankconnector.com\", … }\n```\n\nThe raw key is shown ONCE — store it, and send it as the `X-API-Key` header on every\nrequest. **Your key identifies your platform**, so on every data-plane call you pass only\n`companyId` — you never send `platformId`.\n\n### Step 2: Submit a payment\n\n```http\nPOST /journal/payments\nX-API-Key: key_…\nIdempotency-Key: 8f2c…   (unique per payment; mandatory for host-to-host)\n{\n  \"companyId\": \"comp_…\", \"bankKey\": \"danske-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\nthis route it is WRAPPED under `payment` alongside `companyId` + `bankKey`. The flat 201 response\nincludes `id`, `journalNo`, `status`, the generated `xml`, and `deliveryMode`. A retry with the\nsame `Idempotency-Key` + body replays that 201 with an `Idempotency-Replayed: true` header. NOTE the\nenvelope difference: `POST /validate/{bank}` and `POST /convert/{bank}` take that SAME canonical object\nas the raw top-level body (no `payment` wrapper, no companyId). See CANONICAL_INPUT.md for the full shape.\n\n### Step 3: Read results\n\n```http\nGET  /journal/reconciliation              → matched payments + bank confirmations (cleared / rejected / pending)\nGET  /journal/list?unretrievedOnly=true   → poll for documents you have not collected yet\nPOST /inbound/payment-status              → import a pain.002 status report\nPOST /inbound/statement                   → import a camt.053 / 052 / 054 statement\n```\n\n---\n\n> **Sandbox vs production:** the sandbox at https://sandbox.bankconnector.com is fully isolated —\n> the key, data and demo bank are sandbox-only and no real money can move. Going live (real bank\n> connections + approvals) is a separate step; the same request shapes apply.",
    "contact": {
      "email": "johan@laymanadvisory.com"
    }
  },
  "servers": [
    {
      "url": "https://sandbox.bankconnector.com",
      "description": "Sandbox — the default target for every \"Try it\" call on this site, and where you should do all integration work first. The demo bank channel never moves real money by construction; a real bank connection here is safe from real sends only if your company is provisioned sandboxOnly — confirm that with your operator rather than assuming it from the host alone."
    },
    {
      "url": "https://{company}.bankconnector.com",
      "description": "Production — your company's branded subdomain.",
      "variables": {
        "company": {
          "default": "your-company",
          "description": "Your company's subdomain, from your BankConnector operator."
        }
      }
    }
  ],
  "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": {
    "/sandbox/provision": {
      "post": {
        "tags": [
          "Getting started"
        ],
        "summary": "Create a sandbox in one call (no signup)",
        "description": "Self-serve onboarding — **sandbox only**. With NO authentication, provisions a complete, isolated sandbox: your own platform, a company, an admin user, an **API key**, and an active demo bank connection. Everything created is sandbox-only (no real money can move). Returns the raw API key (shown once) plus a ready-to-run first payment. Your key identifies the platform, so on later requests you pass only `companyId`. Rate-limited per IP; unused sandboxes are removed after 30 days.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bankKey": {
                    "type": "string",
                    "description": "Demo bank to connect (default `danske-dk`). See GET /profiles.",
                    "example": "danske-dk"
                  },
                  "companyName": {
                    "type": "string",
                    "description": "Optional name for the created company.",
                    "example": "Acme Sandbox"
                  },
                  "label": {
                    "type": "string",
                    "description": "Optional label for the API key.",
                    "example": "my-integration"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sandbox provisioned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "type": "string",
                      "description": "Raw API key — shown ONCE. Send as the X-API-Key header.",
                      "example": "key_1a2b…"
                    },
                    "companyId": {
                      "type": "string",
                      "example": "comp_…"
                    },
                    "platformId": {
                      "type": "string",
                      "example": "plat_…"
                    },
                    "baseUrl": {
                      "type": "string",
                      "example": "https://sandbox.bankconnector.com"
                    },
                    "bank": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "country": {
                          "type": "string"
                        }
                      }
                    },
                    "firstPayment": {
                      "type": "object",
                      "description": "A ready-to-run POST /journal/payments request (headers + body)."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not a deployed sandbox (this endpoint does not exist in production)."
          },
          "429": {
            "description": "Rate limited — too many sandboxes from this IP recently."
          }
        }
      }
    },
    "/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"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/ready": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Readiness check",
        "description": "Readiness probe for orchestrators (distinct from /health liveness): 200 when the app can serve — the database is reachable AND migrated when one is configured — else 503 so the load balancer keeps traffic away. Public, like /health.",
        "responses": {
          "200": {
            "description": "Ready",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ready": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Not ready (database unreachable or schema not migrated)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ready": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/fx/rates": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Indicative FX reference rates",
        "description": "Global (non-tenant) indicative EUR-base FX rates for converting balances to a common currency. Read-only, kept fresh by a daily job (seeded so it works offline). Shape: `{ base, rates, asOf }`.",
        "responses": {
          "200": {
            "description": "Cached EUR-base rates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "base": {
                      "type": "string",
                      "example": "EUR"
                    },
                    "rates": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "number"
                      }
                    },
                    "asOf": {
                      "type": "string",
                      "nullable": true
                    }
                  },
                  "required": [
                    "base",
                    "rates"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/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
      }
    },
    "/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
      }
    },
    "/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.\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)"
                    }
                  }
                }
              }
            }
          },
          "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.",
        "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 /approvals/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. 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"
          }
        ]
      }
    },
    "/convert-async/{bankKey}": {
      "post": {
        "tags": [
          "Conversion"
        ],
        "summary": "Enqueue a background conversion (returns a job id)",
        "description": "Non-blocking sibling of POST /convert/{bankKey}: validates the input, enqueues a background conversion, and returns 202 with a `jobId` immediately. Poll GET /convert-jobs/{id} for the result. `?validate=true` is NOT supported here (the async worker can't run the XSD) — use the synchronous /convert/{bankKey}?validate=true instead.",
        "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": {
          "202": {
            "description": "Conversion enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jobId": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "bankKey": {
                      "type": "string"
                    },
                    "autoPaymentType": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "jobId",
                    "status",
                    "bankKey"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body, ?validate=true (unsupported on the async path), or unsupported painVersion",
            "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"
                }
              }
            }
          }
        }
      }
    },
    "/convert-jobs/{id}": {
      "get": {
        "tags": [
          "Conversion"
        ],
        "summary": "Poll a background conversion job",
        "description": "Returns the status of a job created by POST /convert-async/{bankKey}. While pending/running the body carries the job status; once succeeded it returns the converted pain.001 XML. A job is readable only by the tenant that created it (others get 404 — never a cross-tenant leak).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "job_abc123"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status, or the converted XML once the job has succeeded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Conversion job not found (or not owned by the caller)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/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 routes it through approval + delivery. Pass only `companyId` (+ `bankKey` + `payment`) — **platformId is inferred from your API key; do not send it.** **Idempotency-Key is MANDATORY for host-to-host** (a missing key returns 400 `idempotency_key_required`, unless the `payment.messageId` is supplied and used as the fallback dedupe key). Send a unique key per logical payment so a network-timeout retry can never double-submit. A retry with the SAME key + SAME body replays the original **201** response and carries the header `Idempotency-Replayed: true`; the same key with a DIFFERENT body → 409. 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. MANDATORY for host-to-host (else 400 `idempotency_key_required`, unless `payment.messageId` is provided). Same key + same body replays the original 201 (with `Idempotency-Replayed: true`); same key + different body → 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JournalPaymentRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment recorded + journaled. A replay of a prior idempotent submit ALSO returns 201, additionally carrying the response header `Idempotency-Replayed: true`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalDocument"
                },
                "example": {
                  "id": "doc_9f3a",
                  "journalNo": "OUT-000123",
                  "platformId": "plat_123",
                  "companyId": "comp_123",
                  "bankKey": "nordea-dk",
                  "environment": "production",
                  "direction": "outbound",
                  "type": "pain.001",
                  "status": "converted",
                  "summary": "SEPA — 1 payment",
                  "transactionCount": 1,
                  "totals": [
                    {
                      "currency": "EUR",
                      "amount": "1000.00"
                    }
                  ],
                  "xml": "<?xml version=\"1.0\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.09\">…</Document>",
                  "deliveryMode": "host-to-host",
                  "createdAt": "2026-06-20T10:31:00Z",
                  "delivery": {
                    "status": "queued"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_required` — no Idempotency-Key (header or body) and no `payment.messageId` on a host-to-host submit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "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 '{\"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({\"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={\"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. Use it to inspect the exact bank file a payment would produce before submitting it. 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": {
                  "companyId": {
                    "type": "string",
                    "description": "Your company id. platformId is inferred from your API key — do not send it."
                  },
                  "bankKey": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string",
                    "description": "Raw file contents"
                  }
                },
                "required": [
                  "companyId",
                  "content"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File ingested + normalised",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalDocument"
                },
                "example": {
                  "id": "doc_a1b2",
                  "journalNo": "IN-000042",
                  "companyId": "comp_123",
                  "bankKey": "nordea-dk",
                  "environment": "production",
                  "direction": "inbound",
                  "type": "camt.053",
                  "status": "imported",
                  "summary": "Statement — 3 entries",
                  "transactionCount": 3,
                  "totals": [
                    {
                      "currency": "EUR",
                      "amount": "4200.00"
                    }
                  ],
                  "createdAt": "2026-06-20T08:15:00Z"
                }
              }
            }
          },
          "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": "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": {
                  "companyId": {
                    "type": "string",
                    "description": "Your company id. platformId is inferred from your API key — do not send it."
                  },
                  "journalNos": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "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": "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": "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": "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": "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 via `POST /approvals/changes/{changeId}/submit` and have a second Admin co-sign via `POST /approvals/changes/{changeId}/cosign`. 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 + collect surface",
        "description": "Tenant-scoped, newest-first list of journal documents with a stable opaque `cursor` for forward pagination. Metadata only (no decrypted payload). The flagship ERP polling pattern: pass `unretrievedOnly=true` to fetch only documents you have not collected yet, then mark them collected via POST /journal/export. `total` counts all documents; `unretrieved` counts how many are still uncollected. Each item carries `collected` (boolean) and `collectedAt` so a poller can track exactly what it has pulled. platformId is inferred from your API key — pass only `companyId`.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionToken": []
          }
        ],
        "parameters": [
          {
            "name": "companyId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your company id. (platformId is inferred from your API key — do not send it.)"
          },
          {
            "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": 1000,
              "default": 500
            },
            "description": "Page size — default 500, max 1000 (larger than the interactive /journal/documents feed, for bulk ERP pulls)."
          },
          {
            "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": "unretrievedOnly",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When `true`, returns ONLY documents not yet collected (the standard poll-then-collect loop)."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of documents + counts + nextCursor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer",
                      "description": "Total documents matching the filter (not just this page)."
                    },
                    "unretrieved": {
                      "type": "integer",
                      "description": "How many matching documents are still uncollected."
                    },
                    "nextCursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Pass back as `cursor` to fetch the next page; null when there are no more."
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "journalNo": {
                            "type": "string",
                            "example": "OUT-000123"
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "inbound",
                              "outbound"
                            ],
                            "example": "outbound"
                          },
                          "type": {
                            "type": "string",
                            "example": "pain.001"
                          },
                          "sourceFormat": {
                            "type": "string",
                            "example": "pain.001.001.09"
                          },
                          "bankKey": {
                            "type": "string",
                            "example": "nordea-dk"
                          },
                          "summary": {
                            "type": "string",
                            "example": "SEPA — 1 payment"
                          },
                          "status": {
                            "type": "string",
                            "example": "converted"
                          },
                          "collected": {
                            "type": "boolean",
                            "description": "Whether this document has been collected (marked retrieved) yet."
                          },
                          "collectedAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "description": "When it was first collected (null if not yet)."
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "expiresAt": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "total",
                    "unretrieved",
                    "items"
                  ]
                },
                "example": {
                  "total": 42,
                  "unretrieved": 3,
                  "nextCursor": null,
                  "items": [
                    {
                      "journalNo": "OUT-000123",
                      "direction": "outbound",
                      "type": "pain.001",
                      "sourceFormat": "pain.001.001.09",
                      "bankKey": "nordea-dk",
                      "summary": "SEPA — 1 payment",
                      "status": "converted",
                      "collected": false,
                      "collectedAt": null,
                      "createdAt": "2026-06-20T10:31:00Z",
                      "expiresAt": null
                    }
                  ]
                }
              }
            }
          },
          "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": "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": "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": "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: a tally of your outbound payments grouped by payment type.",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionToken": []
          }
        ],
        "parameters": [
          {
            "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": "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": "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": [
                  "companyId",
                  "content"
                ],
                "properties": {
                  "companyId": {
                    "type": "string",
                    "description": "Your company id. platformId is inferred from your API key — do not send it."
                  },
                  "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": "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. The subscription field is `eventTypes` (an array of event names); a typo like `events` is rejected (400) rather than silently subscribing to everything. Omit `eventTypes` entirely to receive all events.",
        "security": [
          {
            "SessionToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platformId",
                  "companyId",
                  "url"
                ],
                "properties": {
                  "platformId": {
                    "type": "string"
                  },
                  "companyId": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "eventTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "description": "Event names to subscribe to, e.g. [\"payment.sent\", \"payment.rejected\"]. Omit to receive all events; an explicitly-empty array is rejected."
                  }
                }
              }
            }
          }
        },
        "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": "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 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": "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": "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": "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: 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": "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": "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": "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"
                }
              }
            }
          }
        }
      }
    },
    "/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": [
          "companyId",
          "bankKey",
          "payment"
        ],
        "description": "platformId is INFERRED from your API key — do not send it. Pass only `companyId`, `bankKey`, and the `payment` object.",
        "properties": {
          "companyId": {
            "type": "string",
            "description": "The company this payment belongs to. (platformId is inferred from your API key — do not send it.)"
          },
          "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"
          },
          "platformId": {
            "type": "string"
          },
          "companyId": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ],
            "example": "outbound",
            "description": "\"outbound\" for a pain.001 you submit; \"inbound\" for a bank file you receive (camt.05x / pain.002)."
          },
          "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"
                }
              }
            }
          },
          "xml": {
            "type": "string",
            "description": "The generated bank file (pain.001 XML). Returned by POST /journal/payments and single-document fetches; omitted from list summaries."
          },
          "deliveryMode": {
            "type": "string",
            "enum": [
              "host-to-host",
              "file-return"
            ],
            "description": "How an outbound payment is delivered."
          },
          "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"
            ],
            "example": "bank",
            "description": "Which validation tier raised the issue. A `BANK_*` code is level `bank`; a `GEN_*` code is `generic`; a payment-type rule is `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": "sepa"
          }
        }
      },
      "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.",
        "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"
}