Skip to content

Server integrations

View as Markdown

For the systems of the association itself — management software, a turnstile, a CMS plugin — a confidential client with client_id + client_secret. The secret is shown once and must never reach a browser.

Node
import { VimineServer } from '@vimine/sdk/server';
const vimineServer = new VimineServer({
clientId: 'vmc_…',
clientSecret: process.env.VIMINE_SECRET,
scopes: ['memberships:read']
});
const token = await vimineServer.token(); // client_credentials — 1-hour token, renewed by itself

Without the SDK: POST /v1/oauth/token with grant_type=client_credentials and HTTP Basic client_id:client_secret (or client_id/client_secret in the body):

Finestra del terminale
curl -u "vmc_…:vms_…" -d "grant_type=client_credentials&scope=memberships:read" \
https://api.vimine.network/v1/oauth/token

On the read endpoints (/v1/memberships/{id}/status, /v1/memberships) HTTP Basic is also accepted directly, without going through a token, for hardware and low-code platforms — never from a browser.

const s = await vimineServer.memberships.status('membership-uuid');
// { id, status: 'active', verdict: 'valid', valid_until: '2027-…', membership_type: { cod: 'ORD' } }

No personal data: status and validity only. The id is the one the member sees on their own card (membership.id via Sign in with Vimine) or that your system received when it registered it.

To verify a card shown at the door use the QR token with /v1/verify/{card_token} instead: it needs no authentication at all.

With identity:signup and enroll:write your back office can create accounts and submit membership requests without the person going through Vimine: they receive by e-mail the link to confirm the address and pick a password.

// The account only (the person is not a member yet)
await vimineServer.signup({ email: 'mario.rossi@example.org', first_name: 'Mario', last_name: 'Rossi' });
// Account + pending membership request, approved by the secretariat in the Vimine back office
const { statuto, membership_types } = await vimineServer.public.association('your-slug');
await vimineServer.enroll({
email: 'mario.rossi@example.org',
first_name: 'Mario',
last_name: 'Rossi',
codicefiscale: 'RSSMRA80A01H501U', // optional: without it, dob + birthplace + birth_country are needed
dob: '1980-01-01', birthplace: 'Roma', birth_country: 'IT',
gender: 'M', // demographic only: it is not cross-checked against the code
membership_type: membership_types[0].uid, // optional when there is exactly one type
accept_statuto: { version: statuto.version, sha256: statuto.sha256 },
privacy_notice: privacy_notice && { version: privacy_notice.version, sha256: privacy_notice.sha256 } // required when the association publishes one
});

The answer is always 202 { status: 'accepted' }, whatever lies behind the address: you cannot learn whether an e-mail already has a Vimine account (only its owner is told, by mail). Specifically:

  • new address → account created and, with enroll, a pending membership request; welcome mail with the address confirmation;
  • address already registered → with enroll nothing is written: the owner receives a “confirm your request to <association>” link valid 24 hours, and the request is created only when they use it. If they are already a member, nothing happens;
  • 422 errors are about your payload only (validation_failed with errors[].field, statuto_outdated, invalid_type); 403 api_disabled when the association does not accept requests; 403 not_available during the private beta, while accounts are created by invitation only.

A person is identified by a fiscal code or by their date, place and country of birth (dob, birthplace, birth_country) — never neither: without a code, each missing particular is a required entry in errors[]. For a person born abroad always send birth_country: their code holds the state only. A code that disagrees with the particulars answers 422 with a codicefiscale_mismatch entry carrying mismatches, hints and confirmable: when confirmable is true the person may confirm it, and you repeat the call with codicefiscale_confirmed: true. When the association collects its members’ address (aps.member_address in the public profile) add address. When it collects the data of whoever holds parental responsibility (aps.collects_guardian_data) and the person is a minor by dob, guardian is required — first_name, last_name, relation, optionally codicefiscale, email, phone — else 422 with guardian: required. The whole path is in Members without a fiscal code.

accept_statuto must name the statuto in force: show it to the person on your site (it is in association(slug); the text and the document in GET /v1/aps/{slug}/statuto) and pass the version they accepted. An association that has registered no readable statuto answers accepting_requests: false, and the request 403 not_available. The same goes for the association’s privacy notice to members (privacy_notice in association(slug), the text at GET /v1/aps/{slug}/privacy-notice): when the association publishes one, the request must name the version the person read, else 422 (privacy_notice missing or privacy_notice_outdated); the acknowledgement is recorded in the person’s name, your client named. Optional, same rule: accept_terms and acknowledge_privacy (the account terms and the platform’s privacy notice, GET /v1/legal/{kind}) when the person accepted them on your site. The calls are idempotent: the SDK generates an Idempotency-Key per call, or pass your own ({ idempotencyKey: 'order-42' }) to retry safely after a timeout. Mails are budgeted (3 per address per day) and never sent from the sandbox.

With checkins:write a device at the entrance records a member’s passage by sending the token read from the card’s QR (not an identifier): the answer is the verdict alone, no personal data. HTTP Basic is accepted directly, for hardware and low-code platforms.

const r = await vimineServer.checkins.create('vmt_…'); // { verdict: 'valid' | 'expired' | 'not_yet_valid' | 'statuto_required' | 'inactive', status, membership_id, valid_until, identity_check, checked_in_at }
Finestra del terminale
curl -u "vmc_…:vms_…" -H "Content-Type: application/json" -d '{"token":"vmt_…"}' https://api.vimine.network/v1/checkins

The body is { "token": "vmt_…" } — the token read from the QR, of a card or of an event registration; for cards only, card is accepted as an alias.

A malformed or forged token is a 422 invalid_card; another association’s card is “not found”.

identity_check: 'pending' is a note for whoever staffs the gate: the member has no verifiable fiscal code and nobody of the association has looked at a document yet. Show it; it is not part of verdict and it restricts nothing (see the identity check).

Listing memberships (Pro and Network plans)

Section titled “Listing memberships (Pro and Network plans)”

vimineServer.memberships.list({ status?, updated_since?, limit?, cursor? }) returns ids, status and dates — never personal data — in pages (next_cursor). With updated_since you sync only what changed. On plans that do not include it the answer is 403 plan_limit.

From the console (Rotate secret): the new secret is shown once, the previous one keeps working for 24 hours, so you can update your systems without downtime. If you suspect a compromise, revoke the client: all of its sessions end immediately.

  • A wrong secret, repeated, locks the client out for 15 minutes.
  • Every token is bound to the client’s association: a card of another association is simply “not found”.
  • A 401 with just-rotated credentials is retried once by the SDK with a fresh token.
  • Responses use application/problem+json (see Permissions, errors and limits).