> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sahlfinancial.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Évaluation du risque

> POST /v1/kyc/assess : entrées, tables de points, bandes et règle d'adéquation, exactement comme le code les calcule.

`POST /v1/kyc/assess` prend la [même requête que `/verify`](/fr/guides/verification#requête), exécute la même vérification, et construit une évaluation par-dessus. Scope : `kyc:verify`. Ce n'est pas un score de crédit. Il renvoie quatre indicateurs pour un conseiller : tolérance au risque, capacité financière, risque de conformité et adéquation.

Chaque nombre de cette page est calculé par une fonction déterministe de `values` et du verdict. Même entrée, même sortie.

## Réponse

```json theme={null}
{
  "verification": { "passed": true, "checks": [], "critical_failures": [], "flags": [], "completeness": {}, "policy": {} },
  "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): ...)"] },
    "suitability": "Suitable",
    "risk_level": "Balanced",
    "verification": { "passed": true }
  },
  "registry": null,
  "case_id": "00000000-0000-4000-8000-000000000001"
}
```

`verification` est raccourci ici ; c'est le verdict complet de `/verify`. L'évaluation répète aussi le verdict sous `assessment.verification`. L'exemple est la sortie réelle du moteur pour des données fictives (voir le [pas-à-pas](/fr/guides/walkthrough)).

| Clé | Signification |
| - | - |
| `verification` | Le verdict de `/verify`, avec `policy`. |
| `assessment.risk_profile` | La tolérance au risque du client, de 0 à 100, et sa bande. |
| `assessment.capacity` | La capacité financière du client, de 0 à 100, et sa bande. |
| `assessment.compliance_risk` | Niveau LBC et de diligence raisonnable : `level`, `score` en points, `factors` en mots. |
| `assessment.suitability` | Une phrase tirée d'une liste fixe. |
| `assessment.risk_level` | La bande de `risk_profile`. Ce n'est **pas** le niveau de conformité. |
| `registry`, `case_id` | Comme dans `/verify`. |

## Tolérance au risque

Exige les quatre réponses dans `values`. Si l'une est vide, `score` et `band` valent `null` et `missing` liste les clés absentes. Aucun score partiel n'est produit et aucune valeur par défaut n'est supposée.

| Entrée | Valeurs acceptées et points |
| - | - |
| `objective` (insensible à la casse) | `capital preservation` 5, `income` 25, `balanced` 50, `growth` 78, `aggressive growth` 95 |
| `horizon` | `< 3 years` 12, `3-5 years` 38, `5-10 years` 68, `> 10 years` 92 |
| `investment_knowledge` | `None` 10, `Limited` 40, `Good` 70, `Excellent` 92 |
| `investment_experience` | `None` 15, `< 5 years` 50, `> 5 years` 85 |

Une réponse présente mais absente de la table obtient une valeur par défaut : objectif 50, horizon 50, connaissances 40, expérience 40. Utilisez les chaînes exactes ci-dessus.

```text theme={null}
score = objective x 0.35 + horizon x 0.25 + knowledge x 0.20 + experience x 0.20
```

Si `uses_leverage` vaut `true` (booléen), `"true"` ou `"Yes"`, ajoutez 8. Le résultat est arrondi et borné entre 0 et 100.

| Score | Bande |
| - | - |
| moins de 20 | Conservative |
| 20 à 39 | Moderate |
| 40 à 59 | Balanced |
| 60 à 79 | Growth |
| 80 et plus | Aggressive |

Exemple : balanced (50), 5-10 years (68), Good (70), moins de 5 ans (50) donne 50 x 0.35 + 68 x 0.25 + 70 x 0.20 + 50 x 0.20 = 58.5, arrondi à 58, `Balanced`.

## Capacité

Exige au moins l'un de `annual_income`, `net_liquid_assets`, `total_net_worth`. Sans aucun, `score` et `band` valent `null`. Avec un ou deux, le score est calculé et `missing` liste les autres. Un montant manquant compte pour une valeur de 0, qui tombe dans la tranche la plus basse (sous-score 12 ou 15), donc une réponse manquante tire le score vers le bas. Les valeurs sont lues à partir de chaînes comme `84000`, `150,000`, `$1.2M` ou `84k`.

| Revenu annuel | Sous-score | Actifs liquides nets | Sous-score | Valeur nette totale | Sous-score |
| - | - | - | - | - | - |
| moins de 50 000 | 15 | moins de 50 000 | 12 | moins de 100 000 | 15 |
| moins de 100 000 | 35 | moins de 250 000 | 35 | moins de 500 000 | 38 |
| moins de 250 000 | 55 | moins de 1 000 000 | 60 | moins de 2 000 000 | 62 |
| moins de 1 000 000 | 80 | moins de 5 000 000 | 82 | moins de 10 000 000 | 85 |
| 1 000 000 et plus | 95 | 5 000 000 et plus | 96 | 10 000 000 et plus | 97 |

```text theme={null}
score = income x 0.30 + liquid x 0.35 + net worth x 0.35
```

| Score | Bande |
| - | - |
| moins de 30 | Low |
| 30 à 54 | Moderate |
| 55 à 79 | High |
| 80 et plus | Very High |

Exemple : revenu 84 000 (35), liquidités 20 000 (12), valeur nette 60 000 (15) donne 10.5 + 4.2 + 5.25 = 19.95, arrondi à 20, `Low`. Les montants de l'exemple sont lus en MAD. Le moteur lit les montants comme de simples nombres, avec des tranches fixes qui ne dépendent pas de la devise : 84 000 donne le même score dans toute devise.

L'API ne convertit pas les devises. Les tranches sont dans l'unité que vous envoyez.

## Risque de conformité

Les points s'additionnent à partir du profil et du verdict.

| Facteur | Points |
| - | - |
| Personne politiquement exposée : l'une des clés `pep`, `pep_foreign`, `pep_domestic`, `pep_hio` égale à `yes` (toute casse), ou une correspondance PEP issue du filtrage | 2 (une seule fois, même si les deux) |
| Géographie : le pire niveau parmi `citizenships` (ou `citizenship`), `country` et `entity_country` (avec repli sur `country`) | prohibited 6, high 3, elevated 1, standard 0 |
| `high_risk_jurisdiction` égal à `yes` | 1 |
| `industry` vaut `Virtual assets / Crypto`, `Money services business`, `Gaming` ou `Cannabis` | 2 |
| `third_party` égal à `yes` | 1 |
| Chaque vérification critique échouée dans le verdict | 3, et le niveau est forcé à High |
| Chaque vérification d'avertissement échouée dans le verdict | 1 |

| Niveau | Règle |
| - | - |
| High | Une vérification critique a échoué, ou plus de 3 points |
| Medium | 2 ou 3 points |
| Low | 0 ou 1 point |

Une politique de l'espace de travail peut abaisser le plafond de Low (1 par défaut) et celui de Medium (3 par défaut), jamais les relever. `factors` liste chaque contribution en mots, par exemple `Flag: <label> (<detail>)` ou `Verification failed: <label> (<detail>)`.

Les vérifications info n'ajoutent rien. Notez qu'un avertissement compte même si le client ne peut pas le corriger, comme `completeness` sous 80 pour cent : un dossier maigre marque un point.

### Niveaux géographiques

Dérivés des listes publiques du GAFI au 19 juin 2026 (`FATF_LISTS_AS_OF`). L'ensemble est un instantané dans le code et il est mis à jour après chaque plénière du GAFI. Les codes sont ISO alpha-2. Quelques codes alpha-3 et noms sont compris (`IRN`, `iran`, `usa`, `canada`). Tout le reste compte comme standard.

| Niveau | Points | Codes |
| - | - | - |
| prohibited | 6 | `IR`, `KP`, `MM`, `SY`, `CU` |
| high | 3 | `AO`, `BO`, `BA`, `BG`, `CM`, `CI`, `CD`, `HT`, `IQ`, `KE`, `KW`, `LA`, `LB`, `MC`, `NP`, `PG`, `SS`, `VE`, `VN`, `VG`, `YE` |
| elevated | 1 | `PA`, `SC`, `KY`, `BZ` |
| standard | 0 | tous les autres codes |

Un pays prohibited seul marque 6, ce qui donne High. N'écrivez pas cette table en dur dans votre application : elle change à chaque publication du GAFI.

## Adéquation

La première règle qui correspond l'emporte.

| Ordre | Règle | `suitability` |
| - | - | - |
| 1 | `verification.passed` est false | `Blocked — document verification failed` |
| 2 | le score de tolérance au risque est de 75 ou plus et le score de capacité est inférieur à 40 | `Review — objective exceeds capacity` |
| 3 | le niveau de conformité est High | `Enhanced due diligence required` |
| 4 | tolérance au risque retenue | `Incomplete — suitability answers missing` |
| 5 | capacité retenue | `Incomplete — financial capacity answers missing` |
| 6 | sinon | `Suitable` |

`Suitable` est un indicateur pour un conseiller, pas une détermination réglementaire d'adéquation. C'est la firme qui rend cette détermination.

## Exemples détaillés

Un dossier complet et propre donne :

```json theme={null}
{
  "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"
}
```

Le même dossier avec une carte nationale expirée (les échecs critiques ajoutent 3 points chacun et forcent High) :

```json theme={null}
{
  "level": "High",
  "score": 7,
  "factors": [
    "Verification failed: national_id — not expired (expired 2024-01-01)",
    "Verification failed: The identity document on file is not expired (expired 2024-01-01)",
    "Flag: KYC/KYB data completeness (61%) (missing 11 required data point(s): street1, city, province, postal_code, phone, email, source_of_funds, account_type)"
  ]
}
```

Son `suitability` est `Blocked — document verification failed`, et `risk_level` indique toujours `Balanced`.

## Exemple de requête

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.sahlfinancial.com/api/v1/kyc/assess \
    -H "Authorization: Bearer $SAHL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "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": []
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch("https://app.sahlfinancial.com/api/v1/kyc/assess", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAHL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      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: [],
    }),
  });
  const { assessment } = await res.json();
  console.log(assessment.risk_profile.band, assessment.capacity.band, assessment.compliance_risk.level, assessment.suitability);
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://app.sahlfinancial.com/api/v1/kyc/assess",
      headers={"Authorization": f"Bearer {os.environ['SAHL_API_KEY']}"},
      json={
          "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": [],
      },
      timeout=60,
  )
  res.raise_for_status()
  a = res.json()["assessment"]
  print(a["risk_profile"]["band"], a["capacity"]["band"], a["compliance_risk"]["level"], a["suitability"])
  ```
</CodeGroup>

Avec `documents: []` et `require_documents: false`, l'exemple ci-dessus n'a aucune vérification de document, donc le risque de conformité dépend de l'avertissement `completeness` et de la ligne de filtrage de votre environnement. Les `risk_profile` et `capacity` attendus sont les mêmes que ci-dessus.

## Résumé des webhooks

L'événement `kyc.case_assessed` transporte `risk_level` et `suitability` dans son résumé `verdict`. Là, `risk_level` est `compliance_risk.level` (`Low`, `Medium`, `High`), pas la bande de tolérance au risque. Voir [Webhooks](/fr/guides/webhooks).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.