# Membres sans code fiscal

> Une personne est identifiée par un code fiscal ou par sa date, son lieu et son pays de naissance. Quoi envoyer pour les personnes nées à l’étranger, comment confirmer un code en désaccord, quoi afficher à l’entrée.

URL: https://developers.vimine.network/fr/guides/members-without-cf/

Adhérer à une association **n’exige pas de code fiscal italien** (codice fiscale). Ce qu’il faut, c’est que la personne
soit identifiée, et sur Vimine elle l’est de l’une de ces deux façons :

- par un **code fiscal**, ou
- par ses **données de naissance** : `dob` + `birthplace` + `birth_country` (ISO 3166-1 alpha-2).

Jamais ni l’un ni l’autre. La suite de ce guide décrit ce qui change, champ par champ, pour une intégration qui fait
adhérer des personnes — étudiants en échange, bénévoles tout juste arrivés, membres qui vivent à l’étranger.

## Identifier une personne

`POST /v1/enroll` et `POST /v1/signup` acceptent les mêmes champs :

| Champ | |
|---|---|
| `codicefiscale` | facultatif. Quand il est présent, il est validé et comparé avec les noms, la date et le lieu de naissance |
| `dob` | date de naissance, `YYYY-MM-DD` |
| `birthplace` | la commune (comune) pour une personne née en Italie ; la ville, en texte libre, pour une personne née à l’étranger |
| `birth_country` | pays de naissance, deux lettres (`IT`, `FR`, `SN`…) |
| `gender` | démographique seulement : il **n’est plus comparé** avec le code |

```js
// Sans code fiscal : date, lieu et pays de naissance
await vimineServer.enroll({
    email: 'marie.dupont@example.org',
    first_name: 'Marie',
    last_name: 'Dupont',
    dob: '1985-03-10',
    birthplace: 'Lyon',
    birth_country: 'FR',
    accept_statuto: { version: statuto.version, sha256: statuto.sha256 }
});
```

Sur `/v1/enroll`, sans code, chaque donnée de naissance manquante est un `422 validation_failed` avec une entrée dans
`errors[]` :

```json
{ "code": "validation_failed", "errors": [{ "field": "birth_country", "code": "required" }] }
```

`/v1/signup` ne crée que le compte : les données de naissance y restent facultatives, et seront nécessaires quand la
personne demandera une adhésion.

## Né à l’étranger : envoyez `birth_country`

Le code fiscal d’une personne née en Italie contient la commune de naissance ; celui d’une personne née à l’étranger ne
contient **que l’État**. Pour ces personnes, la comparaison porte donc sur `birth_country` et traite `birthplace` — la
ville — comme du texte libre. Envoyer `birth_country` est ce qui permet à une personne née à l’étranger avec un code
valable de passer : sans lui, « Lyon » serait cherché parmi les communes italiennes et le code paraîtrait incohérent.
(Un formulaire qui envoyait jusqu’ici le nom italien de l’État comme lieu de naissance — `birthplace: 'Francia'` —
continue de passer ; `birth_country` est la forme à préférer.)

Il en va de même pour la vérification publique, qui dit aussi désormais ce que le code contient :

```js
const r = await publicApi().validateFiscalCode({
    code: 'DPNMRA85C50Z110U', first_name: 'Marie', last_name: 'Dupont', date: '1985-03-10', place: 'Lyon', country: 'FR'
});
// { valid: true, error: null, mismatches: [], hints: [], birth_date: '1985-03-10', birth_country: 'FR' }
```

`birth_date` et `birth_country` sont lus dans le code lui-même : vous pouvez les utiliser pour préremplir le
formulaire. Cet endpoint compare encore `gender`, quand vous l’envoyez ; l’adhésion et le profil, non.

## Un code en désaccord, et la confirmation

Un code mal saisi (structure ou caractère de contrôle) est `invalid_codicefiscale` : il doit être corrigé, il ne peut
jamais être confirmé. Un code bien formé qui est **en désaccord** avec les données saisies répond en revanche `422` avec
une entrée plus riche :

```json
{
  "code": "validation_failed",
  "errors": [{
    "field": "codicefiscale",
    "code": "codicefiscale_mismatch",
    "mismatches": ["place"],
    "hints": [],
    "confirmable": true
  }]
}
```

- `mismatches` — ce qui ne concorde pas : `lastName`, `firstName`, `date`, `place` ;
- `hints` — `names_swapped` quand le code correspond une fois le nom et le prénom intervertis : proposer l’inversion
  suffit généralement ;
- `confirmable` — si la personne peut **confirmer** que c’est bien le code inscrit sur sa carte d’assurance maladie
  (tessera sanitaria). Cela arrive : communes qui n’existent plus, translittérations, noms enregistrés dans un autre
  ordre.

Quand `confirmable` vaut `true`, demandez à la personne et répétez la requête avec `codicefiscale_confirmed: true`.
Quand les deux noms **et** la date sont tous en désaccord, plus rien ne relie ce code à cette personne : `confirmable`
vaut `false` et la seule issue est de le corriger.

```js
import { VimineError } from '@vimine/sdk/server';

try {
    await vimineServer.enroll(input);
} catch (err) {
    const entry = err instanceof VimineError && err.errors?.find((e) => e.code === 'codicefiscale_mismatch');
    if (!entry) throw err;
    if (entry.confirmable && (await askThePerson(entry.mismatches, entry.hints))) {
        await vimineServer.enroll({ ...input, codicefiscale_confirmed: true });
    }
}
```

N’envoyez pas `codicefiscale_confirmed` de votre propre initiative : c’est une déclaration de la personne, et elle est
enregistrée comme telle.

## Ajouter le code plus tard

Une personne qui a adhéré sans code peut l’ajouter quand elle en a un, depuis son propre profil :

```js
const me = await vimine.me.update({ codicefiscale: 'DPNMRA85C50Z110U' });
me.codicefiscale_check;   // 'matched'
```

`PATCH /v1/me` accepte `birth_country` et `codicefiscale_confirmed`, en écriture seule ; `codicefiscale` peut aussi être
remis à `null`. `GET /v1/me` dit comment la personne est identifiée :

| `codicefiscale_check` | |
|---|---|
| `matched` | un code fiscal qui concorde avec les données de naissance de la personne |
| `mismatch_confirmed` | un code en désaccord, confirmé par la personne |
| `absent` | pas de code : la date, le lieu et le pays de naissance l’identifient |
| `null` | pas encore identifiée : elle ne peut pas demander d’adhésion |

`matched` signifie **cohérent**, pas vérifié auprès de l’administration fiscale : un code fiscal reste une donnée
déclarée.

## `identification_required`

Un compte peut exister sans être identifié — une personne qui s’est inscrite seulement pour se connecter, par exemple.
Quand une telle personne demande une adhésion, l’API le dit et nomme ce qui manque :

- **`POST /v1/me/membership`** → `422 identification_required`, avec `errors[]` = les données de naissance manquantes
  (`{ field: 'birthplace', code: 'required' }`…). Envoyez la personne compléter son profil avec `PATCH /v1/me`
  (autorisation `profile:write`) et répétez la requête.
- **`POST /v1/enroll/confirm`** — le lien « confirmez votre demande » que reçoit le titulaire d’un compte existant →
  `409 identification_required`, même `errors[]`. Demandez les données de naissance sur la page et renvoyez le jeton
  avec `identification` :

```js
await publicApi().confirmEnrollment(token, { dob: '1985-03-10', birthplace: 'Lyon', birth_country: 'FR' });
// POST /v1/enroll/confirm  { token, identification: { dob, birthplace, birth_country, codicefiscale?, codicefiscale_confirmed? } }
```

Les données de naissance voyagent **dans le corps de la requête, jamais dans le lien** : rien de personnel n’est jamais
dans le lien. `identification` ne fait que compléter les champs vides du profil, elle n’écrase pas ce qui s’y trouve ;
si elle ne tient pas, la réponse est un `422 validation_failed` comme ci-dessus.

L’admission ne change pas : les `pending_reasons` sont ce que dit le modèle d’admission de l’association, pour tout le
monde (voir [Admission et activation](https://developers.vimine.network/fr/guides/admission/)).

## À l’entrée, le contrôle d’identité

Un code fiscal qui concorde avec les données de naissance peut être vérifié par rapport à elles ; les données de
naissance seules, ou un code que la personne a confirmé, non. Pour ces membres, l’association regarde une pièce
d’identité **une fois**, la première fois que la personne est là. Qui intègre un scanner le voit dans
`POST /v1/checkins` (autorisation `checkins:write`), dans les réponses pour une carte :

```js
const r = await vimineServer.checkins.create('vmt_…');
// { kind: 'card', verdict: 'valid', …, identity_check: 'pending' }
if (r.identity_check === 'pending') show('Première entrée : demandez une pièce d’identité');
```

| `identity_check` | |
|---|---|
| `pending` | le membre n’a pas de code fiscal vérifiable et personne de l’association n’a encore regardé une pièce d’identité : **affichez-le** à la personne qui tient l’entrée |
| `verified` | quelqu’un de l’association a regardé une pièce d’identité |
| `flagged` | quelqu’un en a regardé une et elle ne concordait pas : le secrétariat de l’association prend la suite |
| `null` | rien à vérifier |

C’est une **indication**, elle ne fait pas partie de `verdict` et elle **ne restreint rien** : une carte `valid` avec
`identity_check: 'pending'` entre. Le résultat est enregistré depuis la console de l’association, pas depuis l’API. La
vérification publique `GET /v1/verify/{card_token}` ne le porte jamais : qui contrôle une carte depuis l’extérieur ne
voit que l’état et l’échéance.

## L’adresse

Certaines associations conservent l’adresse de leurs membres dans leur registre, d’autres non. Le choix appartient à
l’association et vaut pour **tous les membres ou pour aucun** ; le profil public le déclare :

```
GET https://api.vimine.network/v1/aps/{slug}
→ { "aps": { …, "member_address": "optional" } }
```

| `member_address` | |
|---|---|
| `none` | ne la demandez pas : une `address` envoyée malgré tout n’est pas conservée |
| `optional` | demandez-la, sans la rendre obligatoire |
| `required` | sans `address`, l’adhésion répond `422` avec `{ field: 'address', code: 'required' }` |

```js
await vimineServer.enroll({
    …,
    address: { kind: 'residence', line: 'Via Roma 1', postal_code: '20121', city: 'Milano', province: 'MI', country: 'IT' }
});
```

`line` et `city` sont obligatoires dans `address` ; `kind` vaut `residence` (par défaut) ou `domicile`, quand il
diffère de la résidence ; `country` vaut `IT` s’il est omis ; `province` ne concerne que les adresses italiennes.
`/v1/signup` n’a pas d’`address` : l’adresse appartient à l’adhésion, pas au compte.

## Une association ne peut pas exiger le code

Il n’existe pas de réglage « code fiscal obligatoire ». Une association ne peut pas exiger un code fiscal de chaque
personne qui adhère : qui n’en a pas est identifié par ses données de naissance, partout. Si votre formulaire le rend
obligatoire, c’est un choix de votre formulaire — et il laisse de côté des personnes que l’association pourrait
accueillir.

## Ce qui a changé (version 2026-09-19)

Tout est additif : **un formulaire qui envoie toujours un code fiscal continue de fonctionner sans changement**. Ce
qu’une intégration existante peut remarquer :

- `codicefiscale` n’est plus obligatoire dans `POST /v1/enroll` ; sans code, il faut `dob`, `birthplace` et
  `birth_country` (`422` `required` sur chacun de ceux qui manquent) ;
- nouveaux champs facultatifs `birth_country`, `codicefiscale_confirmed` (enroll, signup, `PATCH /v1/me`) et `address`
  (enroll) ;
- `gender` n’est plus comparé avec le code : un `422` portant sur le seul sexe n’arrive plus ;
- un code en désaccord répond toujours `codicefiscale_mismatch`, désormais avec `hints` et `confirmable` à côté de
  `mismatches` ; un code malformé reste `invalid_codicefiscale` ;
- `GET`/`PATCH /v1/me` ont désormais un schéma déclaré (`Me`), avec `birth_country` et `codicefiscale_check` ;
- une nouvelle erreur `identification_required` : `422` sur `POST /v1/me/membership`, `409` sur
  `POST /v1/enroll/confirm` (qui accepte `identification` dans le corps) ;
- `POST /v1/validate/fiscal-code` accepte `country` et répond aussi `hints`, `birth_date`, `birth_country` ; `error`
  vaut `invalid`, `mismatch` ou `null` ;
- `POST /v1/checkins` ajoute `identity_check` aux réponses pour une carte ; `GET /v1/verify/{card_token}` reste
  inchangé ;
- le profil public ajoute `aps.member_address`.
