{
  "basePath": "/v2",
  "definitions": {
    "AccountDeletionDescriptor": {
      "description": "Which resources DeleteAccount removed; `false` means the resource was already absent",
      "properties": {
        "Account": {
          "description": "The account record with all certificates was deleted by this call",
          "type": "boolean"
        },
        "AdminUser": {
          "description": "The *admin* mode Cognito user was deleted by this call",
          "type": "boolean"
        },
        "DataUser": {
          "description": "The *data* mode Cognito user was deleted by this call",
          "type": "boolean"
        }
      },
      "required": [
        "AdminUser",
        "DataUser",
        "Account"
      ],
      "type": "object"
    },
    "AccountDescriptor": {
      "example": {
        "AdminMode": "registered",
        "Certs": [],
        "DataMode": "unregistered",
        "Email": "user@example.com",
        "Export": "allowed",
        "Name": "Example User",
        "Phone": "+358401234567"
      },
      "properties": {
        "AccountType": {
          "description": "`integrator` for the API key owner account, `customer` for accounts registered under it",
          "enum": [
            "integrator",
            "customer"
          ],
          "type": "string"
        },
        "AdminMode": {
          "description": "`admin` mode status",
          "enum": [
            "registered",
            "unregistered"
          ],
          "type": "string"
        },
        "Certs": {
          "description": "Certificates directly owned by the account",
          "items": {
            "$ref": "#/definitions/CertDescriptor"
          },
          "type": "array"
        },
        "DataMode": {
          "description": "`data` mode status",
          "enum": [
            "registered",
            "unregistered"
          ],
          "type": "string"
        },
        "Email": {
          "description": "Email address as the account username",
          "type": "string"
        },
        "Export": {
          "description": "Status for certificate and private key export allowance. See ConfigCerts.",
          "enum": [
            "disabled",
            "allowed"
          ],
          "type": "string"
        },
        "LinkedTo": {
          "description": "Email of the account whose certificates this account uses through ShareCerts, or empty",
          "type": "string"
        },
        "Name": {
          "description": "Full name of registrant",
          "type": "string"
        },
        "Phone": {
          "description": "Phone number with country code and `+` in front",
          "type": "string"
        },
        "Retired": {
          "description": "Certificates retired with RetireCert or set aside by ISECure",
          "items": {
            "$ref": "#/definitions/RetiredCertDescriptor"
          },
          "type": "array"
        }
      },
      "required": [
        "Name",
        "Email",
        "Phone",
        "Export",
        "Certs"
      ],
      "type": "object"
    },
    "AccountUsageCollectionDescriptor": {
      "description": "Persistent status of collection for yesterday in Europe/Helsinki. Reading this status never starts a job.",
      "properties": {
        "Date": {
          "format": "date",
          "type": "string"
        },
        "FinishedAt": {
          "format": "date-time",
          "type": "string"
        },
        "StartedAt": {
          "format": "date-time",
          "type": "string"
        },
        "State": {
          "enum": [
            "not_started",
            "running",
            "completed",
            "failed",
            "interrupted",
            "recorded"
          ],
          "type": "string"
        }
      },
      "required": [
        "Date",
        "State"
      ],
      "type": "object"
    },
    "AccountUsageDayDescriptor": {
      "properties": {
        "Date": {
          "type": "string"
        },
        "Issues": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "UniqueAccounts": {
          "description": "Absent when no daily result exists.",
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "Date",
        "Issues"
      ],
      "type": "object"
    },
    "AccountUsageDescriptor": {
      "properties": {
        "Accounts": {
          "items": {
            "$ref": "#/definitions/AccountUsageUserDescriptor"
          },
          "type": "array"
        },
        "Basis": {
          "enum": [
            "observed-camt-accounts@1"
          ],
          "type": "string"
        },
        "CollectedAt": {
          "format": "date-time",
          "type": "string"
        },
        "Collection": {
          "$ref": "#/definitions/AccountUsageCollectionDescriptor"
        },
        "Daily": {
          "items": {
            "$ref": "#/definitions/AccountUsageDayDescriptor"
          },
          "type": "array"
        },
        "Issues": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "Licenses": {
          "$ref": "#/definitions/AccountUsageLicensesDescriptor"
        },
        "MissingDays": {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "Month": {
          "type": "string"
        },
        "Tenant": {
          "type": "string"
        },
        "Timezone": {
          "enum": [
            "Europe/Helsinki"
          ],
          "type": "string"
        },
        "UniqueAccounts": {
          "description": "Distinct observed accounts across all available days in the selected scope; absent when no results exist.",
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "Tenant",
        "Month",
        "Timezone",
        "Basis",
        "Accounts",
        "Daily",
        "MissingDays",
        "Issues"
      ],
      "type": "object"
    },
    "AccountUsageLicensesDescriptor": {
      "description": "Current user-license estimate for the requested tenant or user. Each non-retired user contributes max(1, monthly distinct own bank accounts); Admin/Data modes count together and retired users contribute zero. Tenant figures sum users. Provisional estimates use current membership, not a final invoice. Historical eligibility is unavailable until snapshots exist.",
      "properties": {
        "AsOf": {
          "description": "Time current eligibility was read; present only with an estimate.",
          "format": "date-time",
          "type": "string"
        },
        "Basis": {
          "enum": [
            "non-retired-user-own-accounts@1"
          ],
          "type": "string"
        },
        "BillableUsers": {
          "description": "Current non-retired user identities, independent of registered sign-in modes.",
          "minimum": 0,
          "type": "integer"
        },
        "Count": {
          "description": "Present only for provisional estimates. A known zero remains distinct from an unavailable estimate.",
          "maximum": 9007199254740991,
          "minimum": 0,
          "type": "integer"
        },
        "RetiredUsers": {
          "description": "Retained users with retired bank material and no remaining direct or authorized linked bank certificate.",
          "minimum": 0,
          "type": "integer"
        },
        "Scope": {
          "description": "Customers receive only user scope. Authorized owners/operators may receive tenant scope.",
          "enum": [
            "tenant",
            "user"
          ],
          "type": "string"
        },
        "State": {
          "enum": [
            "provisional",
            "unavailable",
            "historical_unavailable"
          ],
          "type": "string"
        }
      },
      "required": [
        "Basis",
        "Scope",
        "State"
      ],
      "type": "object"
    },
    "AccountUsageUserDescriptor": {
      "properties": {
        "Email": {
          "type": "string"
        },
        "UniqueAccounts": {
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "Email",
        "UniqueAccounts"
      ],
      "type": "object"
    },
    "AuditEventDescriptor": {
      "properties": {
        "action": {
          "description": "Account-management action or certificate.renew for automatic renewal attempts.",
          "type": "string"
        },
        "actorEmail": {
          "description": "Authenticated actor email when available.",
          "type": "string"
        },
        "actorId": {
          "description": "Authenticated actor subject when available.",
          "type": "string"
        },
        "actorMode": {
          "description": "Actor login mode, when known.",
          "type": "string"
        },
        "actorType": {
          "description": "operator, integrator, customer, authenticated, unauthenticated or system. Automatic renewal uses system; registration has no authenticated actor.",
          "type": "string"
        },
        "bank": {
          "description": "Public bank identifier.",
          "type": "string"
        },
        "bankResponseCode": {
          "description": "Bank response code for certificate.renew, preserved as text including leading zeroes. Absent when no valid bank response was received.",
          "maxLength": 16,
          "type": "string"
        },
        "bankResponseText": {
          "description": "Bounded, sanitized bank message for certificate.renew. May be omitted or redacted; render only as plain text. Never contains raw SOAP or internal exceptions.",
          "maxLength": 512,
          "type": "string"
        },
        "completedSteps": {
          "description": "Number of acknowledged mutation steps; missing resources on a deletion retry do not increment it.",
          "type": "integer"
        },
        "eventId": {
          "description": "Stable operation and sequence identifier; deduplicate publication retries by this value.",
          "type": "string"
        },
        "operationId": {
          "description": "Identifier for one invocation; retries have different operation IDs.",
          "type": "string"
        },
        "outcome": {
          "description": "started, accepted, completed, not_found, rejected, failed, partial_failure or skipped.",
          "type": "string"
        },
        "pendingStep": {
          "description": "Step without acknowledged completion; actual state may be unknown.",
          "type": "string"
        },
        "phase": {
          "description": "request, authorization, step or result.",
          "type": "string"
        },
        "reason": {
          "description": "Stable failure category without internal error details.",
          "type": "string"
        },
        "requestId": {
          "description": "Gateway request correlation identifier.",
          "type": "string"
        },
        "resourceId": {
          "description": "Retired certificate attribute or PGP public key identifier.",
          "type": "string"
        },
        "sequence": {
          "description": "Sequence within the operation.",
          "type": "integer"
        },
        "step": {
          "description": "Instrumented operation step.",
          "type": "string"
        },
        "targetEmail": {
          "description": "Affected account, or attempted target in a tenant-visible denial.",
          "type": "string"
        },
        "targetMode": {
          "description": "Registered account mode.",
          "type": "string"
        },
        "timestamp": {
          "description": "UTC event timestamp.",
          "format": "date-time",
          "type": "string"
        }
      },
      "required": [
        "eventId",
        "timestamp",
        "action",
        "phase",
        "outcome",
        "operationId",
        "sequence"
      ],
      "type": "object"
    },
    "BankConnectionCertificateDescriptor": {
      "description": "Minimal certificate status for bank connection discovery",
      "properties": {
        "Expires": {
          "description": "Certificate expiry reported by the certificate parser",
          "type": "string"
        },
        "Purpose": {
          "description": "How the certificate is used for this bank connection",
          "enum": [
            "signing",
            "encryption"
          ],
          "type": "string"
        }
      },
      "required": [
        "Purpose",
        "Expires"
      ],
      "type": "object"
    },
    "BankConnectionDescriptor": {
      "description": "A bank connection the authenticated account can use",
      "properties": {
        "Access": {
          "description": "Whether this account owns the bank certificates or uses certificates shared by its linked account",
          "enum": [
            "direct",
            "shared"
          ],
          "type": "string"
        },
        "Bank": {
          "description": "Public File Exchange API bank identifier",
          "type": "string"
        },
        "Certificates": {
          "description": "Minimal certificate status needed for connection discovery; certificate material and owner identity are omitted",
          "items": {
            "$ref": "#/definitions/BankConnectionCertificateDescriptor"
          },
          "type": "array"
        }
      },
      "required": [
        "Bank",
        "Access",
        "Certificates"
      ],
      "type": "object"
    },
    "CertDescriptor": {
      "example": {
        "CertName": "osuuspankki_customer_signing_cert",
        "Expires": "Oct 28 06:30:08 2017 GMT",
        "Issuer": "CUSTOMER TEST OP-Pohjola WS CA",
        "PEM": "-----BEGIN CERTIFICATE-----\nMIIF+DCCA+CgAwIBAgIDEaBdMA0GCS...x0t6Cnd5lyGKg=\n-----END CERTIFICATE-----",
        "Serial": "11A05D",
        "Subject": "1000038023"
      },
      "properties": {
        "CertName": {
          "description": "Certificate common name",
          "type": "string"
        },
        "Expires": {
          "description": "Date of expiry",
          "type": "string"
        },
        "Issuer": {
          "description": "Certificate issuer",
          "type": "string"
        },
        "PEM": {
          "description": "Certificate in PEM format",
          "type": "string"
        },
        "Serial": {
          "description": "Certificate serial number",
          "type": "string"
        },
        "Subject": {
          "description": "Certificate subject",
          "type": "string"
        }
      },
      "required": [
        "CertName",
        "Expires",
        "Subject",
        "Issuer",
        "Serial",
        "PEM"
      ],
      "type": "object"
    },
    "CertsAndKeys": {
      "properties": {
        "Certificate": {
          "description": "Certificate in PEM format",
          "type": "string"
        },
        "EncryptedPrivateKey": {
          "description": "PGP encrypted ascii armored private key",
          "type": "string"
        }
      },
      "required": [
        "Certificate",
        "EncryptedPrivateKey"
      ],
      "type": "object"
    },
    "ConfigCertsReq": {
      "example": {
        "Export": "disabled"
      },
      "properties": {
        "Export": {
          "description": "Set export to `disabled` to disallow certificate and private key pair exporting",
          "type": "string"
        }
      },
      "required": [
        "Export"
      ],
      "type": "object"
    },
    "DeleteAccountReq": {
      "example": {
        "Confirm": "user@example.com"
      },
      "properties": {
        "Confirm": {
          "description": "Must equal the `Email` path value exactly",
          "type": "string"
        }
      },
      "required": [
        "Confirm"
      ],
      "type": "object"
    },
    "DeleteAccountResp": {
      "example": {
        "Deleted": {
          "Account": true,
          "AdminUser": true,
          "DataUser": true
        },
        "Email": "user@example.com",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Deleted": {
          "$ref": "#/definitions/AccountDeletionDescriptor",
          "description": "Which resources were removed by this call; `false` means it was already absent"
        },
        "Email": {
          "description": "Deleted account email",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Deleted",
        "Email"
      ],
      "type": "object"
    },
    "DeleteKeyReq": {
      "example": {
        "PgpKeyId": "DBCBE671"
      },
      "properties": {
        "PgpKeyId": {
          "description": "8 chars hexadecimal PGP Key Id (see e.g. gpg --list-keys)",
          "type": "string"
        }
      },
      "required": [
        "PgpKeyId"
      ],
      "type": "object"
    },
    "DownloadFileResp": {
      "example": {
        "Content": "xxxxxxxx",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Content": {
          "description": "Downloaded file content as from bank (e.g. in Base64 form)",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Content"
      ],
      "type": "object"
    },
    "EnrollCertReq": {
      "example": {
        "Code": "8642603384107437",
        "Company": "ISECURE OY",
        "WsUserId": "..."
      },
      "properties": {
        "Code": {
          "description": "Full PIN code issued by the real bank (e.g. combined from SMS and letter). For the test-only `simulator`, generate a fresh synthetic code: 16–32 printable ASCII bytes without whitespace or control characters; no bank-issued PIN is needed.",
          "type": "string"
        },
        "Company": {
          "description": "For real banks, the company name as registered with the bank (e.g. full capital letters; see the bank contract). Real-bank enrollment does not compare this field with the account company name because formats differ between banks. For the test-only `simulator`, use the company value from the registered test account.",
          "type": "string"
        },
        "WsUserId": {
          "description": "*SEPA WebServices* channel user ID from the real-bank contract. For the test-only `simulator`, generate a synthetic identifier: 1–16 printable ASCII bytes without control characters.",
          "type": "string"
        }
      },
      "required": [
        "Code",
        "Company",
        "WsUserId"
      ],
      "type": "object"
    },
    "ErrorResponse": {
      "properties": {
        "RequestId": {
          "description": "Service side request id for problem tracing purposes",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "type": "object"
    },
    "ExportCertResp": {
      "example": {
        "CertsAndKeys": [],
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "CertsAndKeys": {
          "description": "List of certificate and encrypted private key pairs",
          "items": {
            "$ref": "#/definitions/CertsAndKeys"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "CertsAndKeys"
      ],
      "type": "object"
    },
    "FileDescriptor": {
      "example": {
        "FileReference": "227166",
        "FileTimestamp": "2017-05-20T03:36:21.148+03:00",
        "FileType": "VA",
        "ServiceId": "N/A",
        "Status": "NEW",
        "TargetId": "MLP"
      },
      "properties": {
        "FileReference": {
          "description": "File reference id, use e.g. when downloading the file",
          "type": "string"
        },
        "FileTimestamp": {
          "description": "Creation time stamp of the file from bank",
          "type": "string"
        },
        "FileType": {
          "description": "Bank specific file type",
          "type": "string"
        },
        "ServiceId": {
          "description": "Bank specific service id (e.g. bank account number)",
          "type": "string"
        },
        "Status": {
          "description": "File download status",
          "type": "string"
        },
        "TargetId": {
          "description": "Bank specific target id",
          "type": "string"
        }
      },
      "required": [
        "FileType",
        "Status",
        "FileTimestamp",
        "FileReference"
      ],
      "type": "object"
    },
    "ImportCertReq": {
      "example": {
        "Certificate": "...",
        "Company": "ISECURE OY",
        "EncCertificate": "...",
        "EncPrivatekey": "...",
        "PrivateKey": "...",
        "WsUserId": "..."
      },
      "properties": {
        "Certificate": {
          "description": "Certificate in PEM format",
          "type": "string"
        },
        "Company": {
          "description": "Company name as registered with bank (e.g. full capital letters without Oy, see contract)",
          "type": "string"
        },
        "EncCertificate": {
          "description": "Certificate in PEM format (encryption certificate for DanskeBank)",
          "type": "string"
        },
        "EncPrivatekey": {
          "description": "Private key in PEM format (encryption certificate for DanskeBank)",
          "type": "string"
        },
        "PrivateKey": {
          "description": "Private key in PEM format",
          "type": "string"
        },
        "WsUserId": {
          "description": "*SEPA WebServices* channel user id as in contract with bank",
          "type": "string"
        }
      },
      "required": [
        "Company",
        "WsUserId",
        "PrivateKey",
        "Certificate"
      ],
      "type": "object"
    },
    "InitLoginResp": {
      "example": {
        "Challenge": "9Ty4zrnJGqNH0i1+I0OTKHjTs03Ymd4tBH70FTiYNhA=|1494962070679|2646b71b-9b51-4d11-bf5e-cca5617bcfde",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Challenge": {
          "description": "Challenge copied from API response",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Challenge"
      ],
      "type": "object"
    },
    "InitRegisterResp": {
      "example": {
        "Challenge": "9Ty4zrnJGqNH0i1+I0OTKHjTs03Ymd4tBH70FTiYNhA=|1494962070679|2646b71b-9b51-4d11-bf5e-cca5617bcfde",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Challenge": {
          "description": "Challenge copied from API response",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Challenge"
      ],
      "type": "object"
    },
    "ListAccountUsageResp": {
      "example": {
        "ResponseCode": "..",
        "ResponseText": "..",
        "Usage": []
      },
      "properties": {
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "Usage": {
          "$ref": "#/definitions/AccountUsageDescriptor",
          "description": "Scoped daily and monthly observed-account usage and its coverage."
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Usage"
      ],
      "type": "object"
    },
    "ListAccountsResp": {
      "example": {
        "Accounts": [],
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Accounts": {
          "description": "List of accounts under the API key",
          "items": {
            "$ref": "#/definitions/AccountDescriptor"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Accounts"
      ],
      "type": "object"
    },
    "ListAuditEventsResp": {
      "example": {
        "Events": [],
        "NextToken": "opaque-cursor",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Events": {
          "description": "Sanitized audit events; may be empty even when NextToken is present.",
          "items": {
            "$ref": "#/definitions/AuditEventDescriptor"
          },
          "type": "array"
        },
        "NextToken": {
          "description": "Optional continuation token; absent when pagination is complete.",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Events"
      ],
      "type": "object"
    },
    "ListCertsResp": {
      "example": {
        "Certs": [],
        "Connections": [],
        "ResponseCode": "..",
        "ResponseText": "..",
        "Retired": []
      },
      "properties": {
        "Certs": {
          "description": "Legacy list of certificates directly owned by this account",
          "items": {
            "$ref": "#/definitions/CertDescriptor"
          },
          "type": "array"
        },
        "Connections": {
          "description": "Effective bank connections, including same-API-key certificates shared by the linked owner",
          "items": {
            "$ref": "#/definitions/BankConnectionDescriptor"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "Retired": {
          "description": "Certificates retired with RetireCert (or set aside by ISECure); no longer usable and removed with the account",
          "items": {
            "$ref": "#/definitions/RetiredCertDescriptor"
          },
          "type": "array"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Certs"
      ],
      "type": "object"
    },
    "ListFilesResp": {
      "example": {
        "FileDescriptors": [],
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "FileDescriptors": {
          "description": "List of downloadable files from bank",
          "items": {
            "$ref": "#/definitions/FileDescriptor"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "FileDescriptors"
      ],
      "type": "object"
    },
    "ListKeysResp": {
      "example": {
        "PgpKeys": [],
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "PgpKeys": {
          "description": "List of PGP keys in API",
          "items": {
            "$ref": "#/definitions/PgpKeyDescriptor"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "PgpKeys"
      ],
      "type": "object"
    },
    "LoginMFAReq": {
      "example": {
        "ChallengeName": "SMS_MFA",
        "Code": "123456",
        "Session": "...",
        "SetupTOTP": false
      },
      "properties": {
        "ChallengeName": {
          "description": "Echo the `ChallengeName` returned by the login response (`SMS_MFA` or `SOFTWARE_TOKEN_MFA`). Optional; defaults to `SMS_MFA` when omitted",
          "type": "string"
        },
        "Code": {
          "description": "MFA code: SMS code (when ChallengeName is `SMS_MFA`) or authenticator/TOTP code (when `SOFTWARE_TOKEN_MFA`)",
          "type": "string"
        },
        "Session": {
          "description": "Session token from login response",
          "type": "string"
        },
        "SetupTOTP": {
          "description": "Optional boolean. Set true on the current MFA submission to start TOTP enrollment after phone and email verification. An intermediate verification response contains no enrollment secret.",
          "type": "boolean"
        }
      },
      "required": [
        "Code",
        "Session"
      ],
      "type": "object"
    },
    "LoginMFAResp": {
      "example": {
        "Account": {
          "Entitlements": [],
          "Features": [],
          "Type": "customer"
        },
        "ApiKey": "example-integrator-api-key",
        "ExpiresIn": 3600,
        "IdToken": "example-id-token",
        "ResponseCode": "00",
        "ResponseText": "Login OK"
      },
      "properties": {
        "AccessToken": {
          "description": "Token for the indicated verification step; keep in memory. Not a business API IdToken. Also returned with TOTP enrollment details when requested after all verification checks pass.",
          "type": "string"
        },
        "Account": {
          "$ref": "#/definitions/SessionAccountDescriptor",
          "description": "Session account facts for the client\n- **Only** present with `IdToken`, i.e. on an authenticated session"
        },
        "ApiKey": {
          "description": "Integrator API key. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "string"
        },
        "ExpiresIn": {
          "description": "Session lifetime in seconds. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "integer"
        },
        "IdToken": {
          "description": "ID token for the Authorization header. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "string"
        },
        "OtpauthUri": {
          "description": "`otpauth://` URI for rendering the enrollment QR code\n- **Only** present when `SetupTOTP` was requested and phone and email verification have already completed",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "SecretCode": {
          "description": "TOTP shared secret\n- **Only** present when `SetupTOTP` was requested and phone and email verification have already completed",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText"
      ],
      "type": "object"
    },
    "LoginReq": {
      "example": {
        "ChResp": "ezwXceQ63fV9oWTSJBAE2Zq1Cw5tBIJe+7+Rl8jrgbk=|1475429754114|4017bda8-0a15-4154-a8b7-88069b05cb4e",
        "Encrypted": "..."
      },
      "properties": {
        "ChResp": {
          "description": "Challenge copied from API response",
          "type": "string"
        },
        "Encrypted": {
          "description": "RSA encrypted password and timestamp",
          "type": "string"
        }
      },
      "required": [
        "ChResp",
        "Encrypted"
      ],
      "type": "object"
    },
    "LoginResp": {
      "example": {
        "ChallengeName": "SMS_MFA",
        "ResponseCode": "00",
        "ResponseText": "Give SMS code",
        "Session": "example-mfa-session"
      },
      "properties": {
        "AccessToken": {
          "description": "Token for the indicated verification step; keep in memory. Not a business API IdToken.",
          "type": "string"
        },
        "Account": {
          "$ref": "#/definitions/SessionAccountDescriptor",
          "description": "Session account facts for the client\n- **Only** present with `IdToken`, i.e. on an authenticated session"
        },
        "ApiKey": {
          "description": "Integrator API key. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "string"
        },
        "ChallengeName": {
          "description": "Next MFA step: SMS_MFA, SOFTWARE_TOKEN_MFA, or SELECT_MFA_TYPE. Not present on an authenticated session.",
          "type": "string"
        },
        "ExpiresIn": {
          "description": "Session lifetime in seconds. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "integer"
        },
        "IdToken": {
          "description": "ID token for the Authorization header. Present only on an authenticated session, not on MFA or verification prompts.",
          "type": "string"
        },
        "MfaOptions": {
          "description": "Offered factors when ChallengeName is SELECT_MFA_TYPE, for example SMS_MFA and SOFTWARE_TOKEN_MFA.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "Session": {
          "description": "Current MFA challenge session. Pass to SelectMFA or LoginMFA as indicated; do not reuse after completing the challenge.",
          "type": "string"
        },
        "SmsDestination": {
          "description": "Masked SMS destination when returned with SELECT_MFA_TYPE; may be empty.",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText"
      ],
      "type": "object"
    },
    "PasswordResetReq": {
      "example": {
        "ChResp": "ezwXceQ63fV9oWTSJBAE2Zq1Cw5tBIJe+7+Rl8jrgbk=|1475429754114|4017bda8-0a15-4154-a8b7-88069b05cb4e",
        "Code": "123456",
        "Encrypted": "..."
      },
      "properties": {
        "ChResp": {
          "description": "Challenge copied from API response",
          "type": "string"
        },
        "Code": {
          "description": "Code from SMS",
          "type": "string"
        },
        "Encrypted": {
          "description": "RSA encrypted NEW password and timestamp",
          "type": "string"
        }
      },
      "required": [
        "Code",
        "ChResp",
        "Encrypted"
      ],
      "type": "object"
    },
    "PgpKeyDescriptor": {
      "example": {
        "PgpKeyId": "3A3A59B2",
        "PgpKeyPurpose": "authorize"
      },
      "properties": {
        "PgpKeyId": {
          "description": "Short version of a PGP Key id identifying the key, e.g. `3A3A59B2`",
          "type": "string"
        },
        "PgpKeyPurpose": {
          "description": "PGP Key purpose",
          "enum": [
            "export",
            "authorize"
          ],
          "type": "string"
        }
      },
      "required": [
        "PgpKeyId"
      ],
      "type": "object"
    },
    "ReadWorkspaceAuthorityReq": {
      "additionalProperties": false,
      "example": {
        "Challenge": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE"
      },
      "properties": {
        "Challenge": {
          "description": "Native-generated canonical base64url encoding of 32 random bytes, without padding.",
          "maxLength": 43,
          "minLength": 43,
          "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$",
          "type": "string"
        }
      },
      "required": [
        "Challenge"
      ],
      "type": "object"
    },
    "ReadWorkspaceAuthorityResp": {
      "additionalProperties": false,
      "example": {
        "Assertion": "header.payload.signature",
        "RequestId": "synthetic-request",
        "ResponseCode": "..",
        "ResponseText": ".."
      },
      "properties": {
        "Assertion": {
          "description": "RS256 compact JWS for native verification. At most 60 seconds of authority; this is never a provider credential.",
          "maxLength": 35000,
          "type": "string"
        },
        "RequestId": {
          "description": "Opaque request identifier for support.",
          "maxLength": 128,
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Assertion",
        "RequestId"
      ],
      "type": "object"
    },
    "RegisterReq": {
      "example": {
        "ApiKey": "example-integrator-api-key",
        "ChResp": "example-challenge-response",
        "Company": "Example Company Oy",
        "Encrypted": "example-encrypted-password",
        "Name": "Example User",
        "Phone": "+358401234567"
      },
      "properties": {
        "ApiKey": {
          "description": "Integrator API Key, or `0` if not already known (e.g. initial integrator registration)",
          "type": "string"
        },
        "ChResp": {
          "description": "Challenge copied from API response",
          "type": "string"
        },
        "Company": {
          "description": "Company name",
          "type": "string"
        },
        "Encrypted": {
          "description": "RSA encrypted password and timestamp",
          "type": "string"
        },
        "Name": {
          "description": "Full name of registrant",
          "type": "string"
        },
        "Phone": {
          "description": "Phone number with country code and `+` in front",
          "type": "string"
        }
      },
      "required": [
        "Name",
        "Phone",
        "Company",
        "ApiKey",
        "ChResp",
        "Encrypted"
      ],
      "type": "object"
    },
    "RegisterResp": {
      "example": {
        "ApiKey": "example-integrator-api-key",
        "ResponseCode": "00",
        "ResponseText": "Sign up successful. Verify phone number."
      },
      "properties": {
        "ApiKey": {
          "description": "Integrator API Key",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "ApiKey"
      ],
      "type": "object"
    },
    "Response": {
      "properties": {
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText"
      ],
      "type": "object"
    },
    "RetireCertResp": {
      "example": {
        "Bank": "nordea",
        "ResponseCode": "..",
        "ResponseText": "..",
        "RetiredCertNames": [
          "nordea.REMOVED.1a2b3c4d"
        ]
      },
      "properties": {
        "Bank": {
          "description": "Public bank name whose certificate was retired",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "RetiredCertNames": {
          "description": "Retired certificate record names, e.g. `nordea.REMOVED.1a2b3c4d`",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Bank",
        "RetiredCertNames"
      ],
      "type": "object"
    },
    "RetiredCertDescriptor": {
      "description": "A certificate retired from active use",
      "properties": {
        "Bank": {
          "description": "Public bank name the certificate belonged to",
          "type": "string"
        },
        "CertName": {
          "description": "Retired certificate record name, e.g. `nordea.REMOVED.1a2b3c4d_customer_signing_cert`",
          "type": "string"
        },
        "Expires": {
          "description": "Certificate expiry, or `unknown` when the retired material cannot be parsed",
          "type": "string"
        },
        "Purpose": {
          "enum": [
            "signing",
            "encryption"
          ],
          "type": "string"
        }
      },
      "required": [
        "CertName",
        "Bank",
        "Purpose",
        "Expires"
      ],
      "type": "object"
    },
    "SelectMFAReq": {
      "example": {
        "MfaType": "SOFTWARE_TOKEN_MFA",
        "Session": "..."
      },
      "properties": {
        "MfaType": {
          "description": "Chosen MFA factor: `SMS_MFA` or `SOFTWARE_TOKEN_MFA`",
          "enum": [
            "SMS_MFA",
            "SOFTWARE_TOKEN_MFA"
          ],
          "type": "string"
        },
        "Session": {
          "description": "Session token from the SELECT_MFA_TYPE login response",
          "type": "string"
        }
      },
      "required": [
        "MfaType",
        "Session"
      ],
      "type": "object"
    },
    "SelectMFAResp": {
      "example": {
        "ChallengeName": "SOFTWARE_TOKEN_MFA",
        "ResponseCode": "00",
        "ResponseText": "Give authenticator code",
        "Session": "example-selected-mfa-session"
      },
      "properties": {
        "ChallengeName": {
          "description": "Selected factor challenge: SMS_MFA or SOFTWARE_TOKEN_MFA. Echo this in LoginMFA.",
          "type": "string"
        },
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "Session": {
          "description": "New challenge session returned after selecting the factor. Use this session in LoginMFA.",
          "type": "string"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "Session",
        "ChallengeName"
      ],
      "type": "object"
    },
    "SessionAccountDescriptor": {
      "description": "Server-decided facts about the authenticated account for client presentation. Authorization is enforced by each operation regardless of these values.",
      "properties": {
        "Entitlements": {
          "description": "Stable ids of the tenant's active products, e.g. `bank-simulator`; absent when the lookup was unavailable at login",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "Features": {
          "description": "Per-user preview feature ids enabled by ISECure for this account; empty by default",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "Type": {
          "description": "`operator` for the ISECure owner account, `integrator` for an API key owner account, `customer` for an account registered under an API key",
          "enum": [
            "operator",
            "integrator",
            "customer"
          ],
          "type": "string"
        }
      },
      "required": [
        "Type",
        "Features"
      ],
      "type": "object"
    },
    "ShareCertsResp": {
      "example": {
        "ResponseCode": "..",
        "ResponseText": "..",
        "SharedFrom": [],
        "SharedTo": []
      },
      "properties": {
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "SharedFrom": {
          "description": "_ExtEmail_ account that this account shares certs from",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "SharedTo": {
          "description": "_ExtEmail_ account that this account shares certs for",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "SharedTo",
        "SharedFrom"
      ],
      "type": "object"
    },
    "UnshareCertsResp": {
      "example": {
        "ResponseCode": "..",
        "ResponseText": "..",
        "SharedFrom": [],
        "SharedTo": []
      },
      "properties": {
        "ResponseCode": {
          "description": "Two digit response code in string format",
          "type": "string"
        },
        "ResponseText": {
          "description": "Human readable response text",
          "type": "string"
        },
        "SharedFrom": {
          "description": "_ExtEmail_ account that this account shares certs from",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "SharedTo": {
          "description": "_ExtEmail_ account that this account shares certs for",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "ResponseCode",
        "ResponseText",
        "SharedTo",
        "SharedFrom"
      ],
      "type": "object"
    },
    "UploadFileReq": {
      "example": {
        "FileContents": "...",
        "FileName": "testfile",
        "FileType": "KTL",
        "Signature": "string"
      },
      "properties": {
        "FileContents": {
          "description": "Base64 encoded file contents",
          "type": "string"
        },
        "FileName": {
          "description": "Upload file name",
          "type": "string"
        },
        "FileType": {
          "description": "Bank specific file type",
          "type": "string"
        },
        "Signature": {
          "description": "Detached PGP signature(s) made with registered PGP key(s)",
          "type": "string"
        }
      },
      "required": [
        "FileContents",
        "FileType",
        "FileName",
        "Signature"
      ],
      "type": "object"
    },
    "UploadKeyReq": {
      "example": {
        "PgpKey": "...",
        "PgpKeyPurpose": "authorize"
      },
      "properties": {
        "PgpKey": {
          "description": "ASCII armored PGP Key",
          "type": "string"
        },
        "PgpKeyPurpose": {
          "description": "PGP key purpose, i.e. `export` (exporting cert private key) or `authorize` (upload content authorization verification).",
          "enum": [
            "export",
            "authorize"
          ],
          "type": "string"
        }
      },
      "required": [
        "PgpKey",
        "PgpKeyPurpose"
      ],
      "type": "object"
    },
    "VerifyEmailReq": {
      "example": {
        "AccessToken": "example-access-token",
        "Code": "123456"
      },
      "properties": {
        "AccessToken": {
          "description": "Access token from login response",
          "type": "string"
        },
        "Code": {
          "description": "Code from email",
          "type": "string"
        }
      },
      "required": [
        "Code",
        "AccessToken"
      ],
      "type": "object"
    },
    "VerifyPhoneReq": {
      "example": {
        "Code": "123456"
      },
      "properties": {
        "Code": {
          "description": "Registration SMS confirmation code. Not a login SMS MFA code.",
          "type": "string"
        }
      },
      "required": [
        "Code"
      ],
      "type": "object"
    },
    "VerifyTOTPReq": {
      "example": {
        "AccessToken": "example-access-token",
        "Code": "123456"
      },
      "properties": {
        "AccessToken": {
          "description": "AccessToken returned by `LoginMFA` when `SetupTOTP` was requested (held by the client in memory only)",
          "type": "string"
        },
        "Code": {
          "description": "6-digit code from the authenticator app",
          "type": "string"
        }
      },
      "required": [
        "Code",
        "AccessToken"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityAssignment": {
      "additionalProperties": false,
      "properties": {
        "assignedByActorId": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        },
        "effectiveFrom": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "effectiveUntil": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "id": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        },
        "roleId": {
          "maxLength": 64,
          "minLength": 1,
          "pattern": "^[a-z][a-z0-9_]{0,63}$",
          "type": "string"
        }
      },
      "required": [
        "id",
        "roleId",
        "assignedByActorId",
        "effectiveFrom",
        "effectiveUntil"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityClaims": {
      "additionalProperties": false,
      "description": "Decoded signed payload for native verification, not a JSON authority response. Require the pinned signature/issuer/audience/environment/key, exact session digest and one-use challenge, exp > now and exp <= iat + 60, current enrollment revisions and exact generated profile. Assignment intervals and role names are validated against that profile; no role implies another role. Schema validity alone grants nothing.",
      "properties": {
        "assignments": {
          "items": {
            "$ref": "#/definitions/WorkspaceAuthorityAssignment"
          },
          "maxItems": 64,
          "type": "array"
        },
        "aud": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        },
        "challenge": {
          "maxLength": 43,
          "minLength": 43,
          "pattern": "^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$",
          "type": "string"
        },
        "directory": {
          "$ref": "#/definitions/WorkspaceAuthorityDirectory"
        },
        "enrollmentDigest": {
          "description": "Stable digest of the complete validated company/member enrollment, including inactive scheduled assignments. Equal company/membership revisions must retain this digest; current active assignments may change at their time boundaries.",
          "maxLength": 71,
          "minLength": 71,
          "pattern": "^sha256:[a-f0-9]{64}$",
          "type": "string"
        },
        "environment": {
          "enum": [
            "test",
            "production"
          ],
          "type": "string"
        },
        "exp": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "iat": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "identityIssuer": {
          "maxLength": 512,
          "minLength": 1,
          "pattern": "^https://",
          "type": "string"
        },
        "identitySubject": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        },
        "iss": {
          "maxLength": 2048,
          "minLength": 1,
          "pattern": "^https://",
          "type": "string"
        },
        "profile": {
          "$ref": "#/definitions/WorkspaceAuthorityProfile"
        },
        "schemaVersion": {
          "enum": [
            2
          ],
          "type": "integer"
        },
        "sessionDigest": {
          "maxLength": 71,
          "minLength": 1,
          "pattern": "^sha256:[a-f0-9]{64}$",
          "type": "string"
        },
        "subscriptionTenant": {
          "maxLength": 128,
          "minLength": 1,
          "type": "string"
        },
        "workspace": {
          "$ref": "#/definitions/WorkspaceAuthorityWorkspace"
        }
      },
      "required": [
        "schemaVersion",
        "iss",
        "aud",
        "environment",
        "iat",
        "exp",
        "identityIssuer",
        "identitySubject",
        "subscriptionTenant",
        "enrollmentDigest",
        "sessionDigest",
        "challenge",
        "workspace",
        "profile",
        "assignments",
        "directory"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityDirectory": {
      "additionalProperties": false,
      "description": "Explicit server-enrolled platform identity facts. These are not display values supplied by the desktop and do not contain role-administration records.",
      "properties": {
        "actorCreatedAt": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "legalEntityEffectiveFrom": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "legalEntityEffectiveUntil": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "legalEntityExternalId": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        },
        "legalEntityName": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        },
        "tenantCreatedAt": {
          "$ref": "#/definitions/WorkspaceAuthorityEpoch"
        },
        "tenantName": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        }
      },
      "required": [
        "tenantName",
        "tenantCreatedAt",
        "legalEntityExternalId",
        "legalEntityName",
        "legalEntityEffectiveFrom",
        "legalEntityEffectiveUntil",
        "actorCreatedAt"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityEpoch": {
      "maximum": 253402300799,
      "minimum": 1,
      "type": "integer"
    },
    "WorkspaceAuthorityHeader": {
      "additionalProperties": false,
      "properties": {
        "alg": {
          "enum": [
            "RS256"
          ],
          "type": "string"
        },
        "kid": {
          "maxLength": 128,
          "minLength": 1,
          "pattern": "^[A-Za-z0-9_.-]{1,128}$",
          "type": "string"
        },
        "typ": {
          "enum": [
            "JWT"
          ],
          "type": "string"
        }
      },
      "required": [
        "alg",
        "typ",
        "kid"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityProfile": {
      "additionalProperties": false,
      "properties": {
        "digest": {
          "maxLength": 71,
          "minLength": 1,
          "pattern": "^sha256:[a-f0-9]{64}$",
          "type": "string"
        },
        "id": {
          "maxLength": 256,
          "minLength": 1,
          "type": "string"
        },
        "revisionId": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        },
        "version": {
          "$ref": "#/definitions/WorkspaceAuthorityRevision"
        }
      },
      "required": [
        "id",
        "version",
        "digest",
        "revisionId"
      ],
      "type": "object"
    },
    "WorkspaceAuthorityRevision": {
      "maximum": 9007199254740991,
      "minimum": 1,
      "type": "integer"
    },
    "WorkspaceAuthorityUuid": {
      "maxLength": 36,
      "minLength": 1,
      "pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$",
      "type": "string"
    },
    "WorkspaceAuthorityWorkspace": {
      "additionalProperties": false,
      "properties": {
        "actorId": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        },
        "companyRevision": {
          "$ref": "#/definitions/WorkspaceAuthorityRevision"
        },
        "legalEntityId": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        },
        "membershipRevision": {
          "$ref": "#/definitions/WorkspaceAuthorityRevision"
        },
        "platformTenantId": {
          "$ref": "#/definitions/WorkspaceAuthorityUuid"
        }
      },
      "required": [
        "platformTenantId",
        "legalEntityId",
        "actorId",
        "companyRevision",
        "membershipRevision"
      ],
      "type": "object"
    }
  },
  "host": "ws-api.isecure.fi",
  "info": {
    "contact": {
      "email": "support@isecure.fi",
      "name": "ISECure Oy",
      "url": "https://www.isecure.fi/"
    },
    "description": "The API provides secure file exchange with common banks in Finland via the *SEPA WebServices* channel, including certificate enrollment (PKI) and automatic certificate renewals.\n\nThe OpenAPI v2 specification is published on GitHub [isecurefi/wsapi-v2](https://github.com/isecurefi/wsapi-v2). Browser-compatible TypeScript SDK is available on GitHub [isecurefi/isecure-ts-client](https://github.com/isecurefi/isecure-ts-client). Command line CLI and PHP SDK remain available on GitHub [isecurefi/wscli-php](https://github.com/isecurefi/wscli-php).\n\nClient SDKs:\n\n- [TypeScript SDK (GA)](https://github.com/isecurefi/isecure-ts-client): supports Node.js and modern browser bundlers. Install with `npm install isecure-ts-client`.\n- [Python SDK (Beta)](https://github.com/isecurefi/isecure-py-client): see its README for installation and current API coverage.\n- [C# / .NET 10 SDK (Beta)](https://github.com/isecurefi/isecure-cs-client): registration, SMS/TOTP authentication, certificate enrollment and discovery, and signed file exchange. Follow the [C# quickstart](https://github.com/isecurefi/isecure-cs-client#tldr-make-your-first-api-call) to install from source; no NuGet release is published. See [preview coverage](https://github.com/isecurefi/isecure-cs-client/blob/main/docs/preview-scope.md) for the supported operations. Certificate import/export/sharing, password reset, and the separate Processing API are outside this preview.\n- [PHP client and SDK (Legacy)](https://github.com/isecurefi/wscli-php): command-line client and PHP SDK. See its README for installation and usage.\n\nC# operation samples assume a configured `ISECureClient` and application-supplied inputs. Authentication samples use `AuthResult.Status` to determine the next step; only `Authenticated` permits protected operations. Authentication refusals return `Failed`; transport/protocol errors and protected-operation refusals throw typed SDK exceptions. See the [C# authentication guide](https://github.com/isecurefi/isecure-cs-client/blob/main/docs/authentication.md) and [error handling](https://github.com/isecurefi/isecure-cs-client/blob/main/docs/errors.md).\n\n~~~ts\nimport { WSChannel } from \"isecure-ts-client\";\n\nconst client = new WSChannel({\n  ApiKey: process.env.ISECURE_API_KEY ?? \"0\",\n  Company: \"Example Company Oy\",\n  Name: \"Example User\",\n  Password: process.env.ISECURE_PASSWORD!,\n  Phone: \"+358401234567\",\n  PublicKey: process.env.ISECURE_PUBLIC_KEY_PEM!,\n  BaseUrl: \"https://ws-api.isecure.fi/v2\",\n  Email: \"user@example.com\",\n  Mode: \"data\",\n  Bank: \"nordea\",\n});\n\nconst state = await client.login();\nif (state.status === \"authenticated\") {\n  const { Connections = [] } = await client.listCerts();\n  for (const connection of Connections) {\n    console.log(connection.Bank, connection.Access);\n  }\n}\n~~~\n\n~~~sh\nexport SESSION=~/.wscli/settings.yaml\nwscli session login -c $SESSION\nexport APIKEY=$(yq -r .settings.apikey $SESSION)\nexport IDTOKEN=$(yq -r .settings.idtoken $SESSION)\n\ncurl -H Authorization:$IDTOKEN \\\n     -H x-api-key:$APIKEY \\\n     https://ws-api.isecure.fi/v2/files/danskebank\n~~~\n\nThe API provides role based access control (RBAC), user account management, password recovery, and SMS or authenticator-app (TOTP) multi-factor authentication based on Amazon Cognito user pools.\n\n*NOTE: The API endpoint for production is the same as for test, but without `test.` in the URL. Production and test APIs are deployed on separate AWS accounts.*\n\n*NOTE: The API runs on AWS API Gateway with AWS Lambda backends. Cold Lambda functions can add a small response delay. Banks can also have considerable processing delays, especially for certificate enrollments.*\n\n## Service enrollment\n\nEvery integrator (partner) has an own *API Key*, and every user account belongs to one integrator. The *API Key* is bound to the service subscription. In other words, a new production *API Key* requires a service agreement before file transfers are allowed.\n\nIf a user registers with `0` as the *API Key*, the API creates a new *API Key* and the user becomes the *API Key* owner. The owner account can list all users under the same *API Key* with the Integrator API. Integrators register their own owner account first and use its API key to register customer accounts.\n\n*NOTE: API call rate limits are set and tracked per API Key by AWS API Gateway.*\n\n## Account management\n\nA user email address can have an *admin* account, a *data* account, or both. The role in the API is called `mode`. The modes have separate passwords and different capabilities.\n\nLogin always requires both email address and mode. *Admin* mode login always requires MFA. Existing accounts may continue with SMS MFA, and accounts that have enrolled software-token MFA use authenticator-app/TOTP codes. *Data* mode uses password authentication and is suitable for automation. *Admin* mode configures the account, such as PGP keys and certificate sharing. *Data* mode exchanges files. Listing files is allowed in both modes.\n\n*NOTE: Integrators register customer accounts by using the API key from the API key owner account.*\n\n## Admin registration and first login\n\nThis sequence describes a new `admin` account using SMS MFA, without an integrator verification-bypass policy. Registration enables SMS MFA automatically. There is no separate SMS MFA activation code. Phone confirmation, login MFA, and email verification use different codes and API operations.\n\n| Code | Sent when | Submit to | Purpose |\n| --- | --- | --- | --- |\n| Registration SMS | `Register` creates an unconfirmed account | `VerifyPhone` with `Code` | Confirm signup and phone ownership once |\n| Login SMS MFA | `Login` returns `SMS_MFA`, or `SelectMFA` selects SMS | `LoginMFA` with `Code`, current `Session`, and `ChallengeName` | Authenticate this login |\n| Email verification | Password authentication and any MFA finish, but email is still unverified | `VerifyEmail` with `Code` and the returned `AccessToken` | Verify email ownership once |\n\n1. Call `InitRegister`, then `Register` with the encrypted password and challenge. Keep the returned `ApiKey`. The normal response says `Sign up successful. Verify phone number.`\n2. Submit the **registration SMS code** to `VerifyPhone`. Wait for `Phone confirmation successful.` before starting login.\n3. Call `InitLogin`, then `Login`. For SMS MFA, the response contains `Session` and `ChallengeName: SMS_MFA`. A **new login SMS code** is sent to the registered phone.\n4. Submit that login code, `Session`, and `ChallengeName` to `LoginMFA`. If email is unverified, the backend requests an email code and returns `Login OK. Verify email address.` with `AccessToken`. There is no `IdToken` yet.\n5. Submit the **email code** and that `AccessToken` to `VerifyEmail`. Successful verification updates Cognito and the account record, but does not issue an API session.\n6. Start a **fresh** `InitLogin` → `Login` → `LoginMFA` cycle. Use the new SMS code and new `Session`. A completed login returns `IdToken`, `ApiKey`, `ExpiresIn`, and `Account`.\n\n### First-login sequence diagram\n\nEach SMS below is a different code. Email verification is reached only after the login MFA step succeeds.\n\n~~~text\nInitRegister → Register\n          │\n          ▼\nRegistration SMS code\n          │ VerifyPhone(code)\n          ▼\nPhone confirmed\n          │ InitLogin → Login\n          ▼\nNew login SMS + Session\n          │ LoginMFA(code, Session)\n          ▼\nEmail code + AccessToken\n          │ VerifyEmail(code, token)\n          ▼\nEmail verified — no session yet\n          │ Fresh InitLogin → Login\n          ▼\nNew login SMS + new Session\n          │ LoginMFA(code, Session)\n          ▼\nIdToken + ApiKey + ExpiresIn\n          + Account\n     AUTHENTICATED\n~~~\n\nA user whose phone is already verified and whose email is not verified starts at step 3. They do not register again or repeat phone confirmation. Cognito `CONFIRMED` and an active MFA setting do not mean that email is verified.\n\n### Recognizing the next login step\n\n`ResponseCode: \"00\"` means the current API step succeeded; it does **not** by itself mean that an authenticated session is available. Use the returned state and fields. The TypeScript SDK exposes these as typed authentication states.\n\n| Response | Meaning | Next action |\n| --- | --- | --- |\n| `ChallengeName: SMS_MFA` or `SOFTWARE_TOKEN_MFA`, plus `Session` | MFA is required | Call `LoginMFA` with that session and the matching SMS or authenticator code |\n| `ChallengeName: SELECT_MFA_TYPE`, plus `Session` and `MfaOptions` | Choose an MFA factor | Call `SelectMFA`, then use its new `Session` with `LoginMFA` |\n| `Login OK. Verify email address.` plus `AccessToken`, without `IdToken` | Email code has been requested; login is incomplete | Call `VerifyEmail`, then start a fresh login |\n| `IdToken`, `ApiKey`, `ExpiresIn`, and `Account` | API session is authenticated | Send `IdToken` as `Authorization` and `ApiKey` as `x-api-key` |\n\nAn `AccessToken` used for verification is not the `IdToken` used for business API requests. Keep the verification token in memory. `SetupTOTP: true` can also return an `AccessToken`, alongside a completed session and TOTP enrollment details.\n\n### Lost codes and interrupted registration\n\n- Signup SMS lost: While the account is still unconfirmed, start `InitLogin` → `Login`. The backend requests another signup-confirmation SMS and returns `User authentication failed. Verify phone number with received SMS.` Submit this code to `VerifyPhone`, then restart login. This is not a login MFA challenge.\n\n- MFA SMS lost: Start a fresh `InitLogin` → `Login` cycle. Use the new SMS code with the new `Session`; do not reuse a completed MFA session.\n\n- Email code lost: If the code or verification token is unavailable, repeat login and MFA. While email is unverified, each successful authentication reaching the email check requests another email code and returns an `AccessToken`.\n\n- Email verified: Start a fresh login, including MFA for admin mode. `VerifyEmail` does not return an `IdToken`.\n\n- MFA restored: If the response says `SMS MFA restored. Start login again.`, the backend restored a missing admin MFA factor. Start a fresh login and complete SMS MFA.\n\nCode requests can fail or be throttled; a request does not guarantee delivery. Check the API response, the registered destination, and spam filtering. If delivery repeatedly fails, provide support with the operation, time, and `RequestId` when present. Do not send passwords, tokens, or verification codes.\n\n`VerifyPhone` confirms **signup**. If an already-confirmed account instead returns `Login OK. Verify phone number.` with an `AccessToken`, contact support: the current API does not expose a matching phone-attribute verification operation. Do not submit that attribute-verification code as a signup or MFA code.\n\n### TypeScript example: normal SMS onboarding\n\n`adminClient` is a configured `WSChannel` instance with `Mode: \"admin\"`. Supply `readCode` from your application's UI. This example follows the normal registration path above; handle transport failures and show API errors in your UI.\n\n~~~ts\nasync function onboardAdmin(readCode: (message: string) => Promise<string>) {\n  const registration = await adminClient.register();\n  if (registration.ResponseCode !== \"00\") throw new Error(registration.ResponseText);\n\n  const phone = await adminClient.verifyPhone(await readCode(\"Registration SMS code\"));\n  if (phone.status !== \"verification_accepted\") throw new Error(\"Phone confirmation failed\");\n\n  async function loginWithSms() {\n    const state = await adminClient.login();\n    if (state.status !== \"needs_mfa\" || state.method !== \"sms\") return state;\n    return adminClient.submitMfaCode(await readCode(\"New login SMS code\"));\n  }\n\n  let state = await loginWithSms();\n  if (state.status === \"needs_email_verification\") {\n    const email = await adminClient.verifyEmail(await readCode(\"Email verification code\"));\n    if (email.status !== \"verification_accepted\") throw new Error(\"Email verification failed\");\n    state = await loginWithSms(); // New login, new SMS code, new Session.\n  }\n  if (state.status !== \"authenticated\") throw new Error(`Login incomplete: ${state.status}`);\n  return state;\n}\n~~~\n\n## Initial registration with TOTP\n\nTOTP enrollment is optional and follows phone and email verification. First complete **Admin registration and first login**. Registration uses SMS MFA to bootstrap the admin account; scanning an authenticator QR code is a separate enrollment step.\n\nDuring a fresh SMS login, set the boolean `SetupTOTP: true` on the `LoginMFA` request that submits the current SMS code and `Session`. If login offers `SELECT_MFA_TYPE`, select `SMS_MFA` first and use the new session returned by `SelectMFA`. Do not call `LoginMFA` twice with an already-consumed session.\n\nIf phone or email verification is still required, that prompt takes precedence: no TOTP secret is returned. Finish verification, restart login, and request enrollment on the new MFA submission.\n\n1. Wait for a successful response containing `SecretCode`, `OtpauthUri`, and `AccessToken`. It also contains the authenticated API session; being authenticated does not mean TOTP enrollment is complete.\n2. Show `OtpauthUri` as a QR code, or let the user enter `SecretCode` in an authenticator app. Keep enrollment material in memory and out of logs, analytics, and URLs other than the QR payload itself.\n3. Submit the current six-digit authenticator code and enrollment `AccessToken` to `VerifyTOTP`. The token must belong to the same email and admin account in the route.\n4. Require `ResponseCode: \"00\"` / `TOTP enabled`, or SDK `status: \"verification_accepted\"`, before showing enrollment as complete. A resolved SDK promise can also contain a `failed` state.\n5. On the next login, follow the returned challenge as described below. Do not assume which factor will be preferred.\n\n### Login after TOTP enrollment\n\nThe next challenge depends on the account's MFA preferences. Both SMS and authenticator-app TOTP are supported by the deployed API in production and test. TOTP becomes enabled for an account only after successful `VerifyTOTP`; that operation keeps SMS enabled as well. As verified on 2026-09-20, production makes TOTP preferred for new enrollments, so subsequent login normally requests `SOFTWARE_TOKEN_MFA` directly. The test environment enables both factors without a preference for new enrollments, so login offers `SELECT_MFA_TYPE`. Existing accounts keep their stored preferences. Having both factors enabled does not guarantee a choice on every login: a direct TOTP challenge cannot be switched to SMS with `SelectMFA`.\n\n- `SELECT_MFA_TYPE`: Offer only the factors in `MfaOptions`. Call `SelectMFA` with the selected factor and current `Session`, then complete the returned challenge with its new `Session`.\n\n- `SOFTWARE_TOKEN_MFA`: Submit the authenticator code to `LoginMFA`, echoing `ChallengeName`. No SMS is sent for this challenge.\n\n- `SMS_MFA`: Submit the newly received SMS code to `LoginMFA`, echoing `ChallengeName`.\n\n`SelectMFA` selects a factor for the current login; it does not change the user's stored MFA preference. Selecting `SMS_MFA` sends an SMS. Selecting TOTP uses a code generated by the authenticator app.\n\n~~~text\nInitLogin → Login\n  │\n  ├─ SELECT_MFA_TYPE\n  │    └─ SelectMFA(offered factor)\n  │         └─ New Session + challenge\n  ├─ SOFTWARE_TOKEN_MFA → app code\n  └─ SMS_MFA → new SMS code\n\nFor each selected or direct challenge:\n  LoginMFA(code, latest Session,\n           returned ChallengeName)\n    │\n    ▼\n  Authenticated or verification required\n  Inspect the response before continuing\n~~~\n\nFor an already-verified admin account, the SDK handles both direct-factor and selection challenges as follows. `readCode` is your application's asynchronous code prompt; retain the returned state and surface any failure.\n\n~~~ts\nlet state = await adminClient.login();\nif (state.status === \"needs_mfa_selection\") {\n  const method = state.methods.includes(\"totp\") ? \"totp\" : \"sms\";\n  if (!state.methods.includes(method)) throw new Error(\"No supported MFA factor offered\");\n  state = await adminClient.selectMfaType(method);\n}\nif (state.status === \"needs_mfa\") {\n  const message = state.method === \"totp\" ? \"Authenticator code\" : \"New login SMS code\";\n  state = await adminClient.submitMfaCode(await readCode(message));\n}\nif (state.status !== \"authenticated\") {\n  throw new Error(`Login incomplete: ${state.status}`);\n}\n~~~\n\n### Interrupted enrollment and lost authenticators\n\n- Invalid authenticator code: Keep the enrollment token and show the failure. Check the authenticator entry and device clock, then submit a current code to `VerifyTOTP`. Do not mark enrollment complete.\n\n- Enrollment token expired, QR lost, or browser closed: Start a fresh login. If SMS bootstrap is restored, follow the restart instruction below. After completing SMS MFA, request a new enrollment with `SetupTOTP: true`; show the newly returned QR code and verify it.\n\n- `SMS MFA restored. Start login again.`: This recovery response is HTTP 400 with `ResponseCode: \"01\"`; the SDK throws an HTTP error. Show the restart instruction, then let the user start a fresh login. No admin session was issued. The backend restored SMS and disabled the incomplete TOTP factor.\n\n- `TOTP verified but setting MFA preference failed`: Enrollment partially completed. Do not report complete success or blindly repeat setup. Restart login to inspect the offered factor; contact support if login cannot be completed.\n\n- Authenticator lost; login offers factor selection: Choose SMS only if `MfaOptions` includes `SMS_MFA`, then complete that SMS challenge.\n\n- Authenticator lost; login directly requires TOTP: `SelectMFA` cannot switch a TOTP challenge to SMS. Contact support for account recovery. There is no public MFA-reset endpoint or backup-code flow. Password reset is not an MFA reset.\n\nAutomatic SMS recovery applies when an interrupted enrollment leaves the admin account with password-only authentication. It is not a general bypass for an account that still requires TOTP. Even if SMS is enabled, a TOTP-preferred account may not offer SMS selection.\n\n## Existing SMS MFA user migration to TOTP\n\nUse an admin account with verified phone and email. Start a fresh login, select SMS if a factor-selection challenge is returned, and submit that login's SMS code with `SetupTOTP: true`. Confirm the returned enrollment before treating TOTP as enabled.\n\n~~~ts\nlet state = await adminClient.login();\nif (state.status === \"needs_mfa_selection\" && state.methods.includes(\"sms\")) {\n  state = await adminClient.selectMfaType(\"sms\");\n}\nif (state.status !== \"needs_mfa\" || state.method !== \"sms\") {\n  throw new Error(`SMS bootstrap required: ${state.status}`);\n}\nstate = await adminClient.submitMfaCode(smsCode, { setupTotp: true });\nif (state.status !== \"authenticated\" || !state.totpEnrollment) {\n  throw new Error(`Enrollment not started: ${state.status}`);\n}\nconst { otpauthUri, secret, accessToken } = state.totpEnrollment;\n// Display otpauthUri as a QR code (or secret for manual entry) in your UI.\n// Obtain authenticatorCode from that newly enrolled authenticator.\nconst verified = await adminClient.verifyTotp(accessToken, authenticatorCode);\nif (verified.status !== \"verification_accepted\") {\n  throw new Error(`TOTP enrollment failed: ${verified.status}`);\n}\n// Future login can offer SELECT_MFA_TYPE or a direct factor challenge.\n~~~\n\nAfter completing the admin account's phone and email verification, register a separate `data` account with the same email and API key if automation/file transfer is needed. Data-mode login does not use admin MFA or TOTP enrollment.\n\n## Password recovery\n\nPassword recovery changes the password for the selected email and mode; it does not remove MFA or issue an authenticated API session.\n\n1. Call `InitPasswordReset` once to request a recovery code. It returns a response envelope, **not** an encryption challenge.\n2. Obtain the recovery code from the configured recovery channel.\n3. Call `InitLogin` to obtain a fresh `Challenge` for the same email and mode. This GET only fetches encryption parameters; it does not authenticate or send an MFA SMS.\n4. Encrypt the new password with that challenge timestamp and submit `PasswordReset` with `Code`, `ChResp`, and `Encrypted`. SDK `passwordReset(code, newPassword, challenge)` performs the encryption.\n5. After success, update the client's password and start a fresh login, including admin MFA.\n\nRecovery requires an eligible verified recovery destination. An account that has not finished email verification may need support if Cognito cannot deliver a recovery code. Do not use registration, login MFA, or email-verification codes as password-reset codes.\n\n## Logout and token lifetime\n\n`Logout` is authenticated with the session's `IdToken` and `ApiKey`. It requests Cognito global sign-out for the selected email and mode. There is no optional `AccessToken` parameter. Discard local session and enrollment material when signing out; the SDK clears its local session after the request returns. If it throws, discard the client instance and clear the application session explicitly.\n\nGlobal sign-out and JWT expiration are different. A service that validates a JWT's signature and expiration without checking revocation can continue to accept it until expiry. Do not promise that `Logout` immediately prevents every API request using a previously issued token.\n\n## Bank certificate enrollment\n\nThe *SEPA WebServices* connection to the bank requires enrolling a PKI certificate with the bank. *Admin* mode can enroll certificates for different banks, but only one certificate per bank. The corresponding private key is generated and stored encrypted with AWS KMS.\n\n### Simulator bank enrollment (test only)\n\nUse `Bank: \"simulator\"` only with the test base URL `https://ws-api.test.isecure.fi/v2`. Bank Simulator is a separately enabled paid feature for the tenant identified by your API key. Ask ISECure to enable access before enrollment. Registration does not enable it automatically; choosing `simulator` does not grant access. Enablement covers the tenant's users, while enrollment still requires an authenticated admin session.\n\nThe simulator uses the normal `EnrollCert` operation (`POST /certs/simulator`). All three fields remain required, but no bank-issued WS channel ID or enrollment PIN is needed:\n\n- `WsUserId`: generate a synthetic identifier, 1–16 bytes of printable ASCII without control characters.\n- `Code`: generate a fresh synthetic enrollment code, 16–32 bytes of printable ASCII without whitespace or control characters. There is no separate endpoint for obtaining it.\n- `Company`: use the company value from the registered test account.\n\nConfigure `adminClient` with the test base URL, `Mode: \"admin\"`, and `Bank: \"simulator\"`, then complete admin login and any required MFA before running this example. Generate new values for a new enrollment and keep them out of logs and source control.\n\n~~~ts\nconst suffix = crypto.randomUUID().replaceAll(\"-\", \"\");\nconst result = await adminClient.enrollCert({\n  Company: adminClient.props.Company,\n  WsUserId: `SIM-${suffix.slice(0, 12)}`,\n  Code: `SIM-${suffix.slice(0, 24)}`,\n});\nif (result.ResponseCode !== \"00\") {\n  throw new Error(result.ResponseText);\n}\n~~~\n\nEnabling access and enrolling a certificate are separate steps: the customer application performs this enrollment after access is enabled. A successful enrollment creates the simulator certificate, which can be found through `ListCerts`; the normal file operations then use `Bank: \"simulator\"`. This is synthetic test infrastructure and does not execute real bank payments. See the [Bank Simulator guide](https://www.isecure.fi/en/bank-simulator/) for the complete file-exchange example.\n\n## Bank certificate sharing\n\nIt is possible to share the same bank certificate with multiple accounts. Certificate sharing can be configured when accounts have the same API key. The account that holds the certificate can share it with another account (*admin* mode operation). Only the account that owns the certificate can PGP-export the certificate and corresponding private key. This allows one *admin* mode account to own certificates and share them with multiple *data* mode accounts.\n\nAn account can never have multiple certificates per bank, whether shared or directly enrolled. The API identifies the bank, not an individual certificate/key pair.\n\n## Access security\n\nAccess is secured with TLS on AWS API Gateway. Inside TLS, register and login use challenge-response: the client fetches fresh username-specific parameters with `InitRegister` or `InitLogin`, RSA-encrypts the password and challenge timestamp with OAEP padding, and submits the encrypted value to `Register`, `Login`, or `PasswordReset`.\n\nSuccessful login returns a Cognito `IdToken` and the integrator `ApiKey`. For protected operations, send `Authorization: <IdToken>` and `x-api-key: <ApiKey>`. The OpenAPI document also models these headers as security schemes. If a generated client exposes both explicit header parameters and security settings, use the same values for both; the wire request must contain exactly those two headers.\n\nAdministrative actions require MFA authentication. SMS MFA remains available and TOTP can be enrolled with `LoginMFA` and confirmed with `VerifyTOTP`. User account management is handled with Cognito user pools. Each email can have separate `admin` and/or `data` mode Cognito users that share the same API account data.\n\n## Error handling and retryability\n\nSuccessful logical API responses have `ResponseCode: \"00\"`. Logical failures usually return `ResponseCode: \"01\"` with a human-readable `ResponseText` and `RequestId`. Include `RequestId` in support tickets.\n\nHTTP status still matters. `401`/`403` authentication and authorization failures are not retryable without changing credentials, token, role, API key, or verification state. `Invalid API key` is not retryable. Validation errors are not retryable without changing the request. Bank/backend integration errors may be retryable only when the text indicates a temporary upstream problem, timeout, throttling, or bank-side availability issue.\n\nThe TypeScript SDK throws typed transport errors for non-2xx responses and network failures, while API-level failures are returned as structured response objects or typed authentication states. Prefer the SDK for browser and Node.js integrations so MFA, retries, logging redaction, and session handling stay consistent. Browser clients use normal API methods through the TypeScript SDK. API Gateway also supports CORS preflight `OPTIONS` requests, but these are infrastructure-only and are not public API operations or SDK methods.\n\n## CHANGELOG\n\n**2.16.0** :: 2026-09-25\n- Added the authenticated, challenge-bound ReadWorkspaceAuthority contract for explicitly enrolled company workspaces.\n\n**2.15.0** :: 2026-09-24\n- Added operator tenant scope to ListAuditEvents, including current-account resolution for existing clients and explicit Tenant for retained deleted-account history.\n\n**2.14.0** :: 2026-09-24\n- `ListAccountUsage.Licenses` adds scoped current user-license estimates with retirement exclusion and a one-license minimum per user.\n\n**2.13.0** :: 2026-09-22\n- `ListAccountUsage` exposes scoped daily and monthly distinct observed bank accounts, with explicit coverage gaps.\n\n**2.12.0** :: 2026-09-19\n- Customer audit history includes automatic certificate renewal attempts, persistence outcomes, and sanitized bank response codes/messages.\n\n**2.11.0** :: 2026-09-19\n- `ListAuditEvents` exposes sanitized audit evidence: integrators see their tenant and customers see their own account.\n- Account-management audit streams survive account deletion, with ten-year event retention.\n\n**2.10.0** :: 2026-09-18\n- `Login` and `LoginMFA` return an `Account` object on authenticated sessions: `Type` (`operator` for the ISECure owner account, `integrator` for an API key owner, `customer` otherwise), `Entitlements` (the tenant's active product ids) and `Features` (per-user preview flags). All values are server-decided; authorization is unchanged.\n\n**2.9.0** :: 2026-09-17\n- Added `RetireCert` (`DELETE /certs/{Bank}`) to retire a bank certificate, optionally on a customer account by the integrator owner.\n- Added `DeleteAccount` (`DELETE /account/{Email}`) for permanent, confirmed customer account deletion by the integrator owner within 10 minutes of an MFA login. Integrator owner accounts cannot be deleted.\n- `ListCerts` reports `Retired` certificates; `ListAccounts` reports `AccountType`, `LinkedTo`, and `Retired`.\n\n**2.8.0** :: 2026-08-10\n- Added privacy-minimal effective bank connection discovery to `ListCerts`, including direct or shared access and certificate purpose/expiry status.\n- Kept legacy `Certs` direct-only and made `Connections` optional for compatible rollout.\n- Corrected `LoginMFA.SetupTOTP` to its runtime boolean type.\n\n**2.7.0** :: 2026-06-25\n- Added software-token/TOTP admin MFA (`LoginMFA` with `SetupTOTP` and `VerifyTOTP`) while keeping SMS MFA available.\n- Documented the browser-compatible TypeScript SDK.\n- Added browser CORS preflight support (`OPTIONS`) for the TypeScript SDK. `OPTIONS` is infrastructure-only and not a public SDK operation.\n\n**2.6.0** :: 2021-06-14\n- Added token revocation for logout (AWS SDK based new capability), no changes to the API itself.\n\n**2.5.0** :: 2020-04-19\n- Removed `OPTIONS` from the documented business API operations.\n- Fixed Login response.\n\nThe test-only bank identifier `simulator` is available at `https://ws-api.test.isecure.fi/v2`. See the [Bank Simulator guide](https://www.isecure.fi/en/bank-simulator/) for enrollment, initial statement download, and signed file upload examples.\n\n## Related API references\n\n- [Processing API documentation](https://www.isecure.fi/apis/processing/): prepare, validate, approve and download payment files before local signing and File Exchange upload.\n- [Bank Simulator API documentation](https://www.isecure.fi/apis/bank-simulator/): configure synthetic banks and accounts, define scenarios, start runs and inspect results. Its control API is separate from the fixed File Exchange simulator example.\n\nProcessing API is Beta, with Nordea validated payment-file profiles for Finland, Sweden, Norway and Denmark. The Bank Simulator control API is Experimental. Both are available in the test environment by request. Use the registration, initial login, MFA and account-management operations documented here for all three APIs. Processing and Bank Simulator then exchange the completed login for a separate [Processing session](https://www.isecure.fi/apis/processing/#operation/processing_session.exchange), with their own subscription and permission checks. File uploads and downloads continue through this File Exchange API.",
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "termsOfService": "https://www.isecure.fi/ws-api-terms/",
    "title": "ISECure WS Channel API",
    "version": "v2.16.0",
    "x-logo": {
      "backgroundColor": "#FFFFFF",
      "url": "https://www.isecure.fi/images/isecure-small-logo.png"
    }
  },
  "paths": {
    "/account/{Email}": {
      "delete": {
        "consumes": [
          "application/json"
        ],
        "description": "Permanently delete one customer account under the caller's API key: both Cognito users (*admin* and *data*), the account record, every active and retired certificate and private key on it, and its link to a certificate-sharing owner. Customer account and certificate material are removed. Audit evidence is retained separately for ten years; audit log streams are never deleted by account deletion. The operation is not reversible.\n\n*Admin* mode only, and only the integrator API key owner account may call it. The admin login (which always includes MFA) must be fresh: a token whose `auth_time` is older than 10 minutes is rejected with `Re-authenticate to delete accounts`, so clients should sign in again with MFA and retry. The caller must repeat the exact account email in the body `Confirm` field; a missing or different value is rejected before any change.\n\nIntegrator API key owner accounts can never be deleted through the API, not even by themselves. An account that still shares its certificates with other accounts (see `ShareCerts`) is rejected until those accounts are unshared or deleted. An interrupted deletion can be repeated safely: already removed Cognito users are tolerated and the account record is removed last.",
        "operationId": "DeleteAccount",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Deletion confirmation",
            "in": "body",
            "name": "DeleteAccountReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/DeleteAccountReq"
            }
          },
          {
            "description": "Email address of the customer account to delete permanently",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/DeleteAccountResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "DeleteAccount",
        "tags": [
          "Integrator"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Permanent deletion. Requires an admin login with MFA at most\n// 10 minutes old and the exact email repeated as confirmation.\nconst result = await client.deleteAccount(\n  \"customer@example.com\",\n  \"customer@example.com\",\n);"
          }
        ]
      }
    },
    "/account/{Email}/{Mode}": {
      "get": {
        "description": "Before register (or login), client must fetch `challenge` from the API. Then on register (or login), the challenge must be passed along to the API (as response to the challenge). The challenge is always fresh for some period of time and the API validates it when passed with register (or login). The challenge has form of `base64-string|timestamp|uuid`. For example:\n\n```ezwXceQ63fV9oWTSJBAE2Zq1Cw5tBIJe+7+Rl8jrgbk=|1475429754114|4017bda8-0a15-4154-a8b7-88069b05cb4e```\n\n**NOTE:** The call must contain the same email as used for registration itself.\n",
        "operationId": "InitRegister",
        "parameters": [
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/InitRegisterResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "InitRegister",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// register() retrieves and answers the registration challenge.\nconst registration = await client.register();"
          },
          {
            "lang": "cURL",
            "source": "curl -H 'Content-Type: application/json' https://ws-api.isecure.fi/v2/account/user%40example.com/admin"
          },
          {
            "lang": "PHP",
            "source": "$config = new Swagger\\Client\\Configuration();\n$api = new Swagger\\Client\\Api\\AccountApi(new Swagger\\Client\\apiClient($config));\n$api->InitRegister(\"user@example.com\", \"admin\");"
          },
          {
            "lang": "C#",
            "source": "InitRegisterResp r = account.InitRegister(\"user@example.com\", \"admin\");\nreturn r.Challenge;"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// One call performs InitRegister and Register; do not call it twice.\n// password is supplied securely by your application. Registration does not log in.\nvar registration = await client.RegisterAsync(password);\n// Keep registration.ApiKey for future client instances.\n// Start LoginAsync to discover any required phone verification step."
          }
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Provide the _Code_ received by email and the _AccessToken_ returned by `Login` or `LoginMFA` with `Login OK. Verify email address.`. For a normal admin SMS login, this prompt follows successful `LoginMFA`; the initial `Login` first returns an MFA challenge.\n\nThe backend requests the email code when authentication reaches the unverified-email check. This is a separate code from registration SMS and login MFA. Keep `AccessToken` in memory and send it in the request body, not as the business API `Authorization` header.\n\nOn `Email verification successful.`, start a fresh `InitLogin` → `Login` cycle and complete admin MFA again. Verification does not return an `IdToken` or resume the previous MFA session. If the code is lost or the token is unavailable, repeat login and MFA to request another email code and obtain an `AccessToken`.\n\n**NOTE:** Phone and email verification bypass is an integrator-level policy option for deployments where the integrator has already verified those attributes. Contact ISECure support if this is required for your API key.",
        "operationId": "VerifyEmail",
        "parameters": [
          {
            "description": "Account parameters",
            "in": "body",
            "name": "VerifyEmailReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/VerifyEmailReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "VerifyEmail",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// After needs_email_verification; the client retains its AccessToken.\nconst result = await client.verifyEmail(\"123456\");\nif (result.status === \"verification_accepted\") {\n  const next = await client.login(); // Fresh login; complete admin MFA again.\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// code is from the verification email. The client retains the access token.\nif (state.Status == AuthStatus.NeedsEmailVerification)\n{\n    var verification = await client.VerifyEmailAsync(code);\n    if (verification.Status == AuthStatus.VerificationAccepted)\n        state = await client.LoginAsync(password);\n}\n// Verification does not log in. Complete the fresh login's MFA again."
          }
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Register the *admin* account first. For automated file exchange, also register a *data* account with the same email address. Both accounts share the same data, but are used for different purposes. *Admin* account must be registered first, then *data* account.\n\n*Admin* account is used to configure setup with **Certs** and **Pgp** operations, while the *data* account is used with **Files** operations only. Both accounts use **Account** and **Session** operations.\n\n*Admin* account always requires MFA during login. SMS MFA remains available, and TOTP can be enrolled after SMS bootstrap. *Data* account does not require MFA. Generally, the *data* account is considered *read-only* when no PGP keys are configured, since PGP Keys are used to verify file upload signatures and are thus required to successfully upload files with **Files** *UploadFile* operation.\n\nThe *admin* and *data* modes are separate Cognito users. Phone and email must be verified for a full session in either mode. Before registering an additional mode, the existing account must have both verifications recorded. Complete any verification requested for the new mode.\n\n`email` is the login username for both accounts and `mode` defines the selected \"mode\" for the login, i.e. *admin* or *data*.\n\nBefore registration client must fetch challenge from API (see Account InitRegister operation) and pass it back within the `ChResp` parameter.\n\nThe following parameters `name`, `phone`, and `company` are required and must be valid (`phone`, `email`) because phone and email ownership must be verified before a full API session is issued.\n\nClient must RSA encrypt (OAEP padding) the _password_ and the challenge _timestamp_ as string in the form `password||timestamp`, base64 encode it and provide the resulting string as `Encrypted` parameter. The RSA encryption can be done e.g. for illustration purposes within command line with openssl rsautl:\n```\necho -n Toddler_..123456789012345\\|\\|1475175151231 |\n\topenssl rsautl -oaep -encrypt -pubin -inkey test.pem |\n\tbase64\n\n```\n\nThe **test** API's RSA public key is as follows:\n\n```\n% cat test.pem\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkuSaoSZztGAIGDTY7Rff\npsBHJJT1k207UodOJbYFhHAq0lWJnvMPLl5Q1DUUZdTGtTdL8Dsaj/Bo2+gSykMM\nR5QiKewvQsLfvqjwOO8JDItnhJl0lUqcPpdQV4M/Ai3YNRjNcVy4a+pichqtSAWl\n9S1HV01MNeouk8PEr/zoUasmgfO3mz6N6XTUtF/tIi8K2kBOsLAtqltihFSd/zT8\nifYZE9cZTJ09lUs7kMz1wxFIsiegaE1jUYV+VSLu3PJ97oKhQpqop8EnkBAoBl6r\nmdmFryBQIdakPIdd4rO5Yg+to10n4u7Wij9ePIwWMfbqY4QoW5nXqMgFJQkIt4TG\neQIDAQAB\n-----END PUBLIC KEY-----\n```\n\nThe **production** API's RSA public key is as follows:\n\n```\n% cat prod.pem\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7wx4l7P3eLsaEyK7ZRME\ng5urEHwaEoY9LjkYcpMw9gmPIi3RoGjQX7HzPad2D7ES2yIGdmyxjN8R2LyFa8ke\nEE+VY3ISYzP2cOjd/zDkX01yjDXQLRxntXbtqIypGQAzmZbCyIB226ZKEE+ldh6M\nYyM41YWYikfocYssFEjY7fpPGeUg4FOmHmyWIZeMkXYovskoi1jZ1Ay1qn95XlpA\n/Ptru2efro4T1xksv4WBBrj8bMNwdDpf4oyzH2PKYkn3/KlNTBCHlAmzP0jd4pIa\nN0tAf2m8TcNq7kuBzyfs8AcCUj870p8SEiko0PMx6K+zVsTVWsxfUX+/+kmapmp/\nAwIDAQAB\n-----END PUBLIC KEY-----\n```\n\n\n\n**NOTE:** Password must be at least 20 characters long, have lower and upper case letters, numbers, and special characters.\n\n**NOTE:** Phone number must be provided with country code, e.g. `+358404982201`.\n\n**Normal admin sequence:** `InitRegister` → `Register` → `VerifyPhone` (registration SMS) → `InitLogin` → `Login` → `LoginMFA` (new SMS) → `VerifyEmail` → fresh login and MFA. Registration enables SMS MFA automatically; the registration SMS confirms signup, not MFA activation. See **Admin registration and first login** for the diagram, response states, and lost-code recovery.",
        "operationId": "Register",
        "parameters": [
          {
            "description": "Account parameters",
            "in": "body",
            "name": "RegisterReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/RegisterReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/RegisterResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "Register",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst registration = await client.register();\nconsole.log(registration.ApiKey);"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// One call performs InitRegister and Register; do not call it twice.\n// password is supplied securely by your application. Registration does not log in.\nvar registration = await client.RegisterAsync(password);\n// Keep registration.ApiKey for future client instances.\n// Start LoginAsync to discover any required phone verification step."
          }
        ]
      }
    },
    "/account/{Email}/{Mode}/password": {
      "get": {
        "description": "Start password reset for the selected email and mode. Cognito sends a confirmation code to the configured recovery channel. This flow is separate from admin login MFA.\n\nA successful response says `Password reset started`; it contains no encryption `Challenge`. After receiving the recovery code, call `InitLogin` for a fresh challenge, then submit `PasswordReset`. Do not call this endpoint again to obtain a challenge: another call requests another recovery code.",
        "operationId": "InitPasswordReset",
        "parameters": [
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "InitPasswordReset",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Requests the recovery code; this does not return an encryption challenge.\nconst result = await client.initPasswordReset();\nif (result.ResponseCode !== \"00\") throw new Error(result.ResponseText);"
          }
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Set a new _password_ for the selected email and mode. Provide the confirmation _Code_ from the password reset flow.\n\n**NOTE:** The new password must be RSA encrypted with the challenge timestamp; see Register for encryption details.\n\nObtain `Code` from `InitPasswordReset`, and fetch `ChResp` separately with `InitLogin` for the same email and mode. `InitPasswordReset` does not return a challenge. After success, update the client password and start a fresh login; admin MFA remains required. See **Password recovery**.",
        "operationId": "PasswordReset",
        "parameters": [
          {
            "description": "Account parameters",
            "in": "body",
            "name": "PasswordResetReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/PasswordResetReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "PasswordReset",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nimport { AxiosTransport, type InitLoginResponse } from \"isecure-ts-client\";\n\n// First call initPasswordReset() and ask the user for the recovery code.\n// Then request a fresh InitLogin challenge for password encryption.\nconst { BaseUrl, Email, Mode } = client.props;\nconst base = BaseUrl.replace(/\\/+$/, \"\");\nconst { data } = await new AxiosTransport().request<InitLoginResponse>({\n  method: \"GET\",\n  url: base + \"/session/\" + encodeURIComponent(Email) + \"/\" + Mode,\n});\nif (data.ResponseCode !== \"00\" || !data.Challenge) {\n  throw new Error(data.ResponseText || \"Missing password encryption challenge\");\n}\nconst newPassword = process.env.ISECURE_NEW_PASSWORD!;\nconst result = await client.passwordReset(\n  \"123456\",\n  newPassword,\n  data.Challenge,\n);\nif (result.ResponseCode !== \"00\") throw new Error(result.ResponseText);\nclient.updateProps({ Password: newPassword });\nconst state = await client.login(); // Complete any returned MFA challenge."
          }
        ]
      }
    },
    "/account/{Email}/{Mode}/{Phone}": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Confirm signup for the _Email_ and _Mode_ user using the **registration SMS code** in `Code`. Call this after `Register` returns `Sign up successful. Verify phone number.`, before admin login and MFA. The `Phone` path parameter is the registered phone number, not a change-phone operation.\n\nThis operation calls signup confirmation. It does not accept a login MFA code, does not activate MFA, and does not return an authenticated session. Registration already enables SMS MFA for admin accounts. On `Phone confirmation successful.`, start `InitLogin` → `Login`.\n\nIf the registration SMS is lost while signup is unconfirmed, start a login attempt to request another signup-confirmation code, then submit that code here. Already-confirmed accounts with an unverified phone attribute require support; this operation does not verify a phone attribute using an `AccessToken`.\n\n**NOTE:** Phone and email verification bypass is an integrator-level policy option for deployments where the integrator has already verified those attributes. Contact ISECure support if this is required for your API key.",
        "operationId": "VerifyPhone",
        "parameters": [
          {
            "description": "Account parameters",
            "in": "body",
            "name": "VerifyPhoneReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/VerifyPhoneReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          },
          {
            "description": "Phone number with country code, e.g. `+358401234567`",
            "in": "path",
            "name": "Phone",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "VerifyPhone",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Use the registration SMS code, not a login MFA code.\nconst result = await client.verifyPhone(\"123456\");\nif (result.status === \"verification_accepted\") {\n  const next = await client.login(); // The login SMS is a different code.\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// First call LoginAsync after registration to obtain NeedsPhoneVerification.\n// code is the registration SMS, not a login MFA code.\nif (state.Status == AuthStatus.NeedsPhoneVerification)\n{\n    var verification = await client.VerifyPhoneAsync(code);\n    if (verification.Status == AuthStatus.VerificationAccepted)\n        state = await client.LoginAsync(password);\n}\n// Complete the new login's required steps, including a separate login SMS."
          }
        ]
      }
    },
    "/audit": {
      "get": {
        "description": "Availability: deployed to production at https://ws-api.isecure.fi/v2 and the test environment at https://ws-api.test.isecure.fi/v2.\n\nRead sanitized account-management and automatic certificate-renewal audit history through the authenticated API. Integrator API key owners can read their entire tenant or select Account; customer accounts can read only their own account. Verified superadmins may select Tenant explicitly. With Account alone, a superadmin reads the account's current tenant resolved by an exact server-side lookup. Deleted accounts require explicit Tenant for superadmins; integrator owners retain deleted history in their own tenant without a lookup. Omitting both selectors reads the caller's own tenant, never all tenants. API-key ownership and server-owned operator authority are verified before any log access. Both admin and data modes can read their permitted history.\n\nEvents include the actor, action, affected account, timestamp, outcome and operation/request correlation. Automatic certificate renewal uses action certificate.renew and actorType system. A request event records the attempt time, step events record bank contact and persistence, and a result records rejected, failed, completed or skipped. completed means credentials were stored; a bank acceptance alone is insufficient. Bank rejections include bankResponseCode and, when safe to expose, bankResponseText. A timeout or malformed response has no invented bank code. A request without a result is incomplete; do not infer rejection or success. No bank files, SOAP messages, credentials, private keys or raw internal diagnostics are returned. Delivery is asynchronous; deduplicate by eventId across pages because publication retries may repeat an event. Streams survive account deletion and events have ten-year retention. Integrator owners can query deleted-account history without the deleted users-table row.\n\nResults are paginated newest first across all accounts in the permitted scope. Each page returns up to Limit events; additional pages continue toward older events. Use From and To to select up to 31 days at a time, and follow NextToken even after an empty page. Do not interpret missing or delayed events as proof that an operation did not happen.",
        "operationId": "ListAuditEvents",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Optional exact tenant API-key identifier. Verified superadmins may select another tenant, including retained history after account deletion. Other callers may only specify their own tenant. Keep Tenant and Account unchanged while following NextToken. With Account alone a superadmin resolves its current tenant; a missing target requires Tenant.",
            "in": "query",
            "name": "Tenant",
            "type": "string"
          },
          {
            "description": "Optional customer email. Integrator owners may select any account history in their tenant, including deleted accounts. Other users may select only their own email; omitted means their own account. For an integrator, omitted means the entire tenant. Superadmins can select an account in another tenant; specify Tenant for deleted accounts.",
            "in": "query",
            "name": "Account",
            "type": "string"
          },
          {
            "description": "Inclusive UTC start time on or after 2024-01-01, e.g. 2026-09-19T00:00:00.000Z. Defaults to seven days ago. Maximum range: 31 days.",
            "in": "query",
            "name": "From",
            "type": "string"
          },
          {
            "description": "Inclusive UTC end time. Defaults to now. Use the same range when continuing a page, or omit From and To to retain the cursor range.",
            "in": "query",
            "name": "To",
            "type": "string"
          },
          {
            "description": "Optional exact audit action, e.g. account.delete, certificate.retire or certificate.renew.",
            "in": "query",
            "name": "Action",
            "type": "string"
          },
          {
            "description": "Optional exact outcome: started, accepted, completed, not_found, rejected, failed, partial_failure or skipped.",
            "in": "query",
            "name": "Outcome",
            "type": "string"
          },
          {
            "description": "Maximum events per page, from 1 to 100; defaults to 100.",
            "in": "query",
            "name": "Limit",
            "type": "integer"
          },
          {
            "description": "Opaque continuation token returned by this operation. Bound to the caller, tenant, account and filters; expires after 24 hours. An empty page may still include a token.",
            "in": "query",
            "name": "NextToken",
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListAuditEventsResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListAuditEvents",
        "tags": [
          "Audit"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// WS API 2.12.0: available in production and the test environment.\n// Integrators see their tenant; customers see only their own account.\nconst query = { Limit: 50 };\nconst first = await client.listAuditEvents(query);\nconsole.log(first.Events);\nif (first.NextToken) {\n  // Fetch another page on demand, even if the previous page was empty.\n  const next = await client.listAuditEvents({ ...query, NextToken: first.NextToken });\n  console.log(next.Events);\n}"
          }
        ]
      }
    },
    "/certs": {
      "get": {
        "description": "List bank connections visible to the authenticated account. Connections includes directly owned and same-API-key shared certificate status without exposing the linked owner or shared certificate material. The legacy Certs field remains limited to certificates directly owned by this account.",
        "operationId": "ListCerts",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListCertsResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListCerts",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst { Connections = [] } = await client.listCerts();\nfor (const connection of Connections) {\n  console.log(connection.Bank, connection.Access, connection.Certificates);\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires an authenticated client. Discovery does not enroll certificates.\nvar certificates = await client.ListCertificatesAsync();\nforeach (var connection in certificates.Connections ?? [])\n    Console.WriteLine(connection.Bank);"
          }
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Reserved admin operation for configuring certificate usage parameters such as private-key export policy. The current deployed implementation validates authorization and then returns `configcerts not yet implemented`; do not build production workflows that depend on this operation changing account state yet.",
        "operationId": "ConfigCerts",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Certs handling settings",
            "in": "body",
            "name": "ConfigCertsReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ConfigCertsReq"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ConfigCerts",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.configCerts({ Export: \"disabled\" });"
          }
        ]
      }
    },
    "/certs/shared/{ExtEmail}": {
      "delete": {
        "description": "Reserved admin operation for removing certificate sharing from an existing _ExtEmail_ account under the same API key. The current deployed implementation validates authorization and then returns `unlinkaccount not yet implemented`; contact ISECure support if certificate sharing must be removed before this operation is fully available.",
        "operationId": "UnshareCerts",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Unshare certs with _ExtEMail_ account.",
            "in": "path",
            "name": "ExtEmail",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/UnshareCertsResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "UnshareCerts",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.unshareCerts(\"customer@example.com\");"
          }
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Share this account's bank certificates with an existing _ExtEmail_ account under the same API key. The caller must be authenticated in `admin` mode and must own the certificate being shared. The target account can then use the shared certificate for bank operations, but certificate/private-key export remains available only to the certificate owner.",
        "operationId": "ShareCerts",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Share certs with _ExtEMail_ account.",
            "in": "path",
            "name": "ExtEmail",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ShareCertsResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ShareCerts",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.shareCerts(\"customer@example.com\");"
          }
        ]
      }
    },
    "/certs/{Bank}": {
      "delete": {
        "description": "Retire the active certificate of *Bank* so the bank has no usable certificate and a fresh enrollment or import becomes possible. The certificate record is renamed on the account with a `.REMOVED.<random>` marker and stays only until the account is deleted; it is never exported, renewed, or used for file exchange again. `ListCerts` reports it under `Retired`.\n\n*Admin* mode only. Without the `Account` query parameter the caller's own certificate is retired. The integrator API key owner account may pass `Account` to retire a certificate on one of its customer accounts under the same API key. Any other account combination is rejected.\n\nRetiring a bank that has no active certificate fails, so a repeated call is a clean error rather than a second rename.",
        "operationId": "RetireCert",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "*Bank* whose active certificate is retired, e.g. `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, or `alandsbanken`.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          },
          {
            "description": "Optional customer account email under the same API key. Only the integrator API key owner account may retire another account's certificate.",
            "in": "query",
            "name": "Account",
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/RetireCertResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "RetireCert",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Retires the active certificate for client's Bank; the integrator\n// owner may pass Account to retire a customer's certificate instead.\nconst result = await client.retireCert({ Account: \"customer@example.com\" });"
          }
        ]
      },
      "get": {
        "description": "Download bank certificate and private key encrypted with stored PGP key.\n\n**NOTE**: The previously uploaded `PgpKeyId` must have purpose type `export`. I.e. purpose type `authorize` PGP keys cannot be used for exporting.\n\n**NOTE**: If `export` has been set to `disabled` (see ConfigCerts), then exporting private keys is not possible through API.",
        "operationId": "ExportCert",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, or `alandsbanken`.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          },
          {
            "description": "Short version of a PGP Key id identifying the exported Private Key, e.g. `3A3A59B2`",
            "in": "query",
            "name": "PgpKeyId",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ExportCertResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ExportCert",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst exported = await client.exportCert(\"3A3A59B2\");"
          }
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Provide WS-Channel user id, _WsUserId_, _Company_, and PIN _Code_ for _Bank_ certificate enrollment. _Company_ must match with the contract with the bank and is part of enrollment process. Note that certificate private key is securely generated and stored encrypted with AWS KMS encrypted authentication on API side. Certificates are automatically renewed when needed.\n\n**NOTE:** For OP bank, ensure that you set the PIN code blocks 1 and 2 in correct order. If not initially in correct order, bank will lock the registration and you need to call them for unlock.\n\n### Simulator enrollment in the test environment\n\nFor `Bank: \"simulator\"`, use the test base URL `https://ws-api.test.isecure.fi/v2`. Bank Simulator is a separately enabled paid tenant feature; registration does not grant access. Complete admin login and MFA before calling this operation.\n\n`Code`, `WsUserId`, and `Company` are still required. Generate your own synthetic `WsUserId` (1–16 printable ASCII bytes) and fresh `Code` (16–32 printable ASCII bytes, no whitespace). Use the registered test account's `Company`. No bank-issued credentials or separate credential-retrieval endpoint are involved. With an authenticated `adminClient` configured for the test API and `Bank: \"simulator\"`:\n\n~~~ts\nconst suffix = crypto.randomUUID().replaceAll(\"-\", \"\");\nconst result = await adminClient.enrollCert({\n  Company: adminClient.props.Company,\n  WsUserId: `SIM-${suffix.slice(0, 12)}`,\n  Code: `SIM-${suffix.slice(0, 24)}`,\n});\nif (result.ResponseCode !== \"00\") {\n  throw new Error(result.ResponseText);\n}\n~~~\n\nAccess enablement does not enroll a certificate automatically. See **Simulator bank enrollment (test only)** in the introduction and the [Bank Simulator guide](https://www.isecure.fi/en/bank-simulator/) for the full workflow.",
        "operationId": "EnrollCert",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Certs parameters",
            "in": "body",
            "name": "EnrollCertReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/EnrollCertReq"
            }
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, or `alandsbanken`. The additional bank identifier `simulator` is available only at `https://ws-api.test.isecure.fi/v2` and requires Bank Simulator access to be enabled separately for your API-key tenant. It is not available in production.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "EnrollCert",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Real-bank enrollment uses credentials issued by that bank.\n// For the separately enabled test-only simulator, use the caller-generated\n// values shown in this operation's \"Simulator enrollment in the test environment\" example.\nconst certificate = await client.enrollCert({\n  Code: process.env.BANK_ENROLLMENT_CODE!,\n  Company: \"EXAMPLE COMPANY OY\",\n  WsUserId: process.env.BANK_WS_USER_ID!,\n});"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires an authenticated admin client configured for the desired bank.\n// company, wsUserId and code are application inputs from bank onboarding.\nawait client.EnrollCertificateAsync(company, wsUserId, code);\nvar certificates = await client.ListCertificatesAsync();\n// For simulator, separately enable access and generate synthetic enrollment values\n// as shown in the Bank Simulator guide; use the registered test company name.\n// If the enrollment response is lost, check certificates before retrying."
          }
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Provide _WsUserId_, _Company_, _PrivateKey_, and _Certificate_ for importing existing WS Channel certificate and private key. _Company_ must match with the contract with the bank. Certificate(s) and private key(s) must be PEM formatted.\n\n**NOTE:** _EncCertificate_ and _EncPrivatekey_ are for DanskeBank only.",
        "operationId": "ImportCert",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Certs parameters",
            "in": "body",
            "name": "ImportCertReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ImportCertReq"
            }
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, or `alandsbanken`.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ImportCert",
        "tags": [
          "Certs"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst certificate = await client.importCert({\n  Company: \"EXAMPLE COMPANY OY\",\n  WsUserId: process.env.BANK_WS_USER_ID!,\n  PrivateKey: process.env.BANK_PRIVATE_KEY_PEM!,\n  Certificate: process.env.BANK_CERTIFICATE_PEM!,\n});"
          }
        ]
      }
    },
    "/desktop/workspace-authority": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "Read short-lived signed company workspace authority for the current verified Admin session and explicit company enrollment. The only input is a native one-use challenge. Tenant selection, operator status and feature flags do not grant company membership or roles. No bank or provider request is performed. The native application must verify the signature, supported contract, session/challenge binding and expiry before use; decoding this string is not authorization. Requires the separately configured workspace authority service and enrollment. Do not log, cache or persist the assertion in browser storage.",
        "operationId": "ReadWorkspaceAuthority",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Account parameters",
            "in": "body",
            "name": "ReadWorkspaceAuthorityReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ReadWorkspaceAuthorityReq"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Cache-Control": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ReadWorkspaceAuthorityResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Cache-Control": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Cache-Control": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Cache-Control": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Cache-Control": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ReadWorkspaceAuthority",
        "tags": [
          "Account"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// The native workspace owner generates the one-use challenge and must verify\n// the signed assertion. Do not log, cache or persist it in the browser.\nconst { Assertion } = await client.readWorkspaceAuthority(nativeChallenge);"
          }
        ]
      }
    },
    "/files/{Bank}": {
      "get": {
        "description": "Ask the selected _Bank_ to list downloadable files matching filters. _Status_ can be e.g. _NEW_, _ALL_, or _DLD_. _FileType_ is bank specific (`ALL` is not accepted); see the bank specification. Returns a list of _FileDescriptors_.\n\n**NOTE:** Certificate must be enrolled before files can be listed, downloaded, or uploaded.\n\n**NOTE:** Uploaded files do not show up in the bank file listing.\n\n~~~sh\nexport SESSION=~/.wscli/settings.yaml\nwscli session login -c $SESSION\n\ncurl -H Authorization:$(yq -r .settings.idtoken $SESSION) \\\n     -H x-api-key:$(yq -r .settings.apikey $SESSION) \\\n     https://ws-api.isecure.fi/v2/files/danskebank\n~~~",
        "operationId": "ListFiles",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, `alandsbanken` or `SEB`. The additional bank identifier `simulator` is available only at `https://ws-api.test.isecure.fi/v2` and requires Bank Simulator access to be enabled separately for your API-key tenant. It is not available in production.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          },
          {
            "description": "Status of the *file*, e.g. `ALL`. `NEW`, `DLD`",
            "in": "query",
            "name": "Status",
            "type": "string"
          },
          {
            "description": "*Bank* specific *FileType* identifies the file type to be listed",
            "in": "query",
            "name": "FileType",
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListFilesResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListFiles",
        "tags": [
          "Files"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst files = await client.listFiles({ Status: \"ALL\" });"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires authentication and an enrolled bank connection with access.\nvar files = await client.ListFilesAsync(fileType: \"camt.053.001.02\", status: \"NEW\");\n// Pass a returned descriptor's FileType and FileReference to DownloadFileAsync.\nConsole.WriteLine(files.FileDescriptors.Count);"
          }
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Uploads file to bank if PGP signature(s) are valid. _FileContents_ is a string of _Base64_ encoded file contents. _FileType_ is bank specific. _Signature_ is detached PGP signature or concatenation of PGP detached signatures in ASCII armor format. PGP signatures are used for authorizing file uploads. Currently one valid PGP authorize registered key signature is enough. _FileName_ is upload filename.\n\n**NOTE:** The uploaded files do not show up on the file listing from bank.\n\n```\n% export SESSION=~/.wscli/settings.yaml\n% wscli session login -c $SESSION\n% export APIKEY=`yq -r .settings.apikey $SESSION`\n% export IDTOKEN=`yq -r .settings.idtoken $SESSION`\n%\n% curl -X PUT -H Content-Type:application/json \\ \n       -H Authorization:$IDTOKEN \\ \n       -H x-api-key:$APIKEY \\ \n       -d @request-example.json \\ \n       https://ws-api.isecure.fi/v2/files/danskebank\n```\n\n",
        "operationId": "UploadFile",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Files parameters",
            "in": "body",
            "name": "UploadFileReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/UploadFileReq"
            }
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, `alandsbanken` or `SEB`. The additional bank identifier `simulator` is available only at `https://ws-api.test.isecure.fi/v2` and requires Bank Simulator access to be enabled separately for your API-key tenant. It is not available in production.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "UploadFile",
        "tags": [
          "Files"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.uploadFile({\n  FileContents: \"<base64-encoded ISO 20022 XML>\",\n  FileName: \"payment.xml\",\n  FileType: \"pain.001.001.03\",\n  Signature: \"<detached PGP signature>\",\n});"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires an authenticated data client and an enrolled bank connection.\n// Caller-chosen paths: paymentFile and signatureFile. Sign the exact bytes first\n// with your own signer; register its public key on the admin account.\nvar bytes = await File.ReadAllBytesAsync(paymentFile);\nvar signature = await File.ReadAllTextAsync(signatureFile);\nawait client.UploadFileAsync(bytes, \"payment.xml\", \"pain.001.001.09\", signature);\n// Use a file type accepted by your bank. Poll separately for feedback.\n// After a timeout, check status before retrying: the upload may have succeeded."
          }
        ]
      }
    },
    "/files/{Bank}/{FileType}/{FileReference}": {
      "delete": {
        "description": "Delete or mark as deleted a bank-side file identified by _Bank_, _FileType_, and _FileReference_. Use values returned by ListFiles. Bank support and final semantics are bank-specific.",
        "operationId": "DeleteFile",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, `alandsbanken` or `SEB`. The additional bank identifier `simulator` is available only at `https://ws-api.test.isecure.fi/v2` and requires Bank Simulator access to be enabled separately for your API-key tenant. It is not available in production.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          },
          {
            "description": "File reference *id* from list files",
            "in": "path",
            "name": "FileType",
            "required": true,
            "type": "string"
          },
          {
            "description": "File reference *id* from list files",
            "in": "path",
            "name": "FileReference",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "DeleteFile",
        "tags": [
          "Files"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.deleteFile(\n  \"pain.001.001.03\",\n  \"file-reference\",\n);"
          }
        ]
      },
      "get": {
        "description": "Download a bank file identified by _Bank_, _FileType_, and _FileReference_. The file reference is received from ListFiles. The response Content field contains the file content returned by the bank, typically Base64 encoded.",
        "operationId": "DownloadFile",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "*Bank* used for this operation, can have values of `nordea`, `osuuspankki`, `danskebank`, `aktia`, `sp`, `shb`, `pop`, `spankki`, `omasp`, `alandsbanken` or `SEB`. The additional bank identifier `simulator` is available only at `https://ws-api.test.isecure.fi/v2` and requires Bank Simulator access to be enabled separately for your API-key tenant. It is not available in production.",
            "in": "path",
            "name": "Bank",
            "required": true,
            "type": "string"
          },
          {
            "description": "File type from list files",
            "in": "path",
            "name": "FileType",
            "required": true,
            "type": "string"
          },
          {
            "description": "File reference identifier from list files",
            "in": "path",
            "name": "FileReference",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/DownloadFileResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "DownloadFile",
        "tags": [
          "Files"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst file = await client.downloadFile(\n  \"camt.053.001.02\",\n  \"file-reference\",\n);"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires an authenticated data client. descriptor came from ListFilesAsync.\nvar file = await client.DownloadFileAsync(descriptor.FileType, descriptor.FileReference);\n// Choose outputFile locally; never use a remote reference as a filesystem path.\nawait using var output = new FileStream(outputFile, FileMode.CreateNew, FileAccess.Write);\nawait output.WriteAsync(file.Bytes); // Preserve the exact downloaded bytes."
          }
        ]
      }
    },
    "/integrator/accounts": {
      "get": {
        "description": "List accounts registered under the authenticated integrator API key. This is an API key owner/admin operation intended for integrators managing their own customer accounts.",
        "operationId": "ListAccounts",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListAccountsResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListAccounts",
        "tags": [
          "Integrator"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst accounts = await client.listAccounts();"
          }
        ]
      }
    },
    "/pgp": {
      "delete": {
        "description": "Reserved admin operation for deleting a registered PGP key by 8-character key id. The current deployed implementation validates authorization and key lookup, but returns `pgpDelete is not yet supported`; do not build production workflows that rely on key deletion yet.",
        "operationId": "DeleteKey",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Pgp parameters",
            "in": "body",
            "name": "DeleteKeyReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/DeleteKeyReq"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "DeleteKey",
        "tags": [
          "Pgp"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.deleteKey(\"DBCBE671\");"
          }
        ]
      },
      "get": {
        "description": "List PGP public keys registered for the authenticated account. `authorize` keys verify UploadFile signatures. `export` keys encrypt exported certificate private keys.",
        "operationId": "ListKeys",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListKeysResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListKeys",
        "tags": [
          "Pgp"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst keys = await client.listKeys();"
          }
        ]
      },
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Upload an ASCII-armored PGP public key for the authenticated admin account. Use purpose `authorize` for detached UploadFile signature verification, or `export` for encrypting ExportCert private-key material. The same PGP key cannot be registered for both purposes at the same time.",
        "operationId": "UploadKey",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "ASCII armored PGP Key in `PgpKey` and key purpose, i.e. `export` (exporting cert private key) or `authorize` (upload content authorization verification) in `PgpKeyPurpose`.\n\n**NOTE**: The same PGP key cannot be used for both `export` and `authorize` purpose at the same time.",
            "in": "body",
            "name": "UploadKeyReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/UploadKeyReq"
            }
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "201": {
            "description": "Operation successfully processed. Resource created. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "UploadKey",
        "tags": [
          "Pgp"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.uploadPgpKey(\n  process.env.ISECURE_PGP_PUBLIC_KEY!,\n  \"authorize\",\n);"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Requires an authenticated admin client; publicKeyFile is a caller-chosen path.\n// Register only the PUBLIC half of the key used for detached file signatures.\nvar publicKey = await File.ReadAllTextAsync(publicKeyFile);\nawait client.UploadPgpKeyAsync(publicKey, PgpKeyPurpose.Authorize);"
          }
        ]
      }
    },
    "/session/{Email}/{Mode}": {
      "delete": {
        "description": "Request Cognito global sign-out for the authenticated email and mode, using `Authorization: <IdToken>` and `x-api-key: <ApiKey>`. There is no optional `AccessToken` parameter.\n\nDiscard local session and enrollment material when signing out. The SDK clears its local session after the request returns. If it throws, discard the client instance and clear the application session explicitly. Do not interpret a transport or backend failure as confirmation that server-side sign-out completed.\n\nPreviously issued JWTs can still be accepted until expiry by services that validate signature and expiration without checking revocation. Do not rely on logout for immediate invalidation at every API boundary. See **Logout and token lifetime**.\n\nSuccessful sign-out returns `ResponseCode: \"00\"`. A Cognito sign-out failure returns HTTP 400 with `ResponseCode: \"01\"` and `ResponseText: \"Failed to sign out\"`.",
        "operationId": "Logout",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "Logout",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst result = await client.logout();"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Call on the same instance that logged in, including in application cleanup.\nawait client.LogoutAsync();\n// Local authentication clears even if server logout throws.\n// Handle the exception: local clearing does not prove server revocation."
          }
        ]
      },
      "get": {
        "description": "Before login, client must fetch `challenge` from the API. Then on login, the challenge must be passed along to the API (as response to the challenge). The challenge is always fresh for some period of time and the API validates it when passed with login. The challenge has form of `base64-string|timestamp|uuid`. For example:\n\n```ezwXceQ63fV9oWTSJBAE2Zq1Cw5tBIJe+7+Rl8jrgbk=|1475429754114|4017bda8-0a15-4154-a8b7-88069b05cb4e```\n\n**NOTE:** The call must contain the same email as used for registration itself.",
        "operationId": "InitLogin",
        "parameters": [
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/InitLoginResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "InitLogin",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// login() retrieves and answers the login challenge.\nconst state = await client.login();"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// One call performs InitLogin and Login; password is an application input.\nvar state = await client.LoginAsync(password);\nif (state.Status == AuthStatus.Authenticated)\n{\n    var certificates = await client.ListCertificatesAsync();\n}\n// Otherwise follow NeedsMfaSelection, NeedsMfa, NeedsPhoneVerification,\n// or NeedsEmailVerification. Failed requires a fresh login before retrying."
          }
        ]
      },
      "post": {
        "consumes": [
          "application/json"
        ],
        "description": "After `InitLogin`, call `Login` with _Email_, _Mode_, and RSA encrypted _admin_ or _data_ account _password_ and _challenge timestamp_. For further API calls (requiring authorization), include the received _IdToken_ into the Authorization header of the request (pass idtoken as required parameter with the client SDK API calls). The _IdToken_ expires in _ExpiresIn_ seconds, after which new login must be performed.\n\n**NOTE:** In case MFA _Code_ is required, the call returns _Session_, _ChallengeName_, _ResponseCode_, and _ResponseText_ as the login process continues with the _LoginMFA_ API call. Echo _ChallengeName_ back to `LoginMFA` so the API can distinguish SMS (`SMS_MFA`) from authenticator/TOTP (`SOFTWARE_TOKEN_MFA`) codes.\n\n**NOTE:** If _Email_ has not been yet verified, successful login provides only _ResponseCode_, _ResponseText_, and an _AccessToken_ that must be used to verify email address.\n\n~~~sh\nCHALLENGE=$(curl -s https://ws-api.isecure.fi/v2/session/user@example.com/data | jq -r .Challenge)\nTIMESTAMP=$(echo $CHALLENGE | cut -f 2 -d \\|)\nENCRYPTED=$(echo -n testPassword..123455677098811\\|\\|$TIMESTAMP | openssl rsautl -oaep -encrypt -pubin -inkey prod.pem | base64)\n~~~\n\n**Login is a sequence, not a single success flag.** `ResponseCode: \"00\"` can mean MFA or verification is still required. For normal admin registration, complete `VerifyPhone` first. An unconfirmed signup instead triggers another registration SMS; submit it to `VerifyPhone`, not `LoginMFA`. `SELECT_MFA_TYPE` requires `SelectMFA` before `LoginMFA`. Only a response containing `IdToken` and `ApiKey` establishes an API session. See **Recognizing the next login step** and **Lost codes and interrupted registration**.\n\n**Example: MFA required**\n\n~~~json\n{\n  \"ResponseCode\": \"00\",\n  \"ResponseText\": \"Give SMS code\",\n  \"Session\": \"example-mfa-session\",\n  \"ChallengeName\": \"SMS_MFA\"\n}\n~~~\n\n**Example: email verification required (after authentication)**\n\n~~~json\n{\n  \"ResponseCode\": \"00\",\n  \"ResponseText\": \"Login OK. Verify email address.\",\n  \"AccessToken\": \"example-access-token\"\n}\n~~~",
        "operationId": "Login",
        "parameters": [
          {
            "description": "Login body parameters",
            "in": "body",
            "name": "LoginReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/LoginReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "Administer account with `admin` mode, exchange files with `data` mode",
            "enum": [
              "admin",
              "data"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/LoginResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "Login",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst state = await client.login();\nif (state.status === \"authenticated\") {\n  console.log(\"Session ready\");\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// One call performs InitLogin and Login; password is an application input.\nvar state = await client.LoginAsync(password);\nif (state.Status == AuthStatus.Authenticated)\n{\n    var certificates = await client.ListCertificatesAsync();\n}\n// Otherwise follow NeedsMfaSelection, NeedsMfa, NeedsPhoneVerification,\n// or NeedsEmailVerification. Failed requires a fresh login before retrying."
          }
        ]
      }
    },
    "/session/{Email}/{Mode}/mfacode": {
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Submit the code for the current login challenge with the latest `Session` and `ChallengeName` from `Login` or `SelectMFA`. For `SMS_MFA`, use the **login SMS code**, not the registration SMS code. For `SOFTWARE_TOKEN_MFA`, use the current authenticator code. A lost SMS or expired session requires a fresh login cycle.\n\nIf phone is verified but email is unverified, successful MFA requests an email verification code and returns only `ResponseCode`, `ResponseText: \"Login OK. Verify email address.\"`, and `AccessToken`. Submit the email code and token to `VerifyEmail`, then start a fresh login and MFA cycle. No `IdToken`, `ApiKey`, or `Account` is returned at this intermediate step.\n\nOnce verification and MFA are complete, the response includes `IdToken`, `ApiKey`, `ExpiresIn`, and `Account`. Send `IdToken` as `Authorization` and `ApiKey` as `x-api-key` for protected operations. The session expires after `ExpiresIn` seconds.\n\nFor optional TOTP enrollment, include the boolean `SetupTOTP: true` on this same MFA submission. Enrollment details are returned only after phone and email verification are complete. Do not reuse an already-consumed `Session` to request enrollment.\n\n**Example: authenticated session**\n\n~~~json\n{\n  \"ResponseCode\": \"00\",\n  \"ResponseText\": \"Login OK\",\n  \"IdToken\": \"example-id-token\",\n  \"ApiKey\": \"example-integrator-api-key\",\n  \"ExpiresIn\": 3600,\n  \"Account\": {\n    \"Type\": \"customer\",\n    \"Entitlements\": [],\n    \"Features\": []\n  }\n}\n~~~\n\n**Example: email verification required (after authentication)**\n\n~~~json\n{\n  \"ResponseCode\": \"00\",\n  \"ResponseText\": \"Login OK. Verify email address.\",\n  \"AccessToken\": \"example-access-token\"\n}\n~~~",
        "operationId": "LoginMFA",
        "parameters": [
          {
            "description": "Session parameters",
            "in": "body",
            "name": "LoginMFAReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/LoginMFAReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "MFA login is an admin-mode operation.",
            "enum": [
              "admin"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/LoginMFAResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "LoginMFA",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// After needs_mfa; the client retains Session and ChallengeName.\nconst state = await client.submitMfaCode(\"123456\");\nif (state.status === \"needs_email_verification\") {\n  // Prompt for the email code, then call client.verifyEmail(code).\n  // After verification, start a fresh login and complete MFA again.\n} else if (state.status === \"authenticated\") {\n  console.log(\"Session ready\");\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// state is the previous login/selection result; code is the requested SMS/TOTP code.\nif (state.Status == AuthStatus.NeedsMfa)\n{\n    state = await client.SubmitMfaCodeAsync(code);\n}\n// Check state.Status: MFA success can still require email verification.\n// Only Authenticated permits protected operations. Failed needs a fresh login.\n// To enroll TOTP, pass setupTotp: true for an eligible SMS challenge,\n// retain the returned TotpEnrollment in memory, then call VerifyTotpAsync."
          }
        ]
      }
    },
    "/session/{Email}/{Mode}/selectmfa": {
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "When login returns a `SELECT_MFA_TYPE` challenge (the account has both SMS and authenticator MFA enabled with neither preferred), choose which factor to use by sending the previously received _Session_ and the chosen _MfaType_. Returns the chosen factor's challenge (`SMS_MFA` or `SOFTWARE_TOKEN_MFA`) with a fresh _Session_; complete it with `LoginMFA`. Choosing `SMS_MFA` sends the SMS.\n\nOffer only factors returned in `MfaOptions`. Selection applies to this login and does not change stored preferences. A direct `SOFTWARE_TOKEN_MFA` challenge cannot be switched to SMS with this operation. See **Login after TOTP enrollment** for both login paths.",
        "operationId": "SelectMFA",
        "parameters": [
          {
            "description": "Session parameters",
            "in": "body",
            "name": "SelectMFAReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SelectMFAReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "MFA selection is an `admin` mode operation",
            "enum": [
              "admin"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/SelectMFAResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "SelectMFA",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// After needs_mfa_selection, choose a method offered in state.methods.\nconst state = await client.selectMfaType(\"totp\");\nif (state.status === \"needs_mfa\") {\n  // Prompt for the selected factor's code, then call submitMfaCode(code).\n  // The client retains the new Session and ChallengeName.\n}"
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// Select only a factor offered by the current login result.\n// This example prefers TOTP when offered; your UI can let the user choose.\nif (state.Status == AuthStatus.NeedsMfaSelection)\n{\n    var method = state.Methods.Contains(MfaMethod.Totp)\n        ? MfaMethod.Totp : state.Methods.First();\n    state = await client.SelectMfaTypeAsync(method);\n}\n// Check the new state; for NeedsMfa, ask for a code matching state.Method."
          }
        ]
      }
    },
    "/session/{Email}/{Mode}/verifytotp": {
      "put": {
        "consumes": [
          "application/json"
        ],
        "description": "Complete TOTP enrollment started by a successful `LoginMFA` request with `SetupTOTP: true`. Submit the current six-digit authenticator `Code` and the enrollment `AccessToken` in the body. The token must belong to the same `Email` and `admin` account named in the route. No phone parameter is required.\n\nScanning the QR code or receiving an authenticated session does not complete enrollment. Require `ResponseCode: \"00\"` and `TOTP enabled`; with the TypeScript SDK, require `status: \"verification_accepted\"`. Failed verification can be a logical failure response, while transport and some backend failures throw SDK errors.\n\nOn success, SMS and TOTP remain enabled. Depending on deployment policy, TOTP becomes preferred or neither factor is preferred. In the test environment, new enrollments use no preference and subsequent login offers `SELECT_MFA_TYPE`. Follow the returned challenge; existing accounts can retain different preferences. `SelectMFA` is available only for a selection challenge, not as an automatic fallback from a direct TOTP challenge.\n\nFor invalid codes, expired enrollment tokens, partial preference failures, or a lost authenticator, see **Interrupted enrollment and lost authenticators**. Keep enrollment material in memory and out of logs.",
        "operationId": "VerifyTOTP",
        "parameters": [
          {
            "description": "Session parameters",
            "in": "body",
            "name": "VerifyTOTPReq",
            "required": true,
            "schema": {
              "$ref": "#/definitions/VerifyTOTPReq"
            }
          },
          {
            "description": "Email address as the account username, e.g. `user@example.com`",
            "in": "path",
            "name": "Email",
            "required": true,
            "type": "string"
          },
          {
            "description": "TOTP enrollment is an `admin` mode operation",
            "enum": [
              "admin"
            ],
            "in": "path",
            "name": "Mode",
            "required": true,
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/Response"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {}
        ],
        "summary": "VerifyTOTP",
        "tags": [
          "Session"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\nconst state = await client.verifyTotp(\n  accessToken,\n  codeFromAuthenticatorApp,\n);\nif (state.status !== \"verification_accepted\") {\n  throw new Error(\"TOTP enrollment was not confirmed\");\n}\n// On the next login, follow the returned MFA challenge or factor selection."
          },
          {
            "lang": "csharp",
            "label": "C# SDK (Beta)",
            "source": "// client is a configured ISECureClient; see the C# quickstart in Introduction.\nusing ISECure;\nusing ISECure.Authentication;\n\n// state is the result of SubmitMfaCodeAsync(code, setupTotp: true).\n// Show its enrollment URI only to the user; never log/store enrollment secrets.\nvar enrollment = state.TotpEnrollment\n    ?? throw new InvalidOperationException(\"No TOTP enrollment returned.\");\n// code is a NEW code from the authenticator after adding the enrollment URI.\nvar verification = await client.VerifyTotpAsync(enrollment.AccessToken, code);\nif (verification.Status != AuthStatus.VerificationAccepted)\n    throw new InvalidOperationException(\"TOTP enrollment was not confirmed.\");\n// Confirmation preserves an existing login; it does not create a new one."
          }
        ]
      }
    },
    "/usage/accounts": {
      "get": {
        "description": "Read daily and monthly distinct accounts observed in supported CAMT bank files. Monthly and tenant totals are set unions, never sums of daily or user counts. Missing days, partial captures and unreadable payloads remain explicit. These are observed-usage figures, not a complete inventory of contracted or idle accounts and not an issued invoice. Payment rows are outside this basis. Authentication and API-key ownership are checked before reading. Integrator owners see their tenant and its users; customer accounts see only themselves; the ISECure operator may select another tenant. No bank account numbers, account tokens, bank messages or keys are returned.",
        "operationId": "ListAccountUsage",
        "parameters": [
          {
            "description": "Use _IdToken_ from the Login response as the `Authorization` header",
            "in": "header",
            "name": "Authorization",
            "required": true,
            "type": "string"
          },
          {
            "description": "Use _ApiKey_ from the Login response as the `x-api-key` header",
            "in": "header",
            "name": "x-api-key",
            "required": true,
            "type": "string"
          },
          {
            "description": "Calendar month YYYY-MM in Europe/Helsinki; defaults to the current month.",
            "in": "query",
            "name": "Month",
            "type": "string"
          },
          {
            "description": "Only the authenticated ISECure operator may select another tenant. Other callers are restricted to their verified API key.",
            "in": "query",
            "name": "Tenant",
            "type": "string"
          },
          {
            "description": "Optional account email. Tenant owners may select a user in their tenant; other users see only themselves.",
            "in": "query",
            "name": "Account",
            "type": "string"
          }
        ],
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "Operation successfully processed. See response.",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ListAccountUsageResp"
            }
          },
          "400": {
            "description": "Request validation error",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "403": {
            "description": "Unauthenticated",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          },
          "500": {
            "description": "Unexpected error occurred",
            "headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              }
            },
            "schema": {
              "$ref": "#/definitions/ErrorResponse"
            }
          }
        },
        "security": [
          {
            "Authorizer": [],
            "X-Api-Key": []
          }
        ],
        "summary": "ListAccountUsage",
        "tags": [
          "Usage"
        ],
        "x-code-samples": [
          {
            "lang": "TypeScript",
            "label": "Official TypeScript SDK",
            "source": "// client is a configured WSChannel instance; see Introduction.\n// Monthly totals are server-side unions; never sum daily or user counts.\nconst { Usage } = await client.listAccountUsage({ Month: \"2026-09\" });\nconsole.log(Usage.UniqueAccounts, Usage.MissingDays, Usage.Issues);"
          }
        ]
      }
    }
  },
  "schemes": [
    "https"
  ],
  "securityDefinitions": {
    "Authorizer": {
      "description": "Successful login provides `IdToken` that must be provided in the `Authorization` header",
      "in": "header",
      "name": "Authorization",
      "type": "apiKey"
    },
    "X-Api-Key": {
      "description": "Integrator specific API Key. For all integrator customers, the API key must be the same.",
      "in": "header",
      "name": "x-api-key",
      "type": "apiKey"
    }
  },
  "swagger": "2.0",
  "x-servers": [
    {
      "description": "Staging server",
      "url": "https://ws-api.test.isecure.fi/v2/"
    },
    {
      "description": "Production server",
      "url": "https://ws-api.isecure.fi/v2/"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Authentication & sessions",
      "tags": [
        "Session"
      ]
    },
    {
      "name": "Bank file exchange",
      "tags": [
        "Files"
      ]
    },
    {
      "name": "Account administration",
      "tags": [
        "Account",
        "Certs",
        "Pgp"
      ]
    },
    {
      "name": "Integrator accounts",
      "tags": [
        "Integrator"
      ]
    },
    {
      "name": "Audit history",
      "tags": [
        "Audit"
      ]
    },
    {
      "name": "Account usage",
      "tags": [
        "Usage"
      ]
    },
    {
      "name": "Data models",
      "tags": [
        "Schemas"
      ]
    }
  ],
  "externalDocs": {
    "description": "Official ISECure TypeScript SDK",
    "url": "https://github.com/isecurefi/isecure-ts-client"
  },
  "tags": [
    {
      "name": "Session",
      "description": "Login, logout, SMS MFA, and authenticator-app TOTP workflows."
    },
    {
      "name": "Files",
      "description": "List, upload, download, and remove bank files through the WS Channel."
    },
    {
      "name": "Account",
      "description": "Registration, verification, password recovery, and account setup."
    },
    {
      "name": "Certs",
      "description": "Enroll, import, export, configure, and share bank certificates."
    },
    {
      "name": "Pgp",
      "description": "Manage the PGP public keys used for protected certificate exports."
    },
    {
      "name": "Audit",
      "description": "Read sanitized account-management evidence, newest first. Integrators see their tenant; customers see only their own account. Available in production and the test environment."
    },
    {
      "name": "Usage",
      "description": "Read daily and monthly distinct bank accounts observed in supported CAMT files."
    },
    {
      "name": "Integrator",
      "description": "Manage the customer accounts associated with an integrator API key."
    }
  ]
}
