Members without a fiscal code
Joining an association does not take an Italian fiscal code (codice fiscale). What it takes is that the person is identified, and on Vimine they are in one of two ways:
- by a fiscal code, or
- by their particulars:
dob+birthplace+birth_country(ISO 3166-1 alpha-2).
Never neither. The rest of this guide is what changes, field by field, for an integration that enrols people — exchange students, volunteers who have just arrived, members who live abroad.
Identifying a person
Section titled “Identifying a person”POST /v1/enroll and POST /v1/signup take the same fields:
| Field | |
|---|---|
codicefiscale |
optional. When present it is validated and cross-checked against the names, the date and the place of birth |
dob |
date of birth, YYYY-MM-DD |
birthplace |
the comune for a person born in Italy; the city, as free text, for a person born abroad |
birth_country |
country of birth, two letters (IT, FR, SN…) |
gender |
demographic only: it is no longer cross-checked against the code |
// No fiscal code: date, place and country of birthawait 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 }});On /v1/enroll, without a code, each missing particular is a 422 validation_failed with an entry in errors[]:
{ "code": "validation_failed", "errors": [{ "field": "birth_country", "code": "required" }] }/v1/signup creates the account only: the particulars stay optional there, and will be needed when the person asks
for a membership.
Born abroad: send birth_country
Section titled “Born abroad: send birth_country”The fiscal code of a person born in Italy holds the comune of birth; that of a person born abroad holds the state
only. So for them the cross-check compares birth_country and treats birthplace — the city — as free text.
Sending birth_country is what makes a person born abroad with a valid code pass: without it, “Lyon” would be looked
up among the Italian comuni and the code would come out inconsistent. (A form that so far sent the Italian name of the
state as the place of birth — birthplace: 'Francia' — keeps passing; birth_country is the form to prefer.)
The same holds for the public check, which now also says what the code holds:
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 and birth_country are read from the code itself: you can use them to prefill the form. This endpoint
still compares gender, when you send it; enrolment and the profile do not.
A code that disagrees, and the confirmation
Section titled “A code that disagrees, and the confirmation”A mistyped code (structure or check character) is invalid_codicefiscale: it has to be corrected, it can never be
confirmed. A well-formed code that disagrees with the data entered answers 422 with a richer entry instead:
{ "code": "validation_failed", "errors": [{ "field": "codicefiscale", "code": "codicefiscale_mismatch", "mismatches": ["place"], "hints": [], "confirmable": true }]}mismatches— what disagrees:lastName,firstName,date,place;hints—names_swappedwhen the code matches once surname and given name are exchanged: offering the swap is usually enough;confirmable— whether the person may confirm that this is the code on their health card. It does happen: comuni that no longer exist, transliterations, names registered in another order.
When confirmable is true, ask the person and repeat the request with codicefiscale_confirmed: true. When both
names and the date all disagree, nothing ties that code to that person any more: confirmable is false and the
only way forward is to correct it.
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 }); }}Do not send codicefiscale_confirmed on your own initiative: it is a statement of the person, and it is recorded as
one.
Adding the code later
Section titled “Adding the code later”Someone who joined without a code can add it once they have one, from their own profile:
const me = await vimine.me.update({ codicefiscale: 'DPNMRA85C50Z110U' });me.codicefiscale_check; // 'matched'PATCH /v1/me accepts birth_country and the write-only codicefiscale_confirmed; codicefiscale may also be set
back to null. GET /v1/me says how the person is identified:
codicefiscale_check |
|
|---|---|
matched |
a fiscal code that agrees with the person’s particulars |
mismatch_confirmed |
a code that disagrees, confirmed by the person |
absent |
no code: date, place and country of birth identify them |
null |
not identified yet: they cannot ask for a membership |
matched means consistent, not verified with the tax registry: a fiscal code stays a declared datum.
identification_required
Section titled “identification_required”An account may exist without being identified — someone who registered only to sign in, for example. When such a person asks for a membership, the API says so and names what is missing:
POST /v1/me/membership→422 identification_required, witherrors[]= the missing particulars ({ field: 'birthplace', code: 'required' }…). Send the person to complete their profile withPATCH /v1/me(permissionprofile:write) and repeat the request.POST /v1/enroll/confirm— the “confirm your request” link the owner of an existing account receives →409 identification_required, sameerrors[]. Ask for the particulars on the page and post the token again withidentification:
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? } }The particulars travel in the request body, never in the link: nothing personal is ever in the link.
identification only completes the empty fields of the profile, it does not overwrite what is there; if it does not
hold, the answer is a 422 validation_failed as above.
Admission does not change: pending_reasons are whatever the association’s admission model says, for everybody (see
Admission and activation).
At the gate: the identity check
Section titled “At the gate: the identity check”A fiscal code that agrees with the particulars can be checked against them; the particulars alone, or a code the
person confirmed, cannot. For these members the association looks at a document once, the first time the person
is there. Whoever integrates a scanner sees it in POST /v1/checkins (permission checkins:write), in the answers
for a card:
const r = await vimineServer.checkins.create('vmt_…');// { kind: 'card', verdict: 'valid', …, identity_check: 'pending' }if (r.identity_check === 'pending') show('First entry: ask for a document');identity_check |
|
|---|---|
pending |
the member has no verifiable fiscal code and nobody of the association has looked at a document yet: show it to whoever staffs the gate |
verified |
somebody of the association has looked at a document |
flagged |
somebody looked at one and it did not agree: the association’s office takes it from there |
null |
nothing to check |
It is a note, it is not part of verdict and it restricts nothing: a valid card with
identity_check: 'pending' goes in. The outcome is recorded from the association’s console, not from the API. The
public verification GET /v1/verify/{card_token} never carries it: whoever checks a card from outside sees status and
expiry only.
The address
Section titled “The address”Some associations keep their members’ address in their register, others do not. The choice is the association’s and holds for every member or for nobody; the public profile declares it:
GET https://api.vimine.network/v1/aps/{slug}→ { "aps": { …, "member_address": "optional" } }member_address |
|
|---|---|
none |
do not ask: an address sent anyway is not stored |
optional |
ask, without making it mandatory |
required |
without an address enrolment answers 422 with { field: 'address', code: 'required' } |
await vimineServer.enroll({ …, address: { kind: 'residence', line: 'Via Roma 1', postal_code: '20121', city: 'Milano', province: 'MI', country: 'IT' }});line and city are required inside address; kind is residence (the default) or domicile, where it differs
from the residence; country is IT when omitted; province concerns Italian addresses only. /v1/signup has no
address: the address belongs to the membership, not to the account.
An association cannot demand the code
Section titled “An association cannot demand the code”There is no “fiscal code required” setting. An association cannot demand a fiscal code from every applicant: whoever has none is identified by their particulars, everywhere. If your form makes it mandatory, that is a choice of your form — and it leaves out people the association could welcome.
What changed (version 2026-09-19)
Section titled “What changed (version 2026-09-19)”All additive: a form that always sends a fiscal code keeps working untouched. What an existing integration may notice:
codicefiscaleis no longer required inPOST /v1/enroll; without a code,dob,birthplaceandbirth_countryare needed (422requiredon each missing one);- new optional fields
birth_country,codicefiscale_confirmed(enroll, signup,PATCH /v1/me) andaddress(enroll); genderis no longer cross-checked against the code: a422about the sex alone does not come any more;- a code that disagrees still answers
codicefiscale_mismatch, now withhintsandconfirmablenext tomismatches; a malformed code staysinvalid_codicefiscale; GET/PATCH /v1/menow have a declared schema (Me), withbirth_countryandcodicefiscale_check;- a new error
identification_required:422onPOST /v1/me/membership,409onPOST /v1/enroll/confirm(which acceptsidentificationin the body); POST /v1/validate/fiscal-codeacceptscountryand also answershints,birth_date,birth_country;errorisinvalid,mismatchornull;POST /v1/checkinsaddsidentity_checkto the answers for a card;GET /v1/verify/{card_token}stays as it was;- the public profile adds
aps.member_address.