{
  "openapi": "3.1.0",
  "info": {
    "title": "Sahl Partner API",
    "version": "0.2.0",
    "description": "Server-to-server API for lenders and banks: document reading, verification, risk assessment and eID checks. Authenticate with `Authorization: Bearer <API key>`. Paths are relative to the server URL. New workspaces start in sandbox."
  },
  "servers": [
    {
      "url": "https://app.sahlfinancial.com/api",
      "description": "Single API host. Send environment=sandbox (the default) for test data."
    }
  ],
  "paths": {
    "/v1/kyc/extract": {
      "post": {
        "tags": [
          "KYC"
        ],
        "summary": "Read documents",
        "description": "Required scope: `kyc:extract`.\n\nRead one upload step's document(s) into canonical fields, with checks.\n\n`doc_type` hints the reader which document this is. `step_key` names\nthe step in a language-independent way; it decides which document types\nthe step accepts.\n\nWith a `reference`, the files and what was read from them are also filed\non that client's case in `environment` ('sandbox' or 'production'), and\nthe answer carries `case_id` and each document's `document_id`.\n\n`kind` (the client kind, as `/verify` takes it) picks the KYC or KYB\npolicy whose thresholds the document checks use; omitted, the KYC one.\n\nGuides: [Read documents](/guides/ocr-documents), [Document types and fields](/guides/document-types).",
        "operationId": "extract",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_extract_api_v1_kyc_extract_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                },
                "example": {
                  "fields": {
                    "first_name": "Test",
                    "last_name": "Client",
                    "document_holder_name": "Test Client",
                    "employer_name": "Test Employer SARL",
                    "occupation": "Analyst",
                    "document_date": "2026-09-30"
                  },
                  "documents": [
                    {
                      "filename": "payslip-test.pdf",
                      "doc_type": "payslip",
                      "step_hint": "payslip",
                      "step_key": null,
                      "fields": {
                        "first_name": "Test",
                        "last_name": "Client",
                        "document_holder_name": "Test Client",
                        "employer_name": "Test Employer SARL",
                        "occupation": "Analyst",
                        "document_date": "2026-09-30"
                      },
                      "meta_created": "2026-10-01",
                      "meta_provenance": {
                        "producer": "Example Payroll 4.2",
                        "revisions": 1
                      },
                      "mapped": 6,
                      "notes": [],
                      "document_id": "22222222-2222-4222-8222-222222222222"
                    }
                  ],
                  "field_count": 6,
                  "checks": [],
                  "reader_unavailable": false,
                  "policy": {
                    "id": null,
                    "version": 0,
                    "source": "legacy",
                    "regime": "none",
                    "regulator": null,
                    "purpose": "onboarding",
                    "overrides_refused": []
                  },
                  "case_id": "00000000-0000-4000-8000-000000000001",
                  "document_ids": [
                    "22222222-2222-4222-8222-222222222222"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Monthly document-read limit reached (`kyc_extract_cap_reached`), or the per-IP rate limit (`rate_limit_exceeded`, top-level `code`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_extract_cap_reached",
                    "message": "Monthly document-read limit of 2000 reached.",
                    "used": 2000,
                    "limit": 2000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not 1 to 5 files, unsupported file type, or content that does not match the declared type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Send between 1 and 5 files."
                }
              }
            }
          },
          "413": {
            "description": "File over the size limit (30 MB by default) or image over 50 megapixels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "File too large. Maximum allowed size is 30 MB."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/kyc/verify": {
      "post": {
        "tags": [
          "KYC"
        ],
        "summary": "Verify a profile",
        "description": "Required scope: `kyc:verify`.\n\nThe full verification verdict for a profile and its documents.\n\nGuide: [Verify a profile](/guides/verification).",
        "operationId": "verify",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal profile, no documents",
                  "description": "Runs as is in the playground. Expect passed true and a completeness warning.",
                  "value": {
                    "reference": "client-0001",
                    "environment": "sandbox",
                    "subject": "Test Client",
                    "kind": "individual",
                    "values": {
                      "first_name": "Test",
                      "last_name": "Client"
                    },
                    "documents": [],
                    "require_documents": false
                  }
                },
                "with_documents": {
                  "summary": "Profile with the documents entries from /extract",
                  "value": {
                    "reference": "client-0001",
                    "environment": "sandbox",
                    "subject": "Test Client",
                    "kind": "individual",
                    "values": {
                      "first_name": "Test",
                      "last_name": "Client",
                      "date_of_birth": "1988-04-12",
                      "citizenship": "MA",
                      "country": "MA",
                      "id_type": "National ID",
                      "id_number": "BK123456",
                      "id_expiry": "2030-05-01"
                    },
                    "documents": [
                      {
                        "filename": "cin-test.jpg",
                        "doc_type": "national_id",
                        "step_hint": "national_id",
                        "step_key": null,
                        "mapped": 9,
                        "notes": [],
                        "fields": {
                          "first_name": "Test",
                          "last_name": "Client",
                          "date_of_birth": "1988-04-12",
                          "id_type": "National ID",
                          "id_number": "BK123456",
                          "id_expiry": "2030-05-01",
                          "citizenship": "MA",
                          "id_country": "MA",
                          "document_holder_name": "Test Client"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResponse"
                },
                "example": {
                  "passed": true,
                  "checks": [
                    {
                      "id": "legible:national_id",
                      "label": "national_id — key fields readable",
                      "severity": "warning",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "expiry:national_id",
                      "label": "national_id — not expired",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "format:cin:national_id",
                      "label": "national_id — CIN number is well-formed",
                      "severity": "warning",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "adult:national_id",
                      "label": "national_id — holder is 18+",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "required:photo_id",
                      "label": "Document establishing the account holder provided",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "expiry:recorded:id_expiry",
                      "label": "The identity document on file is not expired",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "screening",
                      "label": "Sanctions screening — no matches; PEP not list-screened",
                      "severity": "info",
                      "passed": true,
                      "detail": "screened against 23000 sanctions entries. The bundle carries no PEP list, so politically-exposed status rests on the client's declaration, not on a list check."
                    },
                    {
                      "id": "completeness",
                      "label": "KYC/KYB data completeness (61%)",
                      "severity": "warning",
                      "passed": false,
                      "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                    }
                  ],
                  "critical_failures": [],
                  "flags": [
                    {
                      "id": "completeness",
                      "label": "KYC/KYB data completeness (61%)",
                      "severity": "warning",
                      "passed": false,
                      "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                    }
                  ],
                  "completeness": {
                    "required": 28,
                    "present": 17,
                    "missing": [
                      "street1",
                      "city",
                      "province",
                      "postal_code",
                      "phone",
                      "email",
                      "source_of_funds",
                      "account_type",
                      "third_party",
                      "sin/ssn",
                      "pep_foreign/pep_domestic/pep_hio/pep"
                    ],
                    "percent": 61
                  },
                  "policy": {
                    "id": null,
                    "version": 0,
                    "source": "legacy",
                    "regime": "none",
                    "regulator": null,
                    "purpose": "onboarding",
                    "overrides_refused": []
                  },
                  "registry": null,
                  "case_id": "00000000-0000-4000-8000-000000000001"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key, or an identity token that does not verify.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit: 100 requests a minute on these routes. Carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/kyc/assess": {
      "post": {
        "tags": [
          "KYC"
        ],
        "summary": "Verify and assess risk",
        "description": "Required scope: `kyc:verify`.\n\nThe verification verdict plus the risk assessment built on it.\n\nGuide: [Risk assessment](/guides/risk-assessment).",
        "operationId": "assess",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileRequest"
              },
              "examples": {
                "answers": {
                  "summary": "Profile with the suitability answers",
                  "description": "Risk tolerance needs objective, horizon, investment_knowledge and investment_experience. Capacity needs at least one of annual_income, net_liquid_assets, total_net_worth.",
                  "value": {
                    "reference": "client-0001",
                    "environment": "sandbox",
                    "subject": "Test Client",
                    "kind": "individual",
                    "require_documents": false,
                    "values": {
                      "first_name": "Test",
                      "last_name": "Client",
                      "country": "MA",
                      "citizenship": "MA",
                      "annual_income": "84000",
                      "net_liquid_assets": "20000",
                      "total_net_worth": "60000",
                      "objective": "Balanced",
                      "horizon": "5-10 years",
                      "investment_knowledge": "Good",
                      "investment_experience": "< 5 years"
                    },
                    "documents": []
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssessResponse"
                },
                "example": {
                  "verification": {
                    "passed": true,
                    "checks": [
                      {
                        "id": "legible:national_id",
                        "label": "national_id — key fields readable",
                        "severity": "warning",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "expiry:national_id",
                        "label": "national_id — not expired",
                        "severity": "critical",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "format:cin:national_id",
                        "label": "national_id — CIN number is well-formed",
                        "severity": "warning",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "adult:national_id",
                        "label": "national_id — holder is 18+",
                        "severity": "critical",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "required:photo_id",
                        "label": "Document establishing the account holder provided",
                        "severity": "critical",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "expiry:recorded:id_expiry",
                        "label": "The identity document on file is not expired",
                        "severity": "critical",
                        "passed": true,
                        "detail": ""
                      },
                      {
                        "id": "screening",
                        "label": "Sanctions screening — no matches; PEP not list-screened",
                        "severity": "info",
                        "passed": true,
                        "detail": "screened against 23000 sanctions entries. The bundle carries no PEP list, so politically-exposed status rests on the client's declaration, not on a list check."
                      },
                      {
                        "id": "completeness",
                        "label": "KYC/KYB data completeness (61%)",
                        "severity": "warning",
                        "passed": false,
                        "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                      }
                    ],
                    "critical_failures": [],
                    "flags": [
                      {
                        "id": "completeness",
                        "label": "KYC/KYB data completeness (61%)",
                        "severity": "warning",
                        "passed": false,
                        "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                      }
                    ],
                    "completeness": {
                      "required": 28,
                      "present": 17,
                      "missing": [
                        "street1",
                        "city",
                        "province",
                        "postal_code",
                        "phone",
                        "email",
                        "source_of_funds",
                        "account_type",
                        "third_party",
                        "sin/ssn",
                        "pep_foreign/pep_domestic/pep_hio/pep"
                      ],
                      "percent": 61
                    },
                    "policy": {
                      "id": null,
                      "version": 0,
                      "source": "legacy",
                      "regime": "none",
                      "regulator": null,
                      "purpose": "onboarding",
                      "overrides_refused": []
                    }
                  },
                  "assessment": {
                    "risk_profile": {
                      "score": 58,
                      "band": "Balanced",
                      "missing": []
                    },
                    "capacity": {
                      "score": 20,
                      "band": "Low",
                      "missing": []
                    },
                    "compliance_risk": {
                      "level": "Low",
                      "score": 1,
                      "factors": [
                        "Flag: KYC/KYB data completeness (61%) (missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type)"
                      ]
                    },
                    "suitability": "Suitable",
                    "risk_level": "Balanced",
                    "verification": {
                      "passed": true,
                      "checks": [
                        {
                          "id": "legible:national_id",
                          "label": "national_id — key fields readable",
                          "severity": "warning",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "expiry:national_id",
                          "label": "national_id — not expired",
                          "severity": "critical",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "format:cin:national_id",
                          "label": "national_id — CIN number is well-formed",
                          "severity": "warning",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "adult:national_id",
                          "label": "national_id — holder is 18+",
                          "severity": "critical",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "required:photo_id",
                          "label": "Document establishing the account holder provided",
                          "severity": "critical",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "expiry:recorded:id_expiry",
                          "label": "The identity document on file is not expired",
                          "severity": "critical",
                          "passed": true,
                          "detail": ""
                        },
                        {
                          "id": "screening",
                          "label": "Sanctions screening — no matches; PEP not list-screened",
                          "severity": "info",
                          "passed": true,
                          "detail": "screened against 23000 sanctions entries. The bundle carries no PEP list, so politically-exposed status rests on the client's declaration, not on a list check."
                        },
                        {
                          "id": "completeness",
                          "label": "KYC/KYB data completeness (61%)",
                          "severity": "warning",
                          "passed": false,
                          "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                        }
                      ],
                      "critical_failures": [],
                      "flags": [
                        {
                          "id": "completeness",
                          "label": "KYC/KYB data completeness (61%)",
                          "severity": "warning",
                          "passed": false,
                          "detail": "missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type"
                        }
                      ],
                      "completeness": {
                        "required": 28,
                        "present": 17,
                        "missing": [
                          "street1",
                          "city",
                          "province",
                          "postal_code",
                          "phone",
                          "email",
                          "source_of_funds",
                          "account_type",
                          "third_party",
                          "sin/ssn",
                          "pep_foreign/pep_domestic/pep_hio/pep"
                        ],
                        "percent": 61
                      }
                    }
                  },
                  "registry": null,
                  "case_id": "00000000-0000-4000-8000-000000000001"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit: 100 requests a minute on these routes. Carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/kyc/eid": {
      "post": {
        "tags": [
          "KYC"
        ],
        "summary": "Start an eID check",
        "description": "Required scope: `kyc:eid`.\n\nStart an eID check: the eID provider emails the client right away.\n\nThe request is filed, pending, on the case for (environment, reference):\nthat record, completed by `GET /eid/{key}`, is what the policy's eID\nrequirement reads. Nothing the partner sends to /verify can stand in for it.\n\nGuide: [eID check](/guides/eid). The provider emails the client as soon as this call succeeds, in sandbox too.",
        "operationId": "eid_create",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EidRequestIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created. The client has been emailed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EidCreated"
                },
                "example": {
                  "key": 123456,
                  "reference": "client-0001"
                }
              }
            }
          },
          "422": {
            "description": "Validation error, or a client that is not Canadian.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "identity verification is available for Canadian clients only"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit: 100 requests a minute on these routes. Carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          },
          "404": {
            "description": "The workspace has no eID provider account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "identity verification is not set up for this tenant"
                }
              }
            }
          },
          "502": {
            "description": "The eID provider did not answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "the identity verification service did not answer"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/v1/kyc/eid/{key}": {
      "get": {
        "tags": [
          "KYC"
        ],
        "summary": "Get an eID check",
        "description": "Required scope: `kyc:eid`.\n\nWhere the check stands, what it proved, and its checks for the verdict.\n\nWhen the check has finished, Sahl records the outcome on the case the\nrequest was filed on (`metadata_json.eid`), before the eID provider's seven-day\nretention runs out. `environment` only places a request started before\nthat record existed; one `POST /eid` filed keeps its own.\n\nGuide: [eID check](/guides/eid).",
        "operationId": "eid_result",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Key"
            },
            "description": "The `key` returned by `POST /v1/kyc/eid`.",
            "example": 123456
          },
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "sandbox",
                "production"
              ],
              "type": "string",
              "default": "sandbox",
              "title": "Environment"
            },
            "description": "Only places a request started before Sahl kept the eID record. A request made through `POST /v1/kyc/eid` keeps its own environment."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EidSummary"
                },
                "example": {
                  "key": 123456,
                  "complete": true,
                  "passed": true,
                  "identity": {
                    "documentType": "PASSPORT",
                    "documentNumber": "P1234567",
                    "expiryDate": "2030-05-01",
                    "birthDate": "1988-04-12",
                    "firstName": "Test",
                    "lastName": "Client"
                  },
                  "checks": [
                    {
                      "id": "eid:liveness",
                      "label": "Selfie passed the liveness check",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    },
                    {
                      "id": "eid:face_match",
                      "label": "Face on the ID matches the selfie (score 3 or more)",
                      "severity": "critical",
                      "passed": true,
                      "detail": "score 4 of 4, confidence 97%"
                    },
                    {
                      "id": "eid:name_match",
                      "label": "Name on the ID matches the name on the request",
                      "severity": "critical",
                      "passed": true,
                      "detail": ""
                    }
                  ],
                  "completed_date": "2026-10-07T12:00:00Z"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit: 100 requests a minute on these routes. Carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          },
          "404": {
            "description": "Unknown key, a key of another workspace, or no eID provider account (`identity verification is not set up for this tenant`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Not Found"
                }
              }
            }
          },
          "502": {
            "description": "The eID provider did not answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "the identity verification service did not answer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/kyc/eid/{key}/report": {
      "get": {
        "tags": [
          "KYC"
        ],
        "summary": "Download an eID report",
        "description": "Required scope: `kyc:eid`.\n\nthe eID provider's PDF report of a completed check, for the client's file.\n\nGuide: [eID check](/guides/eid).",
        "operationId": "eid_report",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Key"
            },
            "description": "The `key` returned by `POST /v1/kyc/eid`.",
            "example": 123456
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF report (`Content-Disposition: attachment; filename=\"eid-<key>.pdf\"`).",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Invalid or revoked API key"
                }
              }
            }
          },
          "403": {
            "description": "Key lacks the scope, or the workspace is not enabled for the partner KYC API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "kyc_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner KYC API."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit: 100 requests a minute on these routes. Carries `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorFlat"
                },
                "example": {
                  "code": "internal_error",
                  "message": "An unexpected error occurred",
                  "details": null
                }
              }
            }
          },
          "404": {
            "description": "Unknown key, a key of another workspace, or no eID provider account (`identity verification is not set up for this tenant`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Not Found"
                }
              }
            }
          },
          "502": {
            "description": "The eID provider did not answer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "the identity verification service did not answer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/partner/bank-connections": {
      "post": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "Create a bank connection",
        "operationId": "partner_bank_post",
        "description": "Required scope: `bank:write`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nSandbox only today: files a simulated, already connected account. In production this answers `409` `bank_connect_unavailable`.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankConnectionCreate"
              },
              "example": {
                "bank_code": "attijariwafa",
                "bank_name": "Attijariwafa bank",
                "case_id": "7e3b9a14-2d6c-4c85-b1f0-6a8d4e2c9b37"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankConnectionResponse"
                },
                "example": {
                  "id": "c1a4e2d7-93b5-4f08-8a6d-2e7b1f9c3d54",
                  "tenant_id": "0b6f5a3e-7c1d-4e0a-9d2f-3a1c5e8b7f10",
                  "case_id": "7e3b9a14-2d6c-4c85-b1f0-6a8d4e2c9b37",
                  "bank_code": "attijariwafa",
                  "bank_name": "Attijariwafa bank",
                  "account_holder": "Test Client",
                  "account_last4": "4821",
                  "status": "connected",
                  "link_token": "Zk3x...redacted",
                  "connected_at": "2026-10-07T09:14:22Z",
                  "expires_at": "2027-01-05T09:14:22Z",
                  "avg_balance": 21450.0,
                  "monthly_income": 18200.0,
                  "monthly_expenses": 11800.0,
                  "transaction_count": 96,
                  "cash_flow_data": null,
                  "environment": "sandbox",
                  "created_at": "2026-10-07T09:14:22Z",
                  "updated_at": "2026-10-07T09:14:22Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Production, or no Sandbox environment named: no bank data provider is live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_connect_unavailable",
                    "message": "Bank connection is not available yet. No bank data provider is live for this workspace."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      },
      "get": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "List bank connections",
        "operationId": "partner_bank_get",
        "description": "Required scope: `bank:read`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nThe workspace's connections, newest first.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 20,
              "title": "Page Size"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status"
            }
          },
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankConnectionListResponse"
                },
                "example": {
                  "items": [
                    {
                      "id": "c1a4e2d7-93b5-4f08-8a6d-2e7b1f9c3d54",
                      "tenant_id": "0b6f5a3e-7c1d-4e0a-9d2f-3a1c5e8b7f10",
                      "case_id": "7e3b9a14-2d6c-4c85-b1f0-6a8d4e2c9b37",
                      "bank_code": "attijariwafa",
                      "bank_name": "Attijariwafa bank",
                      "account_holder": "Test Client",
                      "account_last4": "4821",
                      "status": "connected",
                      "link_token": "Zk3x...redacted",
                      "connected_at": "2026-10-07T09:14:22Z",
                      "expires_at": "2027-01-05T09:14:22Z",
                      "avg_balance": 21450.0,
                      "monthly_income": 18200.0,
                      "monthly_expenses": 11800.0,
                      "transaction_count": 96,
                      "cash_flow_data": null,
                      "environment": "sandbox",
                      "created_at": "2026-10-07T09:14:22Z",
                      "updated_at": "2026-10-07T09:14:22Z"
                    }
                  ],
                  "total": 1,
                  "page": 1,
                  "page_size": 20
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    },
    "/v1/partner/bank-connections/stats": {
      "get": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "Count bank connections by status",
        "operationId": "partner_bank_get_stats",
        "description": "Required scope: `bank:read`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nCounts by status for the workspace.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankConnectionStats"
                },
                "example": {
                  "total": 3,
                  "connected": 2,
                  "pending": 1,
                  "expired": 0,
                  "failed": 0
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    },
    "/v1/partner/bank-connections/{connection_id}": {
      "get": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "Get a bank connection",
        "operationId": "partner_bank_get_connection_id",
        "description": "Required scope: `bank:read`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nOne connection of the key's workspace. Another workspace's id answers `404`.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "connection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Connection Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankConnectionResponse"
                },
                "example": {
                  "id": "c1a4e2d7-93b5-4f08-8a6d-2e7b1f9c3d54",
                  "tenant_id": "0b6f5a3e-7c1d-4e0a-9d2f-3a1c5e8b7f10",
                  "case_id": "7e3b9a14-2d6c-4c85-b1f0-6a8d4e2c9b37",
                  "bank_code": "attijariwafa",
                  "bank_name": "Attijariwafa bank",
                  "account_holder": "Test Client",
                  "account_last4": "4821",
                  "status": "connected",
                  "link_token": "Zk3x...redacted",
                  "connected_at": "2026-10-07T09:14:22Z",
                  "expires_at": "2027-01-05T09:14:22Z",
                  "avg_balance": 21450.0,
                  "monthly_income": 18200.0,
                  "monthly_expenses": 11800.0,
                  "transaction_count": 96,
                  "cash_flow_data": null,
                  "environment": "sandbox",
                  "created_at": "2026-10-07T09:14:22Z",
                  "updated_at": "2026-10-07T09:14:22Z"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown connection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Bank connection not found"
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    },
    "/v1/partner/bank-connections/{connection_id}/refresh": {
      "post": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "Refresh a bank connection",
        "operationId": "partner_bank_post_connection_id_refresh",
        "description": "Required scope: `bank:write`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nSandbox only today. Only a `connected` account can be refreshed (`400` otherwise). In production: `409` `bank_connect_unavailable`.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "connection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Connection Id"
            }
          },
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankConnectionResponse"
                },
                "example": {
                  "id": "c1a4e2d7-93b5-4f08-8a6d-2e7b1f9c3d54",
                  "tenant_id": "0b6f5a3e-7c1d-4e0a-9d2f-3a1c5e8b7f10",
                  "case_id": "7e3b9a14-2d6c-4c85-b1f0-6a8d4e2c9b37",
                  "bank_code": "attijariwafa",
                  "bank_name": "Attijariwafa bank",
                  "account_holder": "Test Client",
                  "account_last4": "4821",
                  "status": "connected",
                  "link_token": "Zk3x...redacted",
                  "connected_at": "2026-10-07T09:14:22Z",
                  "expires_at": "2027-01-05T09:14:22Z",
                  "avg_balance": 21450.0,
                  "monthly_income": 18200.0,
                  "monthly_expenses": 11800.0,
                  "transaction_count": 96,
                  "cash_flow_data": null,
                  "environment": "sandbox",
                  "created_at": "2026-10-07T09:14:22Z",
                  "updated_at": "2026-10-07T09:14:22Z"
                }
              }
            }
          },
          "400": {
            "description": "The connection is not `connected`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Can only refresh connected accounts"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown connection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Bank connection not found"
                }
              }
            }
          },
          "409": {
            "description": "Production, or no Sandbox environment named: no bank data provider is live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_connect_unavailable",
                    "message": "Bank connection is not available yet. No bank data provider is live for this workspace."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    },
    "/v1/partner/bank-connections/{connection_id}/transactions": {
      "get": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "List the transactions of a bank connection",
        "operationId": "partner_bank_get_connection_id_transactions",
        "description": "Required scope: `bank:read`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nSimulated transactions, sandbox only today. In production: `409` `bank_connect_unavailable`.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "connection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Connection Id"
            }
          },
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionListResponse"
                },
                "example": {
                  "items": [
                    {
                      "id": "txn-2026-09-salary",
                      "date": "2026-09-25",
                      "description": "VIREMENT SALAIRE OCP GROUP",
                      "amount": 18120.5,
                      "type": "credit",
                      "category": "salary",
                      "balance": 31240.75
                    },
                    {
                      "id": "txn-2026-09-000",
                      "date": "2026-09-03",
                      "description": "VIREMENT LOYER",
                      "amount": -4310.0,
                      "type": "debit",
                      "category": "rent",
                      "balance": 13120.25
                    }
                  ],
                  "total": 2,
                  "account_holder": "Test Client",
                  "bank_name": "Attijariwafa bank"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown connection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Bank connection not found"
                }
              }
            }
          },
          "409": {
            "description": "Production, or no Sandbox environment named: no bank data provider is live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_connect_unavailable",
                    "message": "Bank connection is not available yet. No bank data provider is live for this workspace."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    },
    "/v1/partner/bank-connections/{connection_id}/analysis": {
      "get": {
        "tags": [
          "Bank connections (Growth)"
        ],
        "summary": "Get the cash-flow analysis of a bank connection",
        "operationId": "partner_bank_get_connection_id_analysis",
        "description": "Required scope: `bank:read`.\n\nGrowth plan. Switched on per workspace by Sahl: until then a key cannot be issued with this scope and calls answer `403` `bank_scope_not_allowed`. No bank data provider is live: data is a sandbox simulation (send `X-Sahl-Environment: sandbox`), and in production create, refresh, transactions and analysis answer `409` `bank_connect_unavailable`.\n\nSimulated cash-flow analysis, sandbox only today. In production: `409` `bank_connect_unavailable`.\n\nGuide: [Bank connections and open banking](/guides/open-banking).",
        "parameters": [
          {
            "name": "connection_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Connection Id"
            }
          },
          {
            "name": "environment",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Environment"
            }
          },
          {
            "name": "X-Sahl-Environment",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Sahl-Environment"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankAnalysisResponse"
                },
                "example": {
                  "income_regularity": {
                    "score": 0.93,
                    "pattern": "monthly_25th",
                    "employer": "OCP GROUP",
                    "day_of_month": 25,
                    "consecutive_months": 9,
                    "risk": "low"
                  },
                  "risk_flags": {
                    "overdrafts": 0,
                    "bounced_checks": 0,
                    "gambling_transactions": 0,
                    "large_cash_withdrawals": 1,
                    "debt_payments_detected": 2,
                    "flagged_items": [
                      "Large cash withdrawal of 5000 MAD on 14/02"
                    ]
                  },
                  "savings_rate": 0.35,
                  "estimated_dti": 0.31,
                  "avg_end_of_month_balance": 9000.0,
                  "avg_balance": 21000.0,
                  "monthly_income": 18000.0,
                  "monthly_expenses": 11700.0
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, revoked or expired API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope (`insufficient_scope`), or the workspace is not enabled for the partner bank API (`bank_scope_not_allowed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_scope_not_allowed",
                    "message": "This workspace is not enabled for the partner bank API."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown connection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": "Bank connection not found"
                }
              }
            }
          },
          "409": {
            "description": "Production, or no Sandbox environment named: no bank data provider is live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": {
                    "code": "bank_connect_unavailable",
                    "message": "Bank connection is not available yet. No bank data provider is live for this workspace."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "detail": [
                    {
                      "type": "missing",
                      "loc": [
                        "body",
                        "bank_code"
                      ],
                      "msg": "Field required",
                      "input": {}
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "More than 100 requests per minute from one IP address. The body has no `detail`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                },
                "example": {
                  "code": "rate_limit_exceeded",
                  "message": "Too many requests. Please slow down."
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            }
          }
        },
        "x-mint": {
          "metadata": {
            "playground": "none"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Body_extract_api_v1_kyc_extract_post": {
        "properties": {
          "files": {
            "items": {
              "type": "string",
              "contentMediaType": "application/octet-stream",
              "format": "binary"
            },
            "type": "array",
            "title": "Files",
            "description": "1 to 5 files. JPEG, PNG, WebP, TIFF or PDF, up to 30 MB each. The content must match the declared type."
          },
          "doc_type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Doc Type",
            "description": "Hint for the reader about which document this is, for example `payslip` or `national_id`. Applies to every file in the call, so send one document type per call.",
            "examples": [
              "payslip"
            ]
          },
          "step_key": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Step Key",
            "description": "Stable name of your upload step, for example `photo_id` or `proof_of_address`. It decides which document types the step accepts. Optional.",
            "examples": [
              "proof_of_address"
            ]
          },
          "reference": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reference",
            "description": "Your own id for the client: 1 to 64 characters of `A-Z a-z 0-9 _ . : -`. With one, the files, the fields and the verdict are filed on a case in your workspace. Without one, nothing is filed.",
            "examples": [
              "client-0001"
            ]
          },
          "environment": {
            "type": "string",
            "title": "Environment",
            "default": "sandbox",
            "enum": [
              "sandbox",
              "production"
            ],
            "description": "`sandbox` (default) or `production`. Cases are separate per environment.",
            "examples": [
              "sandbox"
            ]
          },
          "subject": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Subject",
            "description": "Client name for the case, up to 255 characters.",
            "examples": [
              "Test Client"
            ]
          },
          "kind": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Kind",
            "description": "Client kind: `individual`, `corporation`, `partnership`, `charitable_org`, `trust`, `estate`. Picks the individual or entity policy thresholds. Omitted, the individual policy applies.",
            "examples": [
              "individual"
            ]
          }
        },
        "type": "object",
        "required": [
          "files"
        ],
        "title": "Extract request",
        "description": "1 to 5 files per call. JPEG, PNG, WebP, TIFF or PDF, up to 30 MB each."
      },
      "CheckIn": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "label": {
            "type": "string",
            "title": "Label"
          },
          "severity": {
            "type": "string",
            "pattern": "^(critical|warning|info)$",
            "title": "Severity",
            "description": "`critical`, `warning` or `info`."
          },
          "passed": {
            "type": "boolean",
            "title": "Passed"
          },
          "detail": {
            "type": "string",
            "title": "Detail",
            "default": ""
          }
        },
        "type": "object",
        "required": [
          "id",
          "label",
          "severity",
          "passed"
        ],
        "title": "CheckIn",
        "description": "A check you ran yourself, added to the verdict."
      },
      "EidRequestIn": {
        "properties": {
          "reference": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:-]+$",
            "title": "Reference",
            "description": "Your own id for the client, 1 to 64 characters of `A-Z a-z 0-9 _ . : -`."
          },
          "first_name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "First Name"
          },
          "last_name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Last Name"
          },
          "email": {
            "type": "string",
            "format": "email",
            "title": "Email",
            "description": "The provider emails the client a PIN and a link to this address as soon as the request is created."
          },
          "country": {
            "type": "string",
            "maxLength": 3,
            "minLength": 2,
            "title": "Country",
            "description": "Country of the client. Only Canada is accepted: `CA` or `CAN`."
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "fr"
            ],
            "title": "Language",
            "default": "en",
            "description": "Language of the client's email and screens: `en` or `fr`."
          },
          "documents": {
            "type": "integer",
            "enum": [
              1,
              2
            ],
            "title": "Documents",
            "default": 1,
            "description": "How many pieces of ID the client must scan: 1 or 2."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ],
            "title": "Environment",
            "default": "sandbox",
            "description": "Picks the case the request is filed on. It does not stop the provider from emailing the client."
          }
        },
        "type": "object",
        "required": [
          "reference",
          "first_name",
          "last_name",
          "email",
          "country"
        ],
        "title": "EidRequestIn",
        "example": {
          "reference": "client-0001",
          "first_name": "Test",
          "last_name": "Client",
          "email": "test.client@example.com",
          "country": "CA",
          "language": "en",
          "documents": 1,
          "environment": "sandbox"
        }
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "ProfileRequest": {
        "properties": {
          "reference": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_.:-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reference",
            "description": "Your own id for the client (see `/extract`). With one, the verdict is filed on the case for (workspace, environment, reference) and the answer carries `case_id`. Documents whose `document_id` came from `/extract` are linked to it."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ],
            "title": "Environment",
            "default": "sandbox",
            "description": "`sandbox` (default) or `production`."
          },
          "subject": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ],
            "title": "Subject",
            "description": "Client name (a person) or legal name (an entity), for the case. Up to 255 characters."
          },
          "values": {
            "additionalProperties": true,
            "type": "object",
            "title": "Values",
            "description": "The client profile, as an object of field keys to values: names, date_of_birth, address, id_type, id_number, id_expiry, occupation, income and the other data points. Unknown keys are ignored."
          },
          "documents": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Documents",
            "description": "The `documents` entries that `/extract` returned, sent back unchanged."
          },
          "kind": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Kind",
            "description": "Client kind: `individual`, `corporation`, `partnership`, `charitable_org`, `trust`, `estate` (aliases: entity, business, kyb, charity, fiducie, societe, succession). Decides which document establishes the account holder. Null falls back on `entity`."
          },
          "entity": {
            "type": "boolean",
            "title": "Entity",
            "default": false,
            "description": "True for a business (KYB). Used when `kind` is null."
          },
          "require_documents": {
            "type": "boolean",
            "title": "Require Documents",
            "default": true,
            "description": "Ask for the identity document. False is honoured only while the workspace policy does not lock identity verification, or on a periodic review. A refused switch is listed in `policy.overrides_refused`."
          },
          "screen": {
            "type": "boolean",
            "title": "Screen",
            "default": true,
            "description": "Sanctions screening of every party on the file. False is honoured only while the policy does not lock the sanctions screen."
          },
          "canadian_screening": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Canadian Screening",
            "description": "Extra Canadian AML and PEP screening for a Canadian client. Null means the workspace policy decides. It needs eID provider credentials on the workspace."
          },
          "extra_checks": {
            "items": {
              "$ref": "#/components/schemas/CheckIn"
            },
            "type": "array",
            "title": "Extra Checks",
            "description": "Checks only you can run (a duplicate client, your own blocklist). They are added to the verdict and can block it. An id starting `eid:` or `policy:` comes back prefixed `partner:`."
          },
          "purpose": {
            "type": "string",
            "enum": [
              "onboarding",
              "periodic_review"
            ],
            "title": "Purpose",
            "default": "onboarding",
            "description": "`onboarding` (default) or `periodic_review`. A review does not re-verify identity; screening and every other lock still apply."
          }
        },
        "type": "object",
        "title": "ProfileRequest",
        "description": "A profile and the per-file extractions that back it."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "FastAPI error body. `detail` is a string, or an object with `code` and `message` (extra keys possible).",
        "properties": {
          "detail": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "additionalProperties": true,
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            ]
          }
        },
        "required": [
          "detail"
        ]
      },
      "Check": {
        "type": "object",
        "title": "Check",
        "description": "One verification result. `passed: false` with severity `critical` blocks the file; `warning` is a flag for a person; `info` is kept for the audit trail.",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable id, for example `expiry:national_id`. Some ids end in a document label or a party name."
          },
          "label": {
            "type": "string",
            "description": "Human sentence."
          },
          "severity": {
            "type": "string",
            "enum": [
              "critical",
              "warning",
              "info"
            ]
          },
          "passed": {
            "type": "boolean"
          },
          "detail": {
            "type": "string",
            "description": "Why it failed. Empty when it passed."
          }
        },
        "required": [
          "id",
          "label",
          "severity",
          "passed",
          "detail"
        ]
      },
      "PolicyBlock": {
        "type": "object",
        "title": "PolicyBlock",
        "description": "Which workspace policy the call ran under, and which request switches it refused.",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Saved policy id, or null when none is saved."
          },
          "version": {
            "type": "integer"
          },
          "source": {
            "type": "string",
            "enum": [
              "tenant",
              "preset",
              "legacy",
              "fallback"
            ],
            "description": "`tenant` a saved policy, `preset` a regime preset, `legacy` the default, `fallback` a saved policy that no longer validates."
          },
          "regime": {
            "type": "string"
          },
          "regulator": {
            "type": [
              "string",
              "null"
            ]
          },
          "purpose": {
            "type": "string",
            "enum": [
              "onboarding",
              "periodic_review"
            ]
          },
          "overrides_refused": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "requested": {
                  "type": "boolean"
                },
                "enforced": {
                  "type": "boolean"
                },
                "locked_item": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "id",
          "version",
          "source",
          "regime",
          "purpose",
          "overrides_refused"
        ]
      },
      "Completeness": {
        "type": "object",
        "title": "Completeness",
        "description": "How many of the required data points for this client kind are present in `values`. The `completeness` check is a warning below 80 percent.",
        "properties": {
          "required": {
            "type": "integer"
          },
          "present": {
            "type": "integer"
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Field keys. A group such as `sin/ssn` counts once."
          },
          "percent": {
            "type": "integer"
          }
        },
        "required": [
          "required",
          "present",
          "missing",
          "percent"
        ]
      },
      "ExtractedDocument": {
        "type": "object",
        "title": "ExtractedDocument",
        "description": "One read file. Send these entries back to `/verify` and `/assess` unchanged, in `documents`.",
        "properties": {
          "filename": {
            "type": "string"
          },
          "doc_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "What the reader says the document is: passport, national_id, drivers_license, pr_card, residence_permit, utility_bill, proof_of_address, bank_statement, void_cheque, bank_letter, invoice, payslip, articles_of_incorporation, business_registration, bylaws, beneficial_ownership, directors_register, board_resolution, financial_statements, trust_deed, beneficiary_list, or other. Other strings are lowercased with `_`; null when it could not be named."
          },
          "step_hint": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `doc_type` you sent."
          },
          "step_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `step_key` you sent."
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Fields read from this file. Values are strings. A field the reader could not read is absent."
          },
          "meta_created": {
            "type": [
              "string",
              "null"
            ],
            "description": "Creation date from the file metadata (PDF CreationDate, or image EXIF), `YYYY-MM-DD`."
          },
          "meta_provenance": {
            "type": "object",
            "additionalProperties": true,
            "description": "What the file says about how it was made: `producer`, `creator`, `modified`, `revisions` for a PDF; `creator`, `camera` for an image. Can be empty."
          },
          "mapped": {
            "type": "integer",
            "description": "Number of fields read from this file."
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Changes the server made to the reader's answer, with the reason."
          },
          "document_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a `reference`."
          }
        },
        "required": [
          "filename",
          "doc_type",
          "step_hint",
          "fields",
          "mapped",
          "notes"
        ]
      },
      "ExtractResponse": {
        "type": "object",
        "title": "ExtractResponse",
        "properties": {
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Fields merged across the files of the call. The first non-empty value wins, in file order, so send the strongest identity document first."
          },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExtractedDocument"
            }
          },
          "field_count": {
            "type": "integer",
            "description": "Number of keys in `fields`."
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            },
            "description": "The per-document checks of every file, in one list."
          },
          "reader_unavailable": {
            "type": "boolean",
            "description": "True when at least one file was never read (a problem on the Sahl side). It does not mean the document was blank."
          },
          "policy": {
            "$ref": "#/components/schemas/PolicyBlock"
          },
          "case_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a `reference`."
          },
          "document_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Only with a `reference`. Same order as `documents`."
          }
        },
        "required": [
          "fields",
          "documents",
          "field_count",
          "checks",
          "reader_unavailable",
          "policy"
        ]
      },
      "VerifyResponse": {
        "type": "object",
        "title": "VerifyResponse",
        "properties": {
          "passed": {
            "type": "boolean",
            "description": "True when no critical check failed. Warnings do not change it."
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            }
          },
          "critical_failures": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            },
            "description": "Checks with `passed: false` and severity `critical`."
          },
          "flags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            },
            "description": "Everything that needs a person: failed critical and warning checks."
          },
          "completeness": {
            "$ref": "#/components/schemas/Completeness"
          },
          "policy": {
            "$ref": "#/components/schemas/PolicyBlock"
          },
          "registry": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "What Corporations Canada lists for a federal (CBCA) corporation, normalised. Null when the registry was not consulted or the corporation was not found."
          },
          "case_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a `reference`."
          }
        },
        "required": [
          "passed",
          "checks",
          "critical_failures",
          "flags",
          "completeness",
          "policy",
          "registry"
        ]
      },
      "Assessment": {
        "type": "object",
        "title": "Assessment",
        "properties": {
          "risk_profile": {
            "type": "object",
            "properties": {
              "score": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "0 to 100. Null when the answers it needs are missing."
              },
              "band": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Conservative",
                  "Moderate",
                  "Balanced",
                  "Growth",
                  "Aggressive",
                  null
                ]
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Inputs that were not provided."
              }
            },
            "description": "Risk tolerance from objective, horizon, investment_knowledge and investment_experience. Withheld (null) unless all four are answered."
          },
          "capacity": {
            "type": "object",
            "properties": {
              "score": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "0 to 100. Null when the answers it needs are missing."
              },
              "band": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Low",
                  "Moderate",
                  "High",
                  "Very High",
                  null
                ]
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Inputs that were not provided."
              }
            },
            "description": "Financial capacity from annual_income, net_liquid_assets and total_net_worth. Withheld (null) when none of the three is present."
          },
          "compliance_risk": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string",
                "enum": [
                  "Low",
                  "Medium",
                  "High"
                ]
              },
              "score": {
                "type": "integer",
                "description": "Risk points."
              },
              "factors": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "level",
              "score",
              "factors"
            ]
          },
          "suitability": {
            "type": "string",
            "description": "One of: `Blocked — document verification failed`, `Review — objective exceeds capacity`, `Enhanced due diligence required`, `Incomplete — suitability answers missing`, `Incomplete — financial capacity answers missing`, `Suitable`."
          },
          "risk_level": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `risk_profile` band."
          },
          "verification": {
            "type": "object",
            "additionalProperties": true,
            "description": "The verdict the assessment was built on (same as `verification` at the top level, without `policy`)."
          }
        },
        "required": [
          "risk_profile",
          "capacity",
          "compliance_risk",
          "suitability",
          "risk_level"
        ]
      },
      "AssessResponse": {
        "type": "object",
        "title": "AssessResponse",
        "properties": {
          "verification": {
            "type": "object",
            "properties": {
              "passed": {
                "type": "boolean",
                "description": "True when no critical check failed. Warnings do not change it."
              },
              "checks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Check"
                }
              },
              "critical_failures": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Check"
                },
                "description": "Checks with `passed: false` and severity `critical`."
              },
              "flags": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Check"
                },
                "description": "Everything that needs a person: failed critical and warning checks."
              },
              "completeness": {
                "$ref": "#/components/schemas/Completeness"
              },
              "policy": {
                "$ref": "#/components/schemas/PolicyBlock"
              }
            },
            "required": [
              "passed",
              "checks",
              "critical_failures",
              "flags",
              "completeness",
              "policy"
            ]
          },
          "assessment": {
            "$ref": "#/components/schemas/Assessment"
          },
          "registry": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "case_id": {
            "type": "string",
            "format": "uuid",
            "description": "Only with a `reference`."
          }
        },
        "required": [
          "verification",
          "assessment",
          "registry"
        ]
      },
      "EidCreated": {
        "type": "object",
        "title": "EidCreated",
        "properties": {
          "key": {
            "type": "integer",
            "description": "Id of the eID request. Use it to poll."
          },
          "reference": {
            "type": "string"
          }
        },
        "required": [
          "key",
          "reference"
        ]
      },
      "EidSummary": {
        "type": "object",
        "title": "EidSummary",
        "properties": {
          "key": {
            "type": "integer"
          },
          "complete": {
            "type": "boolean",
            "description": "True once the client has scanned a document."
          },
          "passed": {
            "type": "boolean",
            "description": "True when complete and no critical eID check failed."
          },
          "identity": {
            "type": "object",
            "additionalProperties": true,
            "description": "What the check proved. Possible keys: documentType, documentNumber, expiryDate, birthDate, firstName, lastName, address. Empty until complete."
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Check"
            }
          },
          "completed_date": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key",
          "complete",
          "passed",
          "identity",
          "checks",
          "completed_date"
        ]
      },
      "ErrorFlat": {
        "type": "object",
        "title": "ErrorFlat",
        "description": "Body of a rate-limit 429, an unknown route 404 and a 500: `code` and `message` at the top level, with no `detail`.",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present on a 500 only; null unless the server runs in debug mode."
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "BankAnalysisResponse": {
        "properties": {
          "income_regularity": {
            "$ref": "#/components/schemas/IncomeRegularity"
          },
          "risk_flags": {
            "$ref": "#/components/schemas/RiskFlags"
          },
          "savings_rate": {
            "type": "number"
          },
          "estimated_dti": {
            "type": "number"
          },
          "avg_end_of_month_balance": {
            "type": "number"
          },
          "avg_balance": {
            "type": "number"
          },
          "monthly_income": {
            "type": "number"
          },
          "monthly_expenses": {
            "type": "number"
          }
        },
        "type": "object",
        "required": [
          "income_regularity",
          "risk_flags",
          "savings_rate",
          "estimated_dti",
          "avg_end_of_month_balance",
          "avg_balance",
          "monthly_income",
          "monthly_expenses"
        ]
      },
      "BankConnectionCreate": {
        "properties": {
          "bank_code": {
            "type": "string",
            "maxLength": 50
          },
          "bank_name": {
            "type": "string",
            "maxLength": 100
          },
          "case_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "bank_code",
          "bank_name"
        ]
      },
      "BankConnectionListResponse": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/BankConnectionResponse"
            },
            "type": "array"
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "page_size": {
            "type": "integer"
          }
        },
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "page_size"
        ]
      },
      "BankConnectionResponse": {
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "case_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ]
          },
          "bank_code": {
            "type": "string"
          },
          "bank_name": {
            "type": "string"
          },
          "account_holder": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "account_last4": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string"
          },
          "link_token": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "connected_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ]
          },
          "avg_balance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "monthly_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "monthly_expenses": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "transaction_count": {
            "type": "integer"
          },
          "cash_flow_data": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ]
          },
          "environment": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "type": "object",
        "required": [
          "id",
          "tenant_id",
          "case_id",
          "bank_code",
          "bank_name",
          "account_holder",
          "account_last4",
          "status",
          "link_token",
          "connected_at",
          "expires_at",
          "avg_balance",
          "monthly_income",
          "monthly_expenses",
          "transaction_count",
          "cash_flow_data",
          "environment",
          "created_at",
          "updated_at"
        ]
      },
      "BankConnectionStats": {
        "properties": {
          "total": {
            "type": "integer"
          },
          "connected": {
            "type": "integer"
          },
          "pending": {
            "type": "integer"
          },
          "expired": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        },
        "type": "object",
        "required": [
          "total",
          "connected",
          "pending",
          "expired",
          "failed"
        ]
      },
      "IncomeRegularity": {
        "properties": {
          "score": {
            "type": "number"
          },
          "pattern": {
            "type": "string"
          },
          "employer": {
            "type": "string"
          },
          "day_of_month": {
            "type": "integer"
          },
          "consecutive_months": {
            "type": "integer"
          },
          "risk": {
            "type": "string"
          }
        },
        "type": "object",
        "required": [
          "score",
          "pattern",
          "employer",
          "day_of_month",
          "consecutive_months",
          "risk"
        ]
      },
      "RateLimitError": {
        "type": "object",
        "description": "Top-level `code` and `message`, no `detail`.",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "RiskFlags": {
        "properties": {
          "overdrafts": {
            "type": "integer"
          },
          "bounced_checks": {
            "type": "integer"
          },
          "gambling_transactions": {
            "type": "integer"
          },
          "large_cash_withdrawals": {
            "type": "integer"
          },
          "debt_payments_detected": {
            "type": "integer"
          },
          "flagged_items": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object",
        "required": [
          "overdrafts",
          "bounced_checks",
          "gambling_transactions",
          "large_cash_withdrawals",
          "debt_payments_detected",
          "flagged_items"
        ]
      },
      "TransactionItem": {
        "properties": {
          "id": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "type": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "balance": {
            "type": "number"
          }
        },
        "type": "object",
        "required": [
          "id",
          "date",
          "description",
          "amount",
          "type",
          "category",
          "balance"
        ]
      },
      "TransactionListResponse": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/TransactionItem"
            },
            "type": "array"
          },
          "total": {
            "type": "integer"
          },
          "account_holder": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "bank_name": {
            "type": "string"
          }
        },
        "type": "object",
        "required": [
          "items",
          "total",
          "account_holder",
          "bank_name"
        ]
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created in the console. Scopes: kyc:extract, kyc:verify, kyc:eid; bank:read, bank:write (Growth plan, enabled per workspace by Sahl)."
      }
    }
  },
  "tags": [
    {
      "name": "KYC",
      "description": "Read documents, verify a profile, assess risk, start an eID check."
    },
    {
      "name": "Bank connections (Growth)",
      "description": "Growth plan, switched on per workspace by Sahl. Sandbox simulation today; production answers 409 until a bank data provider is live."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ]
}