# Soci senza codice fiscale

> Una persona si identifica con il codice fiscale oppure con data, luogo e Stato di nascita. Cosa inviare per chi è nato all'estero, come si conferma un codice che non torna, cosa mostrare all'ingresso.

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

Per diventare socio di un'associazione **non serve un codice fiscale italiano**. Quello che serve è che la persona
sia identificata, e su Vimine lo è in uno di due modi:

- con un **codice fiscale**, oppure
- con i suoi **dati di nascita**: `dob` + `birthplace` + `birth_country` (ISO 3166-1 alpha-2).

Mai nessuno dei due. Il resto di questa guida è che cosa cambia, campo per campo, per un'integrazione che iscrive
persone — studenti in scambio, volontari arrivati da poco, soci che vivono all'estero.

## Identificare una persona

`POST /v1/enroll` e `POST /v1/signup` accettano gli stessi campi:

| Campo | |
|---|---|
| `codicefiscale` | facoltativo. Se c'è, viene validato e confrontato con nome, cognome, data e luogo di nascita |
| `dob` | data di nascita, `YYYY-MM-DD` |
| `birthplace` | il comune per chi è nato in Italia; la città, a testo libero, per chi è nato all'estero |
| `birth_country` | Stato di nascita, due lettere (`IT`, `FR`, `SN`…) |
| `gender` | solo statistico: **non viene più confrontato** con il codice |

```js
// Senza codice fiscale: data, luogo e Stato di nascita
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 }
});
```

Su `/v1/enroll`, senza codice, ogni dato che manca è un `422 validation_failed` con una voce in `errors[]`:

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

`/v1/signup` crea solo l'account: lì i dati di nascita restano facoltativi, e serviranno quando la persona chiederà
una tessera.

## Nati all'estero: invia `birth_country`

Il codice fiscale di chi è nato in Italia contiene il comune di nascita; quello di chi è nato all'estero contiene
**solo lo Stato**. Per questo il confronto, per loro, guarda `birth_country` e tratta `birthplace` — la città — come
testo libero. Inviare `birth_country` è ciò che fa passare una persona nata all'estero con un codice valido: senza,
«Lyon» verrebbe cercata tra i comuni italiani e il codice risulterebbe incoerente. (Un modulo che finora inviava il
nome dello Stato come luogo di nascita — `birthplace: 'Francia'` — continua a passare; `birth_country` è la forma da
preferire.)

Lo stesso vale per il controllo pubblico, che ora dice anche che cosa il codice contiene:

```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` e `birth_country` sono letti dal codice stesso: puoi usarli per precompilare il modulo. Questo endpoint
confronta ancora `gender`, se lo invii; l'iscrizione e il profilo no.

## Un codice che non torna, e la conferma

Un codice scritto male (struttura o carattere di controllo) è `invalid_codicefiscale`: va corretto, non si può
confermare. Un codice ben formato che **non concorda** con i dati inseriti risponde invece `422` con una voce più
ricca:

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

- `mismatches` — che cosa non concorda: `lastName`, `firstName`, `date`, `place`;
- `hints` — `names_swapped` quando il codice torna scambiando nome e cognome: di solito basta proporre lo scambio;
- `confirmable` — se la persona può **confermare** che quello è il codice della sua tessera sanitaria. Succede
  davvero: comuni soppressi, traslitterazioni, nomi registrati in un altro ordine.

Quando `confirmable` è `true`, chiedilo alla persona e ripeti la richiesta con `codicefiscale_confirmed: true`.
Quando nome, cognome **e** data non concordano tutti insieme, nulla lega più quel codice a quella persona:
`confirmable` è `false` e l'unica strada è correggere.

```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 });
    }
}
```

Non inviare `codicefiscale_confirmed` di tua iniziativa: è una dichiarazione della persona, e viene registrata come
tale.

## Aggiungere il codice più tardi

Chi si è iscritto senza codice può aggiungerlo quando lo ottiene, dal proprio profilo:

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

`PATCH /v1/me` accetta `birth_country` e, solo in scrittura, `codicefiscale_confirmed`; `codicefiscale` si può anche
riportare a `null`. `GET /v1/me` dice come la persona è identificata:

| `codicefiscale_check` | |
|---|---|
| `matched` | un codice fiscale che concorda con i dati della persona |
| `mismatch_confirmed` | un codice che non concorda, confermato dalla persona |
| `absent` | nessun codice: la identificano data, luogo e Stato di nascita |
| `null` | non ancora identificata: non può chiedere una tessera |

`matched` vuol dire **coerente**, non verificato presso l'anagrafe tributaria: il codice fiscale resta un dato
dichiarato.

## `identification_required`

Un account può esistere senza essere identificato — chi si è registrato solo per accedere, per esempio. Quando una
persona così chiede una tessera, l'API lo dice e nomina che cosa manca:

- **`POST /v1/me/membership`** → `422 identification_required`, con `errors[]` = i dati mancanti
  (`{ field: 'birthplace', code: 'required' }`…). Manda la persona a completare il profilo con `PATCH /v1/me`
  (permesso `profile:write`) e ripeti la richiesta.
- **`POST /v1/enroll/confirm`** — il link «conferma la richiesta» che riceve il titolare di un account esistente →
  `409 identification_required`, stesso `errors[]`. Chiedi i dati nella pagina e invia di nuovo il token con
  `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? } }
```

I dati viaggiano **nel corpo della richiesta, mai nel link**: nel link non c'è nulla di personale. `identification`
completa solo i campi vuoti del profilo, non sovrascrive quelli che ci sono; se non regge, la risposta è un
`422 validation_failed` come sopra.

L'ammissione non cambia: `pending_reasons` resta quello che dice il modello di ammissione dell'associazione, per
chiunque (vedi [Ammissione e attivazione](https://developers.vimine.network/guides/admission/)).

## All'ingresso: il controllo d'identità

Un codice fiscale che concorda con i dati si può riscontrare; i dati di nascita da soli, o un codice confermato dalla
persona, no. Per questi soci l'associazione guarda un documento **una volta**, la prima volta che la persona è
presente. Chi integra uno scanner lo vede in `POST /v1/checkins` (permesso `checkins:write`), nelle risposte per una
tessera:

```js
const r = await vimineServer.checkins.create('vmt_…');
// { kind: 'card', verdict: 'valid', …, identity_check: 'pending' }
if (r.identity_check === 'pending') show('Primo ingresso: chiedi un documento');
```

| `identity_check` | |
|---|---|
| `pending` | il socio non ha un codice fiscale verificabile e nessuno dell'associazione ha ancora visto un documento: **mostralo** a chi è all'ingresso |
| `verified` | qualcuno dell'associazione ha visto un documento |
| `flagged` | qualcuno l'ha visto e non corrispondeva: se ne occupa la segreteria |
| `null` | niente da controllare |

È una **nota**, non fa parte di `verdict` e **non limita nulla**: una tessera `valid` con `identity_check: 'pending'`
entra. L'esito si registra dalla console dell'associazione, non dall'API. La verifica pubblica
`GET /v1/verify/{card_token}` non lo riporta mai: chi controlla una tessera da fuori vede solo stato e scadenza.

## L'indirizzo

Alcune associazioni tengono l'indirizzo dei soci nel proprio libro, altre no. La scelta è dell'associazione e vale per
**tutti i soci o per nessuno**; il profilo pubblico la dichiara:

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

| `member_address` | |
|---|---|
| `none` | non chiederlo: un `address` inviato comunque non viene conservato |
| `optional` | chiedilo, senza obbligo |
| `required` | senza `address` l'iscrizione risponde `422` con `{ 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` e `city` sono obbligatori dentro `address`; `kind` vale `residence` (predefinito) o `domicile`, quando è
diverso dalla residenza; `country` è `IT` se omesso; `province` riguarda solo gli indirizzi italiani. `/v1/signup`
non ha `address`: l'indirizzo appartiene all'adesione, non all'account.

## Un'associazione non può pretendere il codice

Non esiste un'impostazione «codice fiscale obbligatorio». Un'associazione non può chiedere il codice fiscale a ogni
persona che fa domanda: chi non ce l'ha si identifica con i dati di nascita, ovunque. Se il tuo modulo lo rende
obbligatorio, è una scelta del tuo modulo — e lascia fuori persone che l'associazione potrebbe accogliere.

## Cosa è cambiato (versione 2026-09-19)

Tutto additivo: **un modulo che invia sempre un codice fiscale continua a funzionare senza toccare nulla**. Quello
che un'integrazione esistente può notare:

- `codicefiscale` non è più obbligatorio in `POST /v1/enroll`; senza codice servono `dob`, `birthplace` e
  `birth_country` (`422` `required` su ciascuno che manca);
- nuovi campi facoltativi `birth_country`, `codicefiscale_confirmed` (enroll, signup, `PATCH /v1/me`) e `address`
  (enroll);
- `gender` non viene più confrontato con il codice: un `422` per il solo sesso non arriva più;
- un codice che non concorda risponde sempre `codicefiscale_mismatch`, ora con `hints` e `confirmable` accanto a
  `mismatches`; un codice malformato resta `invalid_codicefiscale`;
- `GET`/`PATCH /v1/me` hanno ora uno schema dichiarato (`Me`), con `birth_country` e `codicefiscale_check`;
- nuovo errore `identification_required`: `422` su `POST /v1/me/membership`, `409` su `POST /v1/enroll/confirm` (che
  accetta `identification` nel corpo);
- `POST /v1/validate/fiscal-code` accetta `country` e risponde anche `hints`, `birth_date`, `birth_country`; `error`
  vale `invalid`, `mismatch` o `null`;
- `POST /v1/checkins` aggiunge `identity_check` alle risposte per una tessera; `GET /v1/verify/{card_token}` resta
  com'era;
- il profilo pubblico aggiunge `aps.member_address`.
