Die Typen werden von @vimine/sdk (und von /server) exportiert und spiegeln das OpenAPI-Dokument: Die Feldnamen
sind die des JSON. Die vollständigen Strukturen stehen in der API-Referenz; hier die wichtigsten.
Berechtigungen und Status
Abschnitt betitelt „Berechtigungen und Status“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';/** Was der Ausweis gerade jetzt gilt. */type MembershipVerdict = 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive';Das Mitglied
Abschnitt betitelt „Das Mitglied“interface Me { sub: string; // Kennung für Ihren Verein (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; // nur statistisch: wird nicht mit der Steuernummer abgeglichen birthplace: string | null; // Gemeinde (in Italien geboren) oder Stadt, freier Text (im Ausland geboren) birth_country: string | null; // ISO 3166-1 alpha-2 codicefiscale: string | null; // optional: Eine Person kann keine haben codicefiscale_check: CodiceFiscaleCheck | null; // wie die Person identifiziert ist; null = noch nicht locale: Locale | null; // die Sprache, die die Person gewählt hat; null = nie gewählt updated_at: string | null;}
/** Eine Sprache, die Vimine spricht. Wo die API eine annimmt, nimmt sie auch ein regionales Tag (`en-GB` → `en`, `zh-TW` → `zh-Hant`) an und reduziert es. */type Locale = 'it' | 'en' | 'zh-Hans' | 'zh-Hant' | 'fr' | 'es' | 'de' | 'pt-BR';interface LocaleParam { locale?: Locale | string } // ?locale= der Lesezugriffe — siehe die Anleitung Sprachen
/** Wie eine Person identifiziert ist: über eine Steuernummer ODER über Geburtsdatum, -ort und -land — nie über keins von beidem. */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; // kann später ergänzt oder wieder auf null gesetzt werden codicefiscale_confirmed?: boolean; // nur schreibbar: Die Person bestätigt eine abweichende Steuernummer (nach einem `confirmable: true`) locale?: Locale | null; // null löscht die Wahl; 422 `unsupported` für eine Sprache, die Vimine nicht spricht}Der Ausweis
Abschnitt betitelt „Der Ausweis“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; // seit der Annahme durch das Mitglied wurde ein neueres Statuto veröffentlicht overdue: boolean; // … und die Frist für die erneute Annahme ist abgelaufen }; pending_reasons: Array<'approval' | 'payment'>; // warum er noch aussteht, in Reihenfolge: Aufnahme, dann Mitgliedsbeitrag payment_open: boolean; // darf der Beitrag jetzt gezahlt werden? (die Aufnahme öffnet die Zahlung) valid_from: string | null; // wann der Ausweis nutzbar wird (die Frist des Vereins) usable: boolean; // der Ausweis gilt gerade jetzt am Einlass 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 }; // Verlängerung durch das Mitglied: 60 Tage vor Ablauf offen network_opt_in: boolean; card: { token: string; nbf: number; exp: number } | null; // das vmt_…-Token des QR-Codes und sein Fenster (Unix-Sekunden)}
interface Card { token: string; nbf: number; exp: number; status: MembershipStatus }
interface MembershipRequest { // POST /v1/me/membership membership_type?: string; // uid; optional, wenn der Verein genau eine anbietet 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;}Öffentlich
Abschnitt betitelt „Öffentlich“interface Verification { status: 'active' | 'inactive'; valid_until?: string; valid_from?: string; // mit reason 'not_yet_valid': Der Ausweis ist echt und aktiv und wird ab hier nutzbar reason?: 'expired' | 'invalid' | 'not_yet_valid';}
type AdmissionModel = 'board' | 'provisional' | 'automatic'; // wie der Verein seine Mitglieder aufnimmt
interface AssociationPublicConfig { aps: { slug: string; name: string; picture_url: string | null; citta: string | null; provincia: string | null; enroll_enabled: boolean; accepting_requests: boolean; // der Beitritt ist offen UND es gibt gerade Platz für ein weiteres Mitglied invitation_only: boolean; // private Beta: nimmt nur Personen auf, die er eingeladen hat (accepting_requests ist false) // wie der Verein seine Mitglieder aufnimmt — vorab der antragstellenden Person zu sagen. board: Der Vorstand entscheidet, der Beitrag danach; // provisional: sofort aufgenommen, später bestätigt; automatic: aktiv, sobald die Bedingungen erfüllt sind. // delay_hours: Stunden zwischen der Aufnahme und einem NUTZBAREN Ausweis (0 = sofort) admission: { model: AdmissionModel; delay_hours: number }; approval_required: boolean; // veraltet: true, wenn admission.model 'board' ist — lesen Sie `admission` payment_instructions: string | null; // wie der Beitrag zu zahlen ist, sofern der Verein es veröffentlicht hat member_address: MemberAddressPolicy; // ob der Verein die Adresse seiner Mitglieder erfasst — von allen oder von keinem // Tätigkeiten von allgemeinem Interesse (Art. 5 des Kodex des Dritten Sektors): Buchstabe, Bezeichnung, welche überwiegt activity_sectors: Array<{ cod: string; description: string; prevalent: boolean }>; // laut den offiziellen Listen im RUNTS eingetragen, sonst 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; // Geburtsland: Bei einer im Ausland geborenen Person enthält die Steuernummer nur den Staat, und `place` wird nicht verglichen}type FiscalCodeMismatch = 'lastName' | 'firstName' | 'gender' | 'date' | 'place';type FiscalCodeHint = 'names_swapped';interface FiscalCodeResult { valid: boolean; error: 'invalid' | 'mismatch' | null; // 'invalid': Aufbau oder Prüfzeichen; 'mismatch': passt nicht zu den gesendeten Daten mismatches: FiscalCodeMismatch[]; hints: FiscalCodeHint[]; birth_date: string | null; // das Geburtsdatum, das die Steuernummer enthält (YYYY-MM-DD) birth_country: string | null; // das Geburtsland, das die Steuernummer enthält (IT für eine Gemeinde)}
interface CardKeys { keys: Array<{ kty: string; crv: string; x: string; kid: string; use: string; alg: string }> }Vertrauliche Clients
Abschnitt betitelt „Vertrauliche Clients“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 — bei einer im Ausland geborenen Person: Die Steuernummer enthält nur den Staat codicefiscale?: string | null; codicefiscale_confirmed?: boolean; // die Person bestätigt eine abweichende Steuernummer (422 `codicefiscale_mismatch` mit `confirmable: true`) accept_terms?: LegalRef; // die auf Ihrer Website akzeptierten Nutzungsbedingungen des Kontos — optional, die geltende Version acknowledge_privacy?: LegalRef; // die auf Ihrer Website gelesene Datenschutzerklärung der Plattform — optional, dieselbe Regel}interface EnrollInput extends SignupInput { // POST /v1/enroll codicefiscale?: string | null; // optional: eine Steuernummer ODER dob + birthplace + birth_country, nie keins von beidem address?: MemberAddress; // Wohnsitz oder Domizil, wenn `aps.member_address` nicht `none` ist membership_type?: string; // optional, wenn es genau eine Art gibt accept_statuto: { version: number; sha256?: string }; privacy_notice?: { version: number; sha256?: string }; // Pflicht, wenn der Verein Datenschutzhinweise veröffentlicht}interface MemberAddress { kind?: 'residence' | 'domicile'; // Standard 'residence' line: string; // Straße und Hausnummer postal_code?: string | null; city: string; province?: string | null; // nur italienische Adressen (zwei Buchstaben) country?: string; // ISO 3166-1 alpha-2, Standard 'IT'}interface EnrollIdentification { // POST /v1/enroll/confirm nach einem 409 identification_required dob?: string; birthplace?: string; birth_country?: string; codicefiscale?: string; codicefiscale_confirmed?: boolean;}interface CardCheckinResult { // POST /v1/checkins für einen Ausweis kind: 'card'; verdict: MembershipVerdict; status: MembershipStatus; membership_id: string; valid_until: string | null; valid_from: string | null; identity_check: IdentityCheck | null; // ein Hinweis für die Person am Einlass: nicht Teil von `verdict`, schränkt nichts ein checked_in_at: string;}/** 'pending': Ein Ausweisdokument ist noch anzusehen; 'verified': angesehen; 'flagged': angesehen, und es passte nicht. */type IdentityCheck = 'pending' | 'verified' | 'flagged';interface FieldError { // ein Eintrag von `VimineError.errors` field?: string; code?: string; message?: string; mismatches?: FiscalCodeMismatch[]; hints?: FiscalCodeHint[]; confirmable?: boolean; // nur `codicefiscale_mismatch`}interface LegalRef { version: string; sha256?: string } // accept_terms / acknowledge_privacy (optional, geltende Version)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' } // die Antwort, immer dieselbeinterface EnrollConfirmResult { // POST /v1/enroll/confirm status: MembershipStatus; already: boolean; // hatte schon eine Mitgliedschaft oder einen Antrag pending_reasons: Array<'approval' | 'payment'>; // in Reihenfolge: zuerst die Aufnahme, dann der Mitgliedsbeitrag payment_open: boolean; valid_from: string | null; association: { name: string; slug: string }; event?: EnrolledPlace | null; // nur bei einem Link, der auf der von einer Veranstaltung aus geöffneten Beitrittsseite begann: was aus dem Platz wurde}interface EnrolledPlace { // der mit dem Beitrittsantrag angefragte Platz (Anleitung Veranstaltungen › Wer noch nicht Mitglied ist) 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: Der Antrag selbst wartet auf die Bestätigung der Adresse waiting: Array<'membership' | 'contribution'>; // worauf ein `reserved`-Platz wartet reasons: string[]; // warum er `unavailable` ist (die Gründe der Teilnahmeprüfung) reserved_until: string | null; payment_instructions: string | null; already: boolean; // der Platz war schon vorgemerkt}
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 }> }interface TokenResponse { access_token: string; token_type: 'Bearer'; expires_in: number; refresh_token?: string; scope: string; sub?: string; // fehlt bei Client-Tokens}Das Vokabular des OpenAPI-Dokuments
Abschnitt betitelt „Das Vokabular des OpenAPI-Dokuments“Spec wird aus openapi/v1.json erzeugt (npm run spec:types, in der CI geprüft): jedes Schema des Dokuments als Typ, dazu Spec.operations (ID → Methode und Pfad). Die Typen oben bleiben die dokumentierte Oberfläche; Spec ist für alle, die direkt gegen die API arbeiten.
import type { Spec } from '@vimine/sdk';let p: Spec.Problem; const [m, path] = Spec.operations.listMemberships;