# Types

> Les types TypeScript du SDK — les mêmes structures JSON que l’API v1, noms de champs inchangés.

URL: https://developers.vimine.network/fr/sdk/types/

Les types sont exportés par `@vimine/sdk` (et par `/server`) et reflètent le document OpenAPI : les noms des champs
sont ceux du JSON. Les structures complètes sont dans la [référence de l’API](https://developers.vimine.network/api/) ; voici les principales.

## Autorisations et états

```ts
type Scope =
    | 'profile:read' | 'profile:write'
    | 'membership:read' | 'membership:request' | 'statuto:accept' | 'network:manage'
    | 'memberships:read' | 'verify:read'
    | 'identity:signup' | 'enroll:write' | 'checkins:write' | 'webhooks:manage'
    | 'events:read' | 'registrations:read' | 'registrations:write';

type MembershipStatus = 'pending' | 'active' | 'suspended' | 'revoked' | 'rejected';
/** Ce que vaut la carte en ce moment. */
type MembershipVerdict = 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive';
```

## Le membre

```ts
interface Me {
    sub: string;                  // identifiant pour votre association (vms_…)
    email: string;
    email_verified: boolean;
    first_name: string | null;
    last_name: string | null;
    phone: string | null;
    dob: string | null;           // YYYY-MM-DD
    gender: 'M' | 'F' | null;     // démographique seulement : il n’est pas comparé avec le code
    birthplace: string | null;    // comune (né en Italie) ou ville, texte libre (né à l’étranger)
    birth_country: string | null; // ISO 3166-1 alpha-2
    codicefiscale: string | null; // facultatif : une personne peut ne pas en avoir
    codicefiscale_check: CodiceFiscaleCheck | null;   // comment la personne est identifiée ; null = pas encore
    locale: Locale | null;        // la langue choisie par la personne ; null = jamais choisie
    updated_at: string | null;
}

/** Une langue que Vimine parle. Partout où l’API en accepte une, elle accepte aussi une étiquette régionale (`en-GB` → `en`, `zh-TW` → `zh-Hant`) et la ramène à sa langue. */
type Locale = 'it' | 'en' | 'zh-Hans' | 'zh-Hant' | 'fr' | 'es' | 'de' | 'pt-BR';
interface LocaleParam { locale?: Locale | string }   // ?locale= des lectures — voir le guide Langues

/** Comment une personne est identifiée : par un code fiscal OU par sa date, son lieu et son pays de naissance — jamais ni l’un ni l’autre. */
type CodiceFiscaleCheck = 'matched' | 'mismatch_confirmed' | 'absent';

interface MeUpdate {              // PATCH /v1/me
    phone?: string | null;
    dob?: string | null;
    gender?: 'M' | 'F' | null;
    birthplace?: string | null;
    birth_country?: string | null;            // ISO 3166-1 alpha-2
    codicefiscale?: string | null;            // peut être ajouté plus tard, ou remis à null
    codicefiscale_confirmed?: boolean;        // écriture seule : la personne confirme un code en désaccord (après un `confirmable: true`)
    locale?: Locale | null;                   // null efface le choix ; 422 `unsupported` pour une langue que Vimine ne parle pas
}
```

## La carte

```ts
interface Membership {
    id: string;
    status: MembershipStatus;
    verdict: MembershipVerdict;
    subdate: string | null;
    issued_at: string | null;
    expires_at: string | null;
    membership_type: { uid: string; cod: string; name: string } | null;
    statuto: {
        current_version: number | null;
        current_sha256: string | null;
        accepted_version_id: string | null;
        accepted_at: string | null;
        pending: boolean;         // un statuto plus récent a été publié depuis que le membre en a accepté un
        overdue: boolean;         // … et le délai pour l’accepter de nouveau est écoulé
    };
    pending_reasons: Array<'approval' | 'payment'>;   // pourquoi elle est encore en attente, dans l’ordre : l’admission, puis la cotisation
    payment_open: boolean;        // la cotisation peut-elle être payée maintenant ? (l’admission ouvre le paiement)
    valid_from: string | null;    // quand la carte devient utilisable (le délai de l’association)
    usable: boolean;              // la carte fait foi à l’entrée en ce moment
    quota: { amount: number; currency: string; paid: boolean } | null;
    payment_instructions: string | null;
    expires_in_days: number | null;
    renewal: { window_open: boolean; requested_at: string | null };   // renouvellement par le membre : ouvert 60 jours avant l’échéance
    network_opt_in: boolean;
    card: { token: string; nbf: number; exp: number } | null;   // le jeton vmt_… du QR et sa fenêtre (unix s)
}

interface Card { token: string; nbf: number; exp: number; status: MembershipStatus }

interface MembershipRequest {     // POST /v1/me/membership
    membership_type?: string;     // uid ; facultatif quand l’association en propose exactement un
    accept_statuto: { version: number; sha256?: string };
}
type MembershipRequestResult =
    | { created: true; status: 'pending'; id: string }
    | { already_requested: true; status: MembershipStatus };

interface MembershipEvents {
    items: Array<{ from: MembershipStatus | null; to: MembershipStatus; reason: string | null; changed_at: string; channel: string }>;
    next_cursor: string | null;
}

interface StatutoInfo {
    uid: string;
    version: number;
    approvaldate: string | null;
    sha256: string | null;
    content: string | null;
    document_url: string | null;
    accepted: boolean;
    accepted_at: string | null;
    overdue: boolean;
}

interface Connection {
    client_id: string;
    name: string;
    kind: 'public' | 'native' | 'confidential' | 'first_party';
    scopes: Scope[];
    granted_at: string;
}
```

## Public

```ts
interface Verification {
    status: 'active' | 'inactive';
    valid_until?: string;
    valid_from?: string;          // avec reason 'not_yet_valid' : la carte est authentique et active, et devient utilisable à partir d’ici
    reason?: 'expired' | 'invalid' | 'not_yet_valid';
}

type AdmissionModel = 'board' | 'provisional' | 'automatic';   // comment l’association admet ses membres

interface AssociationPublicConfig {
    aps: {
        slug: string; name: string;
        picture_url: string | null;
        citta: string | null; provincia: string | null;
        enroll_enabled: boolean;
        accepting_requests: boolean;  // l’adhésion est ouverte ET il y a de la place pour un membre de plus, en ce moment
        invitation_only: boolean;     // bêta privée : ne prend que les personnes qu’elle a invitées (accepting_requests vaut false)
        // comment l’association admet ses membres — dites-le d’emblée à qui demande à adhérer. board : le conseil décide, la cotisation après ;
        // provisional : admis tout de suite, ratifié plus tard ; automatic : active dès que les conditions sont remplies.
        // delay_hours : heures entre l’admission et une carte UTILISABLE (0 = tout de suite)
        admission: { model: AdmissionModel; delay_hours: number };
        approval_required: boolean;   // obsolète : true quand admission.model vaut 'board' — lisez `admission`
        payment_instructions: string | null;   // comment payer la cotisation, quand l’association l’a publié
        member_address: MemberAddressPolicy;   // si l’association recueille l’adresse de ses membres — de tous ou d’aucun
        // activités d’intérêt général (art. 5 du Code du tiers secteur) : lettre, libellé, laquelle est prépondérante
        activity_sectors: Array<{ cod: string; description: string; prevalent: boolean }>;
        // inscription au RUNTS d’après les listes officielles, sinon null
        registry: {
            section: 'APS' | 'ODV' | 'EF' | 'IS' | 'RA' | 'SOMS' | 'ALTRI' | null;
            number: string | null; registered_on: string | null; checked_on: string | null;
        } | null;
    };
    membership_types: Array<{
        uid: string; cod: string; name: string; description: string | null;
        quota_amount: number | null; currency: string;
        expiry_model: 'solar_year' | 'rolling_12' | 'rolling_24' | 'prorated_solar' | 'custom';
        network_eligible: boolean;
    }>;
    statuto: { uid: string; version: number; approvaldate: string | null; sha256: string | null; content: string | null; document_url: string | null } | null;
}

type MemberAddressPolicy = 'none' | 'optional' | 'required';

interface FiscalCodeCheck {
    code: string; first_name?: string; last_name?: string; gender?: 'M' | 'F'; date?: string; place?: string;
    country?: string;             // pays de naissance : pour une personne née à l’étranger, le code ne contient que l’État, et `place` n’est pas comparé
}
type FiscalCodeMismatch = 'lastName' | 'firstName' | 'gender' | 'date' | 'place';
type FiscalCodeHint = 'names_swapped';
interface FiscalCodeResult {
    valid: boolean;
    error: 'invalid' | 'mismatch' | null;     // 'invalid' : structure ou caractère de contrôle ; 'mismatch' : en désaccord avec les données envoyées
    mismatches: FiscalCodeMismatch[];
    hints: FiscalCodeHint[];
    birth_date: string | null;    // la date de naissance que contient le code (YYYY-MM-DD)
    birth_country: string | null; // le pays de naissance que contient le code (IT pour une comune)
}

interface CardKeys { keys: Array<{ kty: string; crv: string; x: string; kid: string; use: string; alg: string }> }
```

## Clients confidentiels

```ts
interface MembershipStatusInfo {  // GET /v1/memberships/{id}/status
    id: string;
    status: MembershipStatus;
    verdict: MembershipVerdict;
    valid_until: string | null;
    membership_type: { cod: string } | null;
}

interface SignupInput {           // POST /v1/signup
    email: string;
    first_name: string;
    last_name: string;
    phone?: string | null;
    dob?: string | null;          // YYYY-MM-DD
    gender?: 'M' | 'F' | null;
    birthplace?: string | null;
    birth_country?: string | null;            // ISO 3166-1 alpha-2 — pour une personne née à l’étranger : le code ne contient que l’État
    codicefiscale?: string | null;
    codicefiscale_confirmed?: boolean;        // la personne confirme un code en désaccord (422 `codicefiscale_mismatch` avec `confirmable: true`)
    accept_terms?: LegalRef;                  // les conditions du compte acceptées sur votre site — facultatif, la version en vigueur
    acknowledge_privacy?: LegalRef;           // la notice d’information de la plateforme lue sur votre site — facultatif, même règle
}
interface EnrollInput extends SignupInput {   // POST /v1/enroll
    codicefiscale?: string | null;            // facultatif : un code fiscal OU dob + birthplace + birth_country, jamais ni l’un ni l’autre
    address?: MemberAddress;                  // résidence ou domicile, quand `aps.member_address` ne vaut pas `none`
    membership_type?: string;     // facultatif quand il y a exactement un type
    accept_statuto: { version: number; sha256?: string };
    privacy_notice?: { version: number; sha256?: string };   // obligatoire quand l’association publie une notice
}
interface MemberAddress {
    kind?: 'residence' | 'domicile';          // par défaut 'residence'
    line: string;                 // rue et numéro
    postal_code?: string | null;
    city: string;
    province?: string | null;     // adresses italiennes seulement (deux lettres)
    country?: string;             // ISO 3166-1 alpha-2, par défaut 'IT'
}
interface EnrollIdentification {  // POST /v1/enroll/confirm après un 409 identification_required
    dob?: string; birthplace?: string; birth_country?: string; codicefiscale?: string; codicefiscale_confirmed?: boolean;
}
interface CardCheckinResult {     // POST /v1/checkins pour une carte
    kind: 'card';
    verdict: MembershipVerdict;
    status: MembershipStatus;
    membership_id: string;
    valid_until: string | null;
    valid_from: string | null;
    identity_check: IdentityCheck | null;     // une indication pour la personne qui tient l’entrée : hors de `verdict`, elle ne restreint rien
    checked_in_at: string;
}
/** 'pending' : une pièce d’identité reste à regarder ; 'verified' : regardée ; 'flagged' : regardée, et elle ne concordait pas. */
type IdentityCheck = 'pending' | 'verified' | 'flagged';
interface FieldError {            // une entrée de `VimineError.errors`
    field?: string; code?: string; message?: string;
    mismatches?: FiscalCodeMismatch[]; hints?: FiscalCodeHint[]; confirmable?: boolean;   // `codicefiscale_mismatch` seulement
}
interface LegalRef { version: string; sha256?: string }      // accept_terms / acknowledge_privacy (facultatif, version en vigueur)
interface MyAcceptances {         // GET /v1/me/acceptances (legal:read)
    items: AcceptanceRecord[];    // at, action, channel, kind, title, version, sha256, rendered_sha256, locale, association
    pending: { platform: PendingDocument[]; privacy_notice: PendingDocument | null };
}
interface AcceptanceInput { kind: string; version: string | number; sha256?: string }   // POST /v1/me/acceptances (legal:accept)
interface Accepted { status: 'accepted' }     // la réponse, toujours la même
interface EnrollConfirmResult {  // POST /v1/enroll/confirm
    status: MembershipStatus;
    already: boolean;             // avait déjà une adhésion ou une demande
    pending_reasons: Array<'approval' | 'payment'>;   // dans l’ordre : l’admission d’abord, puis la cotisation
    payment_open: boolean;
    valid_from: string | null;
    association: { name: string; slug: string };
    event?: EnrolledPlace | null; // seulement pour un lien parti de la page d’adhésion ouverte depuis un événement : ce qu’est devenue la place
}
interface EnrolledPlace {        // la place demandée avec la demande d’adhésion (guide Événements › Une personne qui n’est pas encore membre)
    slug: string; title: string; starts_at: string; timezone: string; place: string | null;
    registration_type: { uid: string; name: string; amount: number; currency: string };
    state: 'confirmed' | 'reserved' | 'held' | 'unavailable';   // held : la demande elle-même attend la confirmation de l’adresse
    waiting: Array<'membership' | 'contribution'>;               // ce qu’attend une place `reserved`
    reasons: string[];                                           // pourquoi elle est `unavailable` (les raisons d’éligibilité)
    reserved_until: string | null;
    payment_instructions: string | null;
    already: boolean;                                            // la place était déjà retenue
}

interface SandboxMembers { slug: string; members: Array<{ email: string; name: string; status: MembershipStatus; expires_at: string | null; statuto_accepted: boolean }> }
interface SandboxReset { password: string; members: Array<{ email: string; name: string; status: MembershipStatus }> }
```

## OAuth

```ts
interface TokenResponse {
    access_token: string;
    token_type: 'Bearer';
    expires_in: number;
    refresh_token?: string;
    scope: string;
    sub?: string;                 // absent sur les jetons client
}
```

## Le vocabulaire du document OpenAPI

`Spec` est généré à partir de `openapi/v1.json` (`npm run spec:types`, vérifié en CI) : chaque schéma du document sous forme de type, plus `Spec.operations` (id → méthode et chemin). Les types ci-dessus restent la surface documentée ; `Spec` est pour qui travaille directement avec l’API.

```ts
import type { Spec } from '@vimine/sdk';
let p: Spec.Problem; const [m, path] = Spec.operations.listMemberships;
```
