這些型別由 @vimine/sdk(以及 /server)匯出,與 OpenAPI 文件一一對應:欄位名就是 JSON 中的欄位名。完整的結構見 API 參考;這裡列出主要的型別。
權限與狀態
章節「權限與狀態」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';/** 會員卡此刻的效力。 */type MembershipVerdict = 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive';會員
章節「會員」interface Me { sub: string; // 在您的協會中的識別碼(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; // 僅用於統計:不與稅號比對 birthplace: string | null; // 市鎮(在義大利出生)或城市,自由文字(在國外出生) birth_country: string | null; // ISO 3166-1 alpha-2 codicefiscale: string | null; // 可選:一個人可以沒有稅號 codicefiscale_check: CodiceFiscaleCheck | null; // 當事人的身份識別方式;null = 尚未識別 locale: Locale | null; // 當事人選擇的語言;null = 從未選擇 updated_at: string | null;}
/** Vimine 支援的一種語言。API 在接受語言的地方也接受帶地區的標籤(`en-GB` → `en`,`zh-TW` → `zh-Hant`),並將其歸併。 */type Locale = 'it' | 'en' | 'zh-Hans' | 'zh-Hant' | 'fr' | 'es' | 'de' | 'pt-BR';interface LocaleParam { locale?: Locale | string } // 讀取介面的 ?locale=——參見“語言”指南
/** 一個人如何被識別:透過稅號,或者透過出生日期、出生地和出生國——兩者不能都沒有。 */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; // 可以之後再補充,或重新設為 null codicefiscale_confirmed?: boolean; // 唯寫:當事人確認一個不一致的稅號(在 `confirmable: true` 之後) locale?: Locale | null; // null 清除所選語言;Vimine 不支援的語言傳回 422 `unsupported`}會員卡
章節「會員卡」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; // 會員接受章程之後,又發布了更新的章程 overdue: boolean; // ……而且重新接受的期限已過 }; pending_reasons: Array<'approval' | 'payment'>; // 仍在等待的原因,按順序:先入會審核,再繳納會費 payment_open: boolean; // 現在可以繳納會費嗎?(獲准入會後才開放繳費) valid_from: string | null; // 會員卡何時可以使用(協會設定的間隔) usable: boolean; // 會員卡此刻是否可在入場時使用 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 }; // 自助續期:到期前 60 天開放 network_opt_in: boolean; card: { token: string; nbf: number; exp: number } | null; // QR Code的 vmt_… 權杖及其有效時段(unix 秒)}
interface Card { token: string; nbf: number; exp: number; status: MembershipStatus }
interface MembershipRequest { // POST /v1/me/membership membership_type?: string; // uid;協會只提供一種類型時可省略 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;}公開
章節「公開」interface Verification { status: 'active' | 'inactive'; valid_until?: string; valid_from?: string; // 當 reason 為 'not_yet_valid' 時:會員卡真實且有效,從此時起可以使用 reason?: 'expired' | 'invalid' | 'not_yet_valid';}
type AdmissionModel = 'board' | 'provisional' | 'automatic'; // 協會如何接納會員
interface AssociationPublicConfig { aps: { slug: string; name: string; picture_url: string | null; citta: string | null; provincia: string | null; enroll_enabled: boolean; accepting_requests: boolean; // 入會已開放,並且此刻還能再容納一位會員 invitation_only: boolean; // 內測:只接納受邀請的人(accepting_requests 為 false) // 協會如何接納會員——請事先告知申請人。board:由理事會決定,之後繳納會費; // provisional:立即臨時入會,之後追認;automatic:條件一滿足即生效。 // delay_hours:從獲准入會到會員卡可以使用之間的小時數(0 = 立即) admission: { model: AdmissionModel; delay_hours: number }; approval_required: boolean; // 已棄用:當 admission.model 為 'board' 時為 true——請讀取 `admission` payment_instructions: string | null; // 如何繳納會費,在協會發布了說明時提供 member_address: MemberAddressPolicy; // 協會是否收集會員地址——對所有會員都收集,或對所有會員都不收集 // 公共利益活動(《第三部門法典》第 5 條):字母編號、表述、是否為主要活動 activity_sectors: Array<{ cod: string; description: string; prevalent: boolean }>; // 根據官方名單已在 RUNTS 登記的資訊,否則為 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; // 出生國:在國外出生的人,稅號中只包含國家,`place` 不參與比對}type FiscalCodeMismatch = 'lastName' | 'firstName' | 'gender' | 'date' | 'place';type FiscalCodeHint = 'names_swapped';interface FiscalCodeResult { valid: boolean; error: 'invalid' | 'mismatch' | null; // 'invalid':結構或檢查碼有誤;'mismatch':與所傳送的資訊不一致 mismatches: FiscalCodeMismatch[]; hints: FiscalCodeHint[]; birth_date: string | null; // 稅號中包含的出生日期(YYYY-MM-DD) birth_country: string | null; // 稅號中包含的出生國(在義大利市鎮出生則為 IT)}
interface CardKeys { keys: Array<{ kty: string; crv: string; x: string; kid: string; use: string; alg: string }> }機密用戶端
章節「機密用戶端」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——對於在國外出生的人:稅號中只包含國家 codicefiscale?: string | null; codicefiscale_confirmed?: boolean; // 當事人確認一個不一致的稅號(422 `codicefiscale_mismatch` 且 `confirmable: true`) accept_terms?: LegalRef; // 在您的網站上接受的帳戶使用條款——可選,現行版本 acknowledge_privacy?: LegalRef; // 在您的網站上閱讀的平臺隱私權聲明——可選,規則相同}interface EnrollInput extends SignupInput { // POST /v1/enroll codicefiscale?: string | null; // 可選:稅號,或 dob + birthplace + birth_country,兩者不能都沒有 address?: MemberAddress; // 戶籍地或住所地,在 `aps.member_address` 不為 `none` 時提供 membership_type?: string; // 只有一種類型時可省略 accept_statuto: { version: number; sha256?: string }; privacy_notice?: { version: number; sha256?: string }; // 協會發布了隱私權聲明時必填}interface MemberAddress { kind?: 'residence' | 'domicile'; // 預設 'residence' line: string; // 街道和門牌號 postal_code?: string | null; city: string; province?: string | null; // 僅限義大利地址(兩個字母) country?: string; // ISO 3166-1 alpha-2,預設 'IT'}interface EnrollIdentification { // 收到 409 identification_required 之後呼叫 POST /v1/enroll/confirm dob?: string; birthplace?: string; birth_country?: string; codicefiscale?: string; codicefiscale_confirmed?: boolean;}interface CardCheckinResult { // 會員卡的 POST /v1/checkins kind: 'card'; verdict: MembershipVerdict; status: MembershipStatus; membership_id: string; valid_until: string | null; valid_from: string | null; identity_check: IdentityCheck | null; // 給入口值班人員的提示:不屬於 `verdict`,不限制任何操作 checked_in_at: string;}/** 'pending':尚需檢視證件;'verified':已檢視;'flagged':已檢視,但與資訊不符。 */type IdentityCheck = 'pending' | 'verified' | 'flagged';interface FieldError { // `VimineError.errors` 中的一條記錄 field?: string; code?: string; message?: string; mismatches?: FiscalCodeMismatch[]; hints?: FiscalCodeHint[]; confirmable?: boolean; // 僅限 `codicefiscale_mismatch`}interface LegalRef { version: string; sha256?: string } // accept_terms / acknowledge_privacy(可選,現行版本)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' } // 回應,始終相同interface EnrollConfirmResult { // POST /v1/enroll/confirm status: MembershipStatus; already: boolean; // 已經有會員資格或申請 pending_reasons: Array<'approval' | 'payment'>; // 按順序:先入會審核,再繳納會費 payment_open: boolean; valid_from: string | null; association: { name: string; slug: string }; event?: EnrolledPlace | null; // 僅當連結來自從活動開啟的入會頁面時提供:名額的結果}interface EnrolledPlace { // 隨入會申請一起申請的名額(“活動與報名”指南 › 尚未入會的人) 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:申請本身在等待電子郵件地址確認 waiting: Array<'membership' | 'contribution'>; // `reserved` 名額在等待什麼 reasons: string[]; // 為什麼是 `unavailable`(報名資格的原因) reserved_until: string | null; payment_instructions: string | null; already: boolean; // 名額此前已被保留}
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
章節「OAuth」interface TokenResponse { access_token: string; token_type: 'Bearer'; expires_in: number; refresh_token?: string; scope: string; sub?: string; // 用戶端權杖中沒有此欄位}OpenAPI 文件的詞彙
章節「OpenAPI 文件的詞彙」Spec 由 openapi/v1.json 產生(npm run spec:types,在 CI 中檢查):文件中的每個 schema 都對應一個型別,另有 Spec.operations(id → 方法和路徑)。上面的型別仍是文件化的公開介面;Spec 供直接串接 API 的開發者使用。
import type { Spec } from '@vimine/sdk';let p: Spec.Problem; const [m, path] = Spec.operations.listMemberships;