# Server integrations

> Confidential clients for the association's own systems, client_credentials, card status, secret rotation.

URL: https://developers.vimine.network/en/guides/server/

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.

## Authenticating

```js title="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):

```sh
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.

## Status of a card

```js
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}`](https://developers.vimine.network/en/guides/embed-and-verify/) instead: it needs no authentication at all.

## Enrol members from your system

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.

```js
// 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](https://developers.vimine.network/en/guides/scopes-and-errors/#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](https://developers.vimine.network/en/guides/members-without-cf/).

`accept_statuto` must name the statuto **in force**: show it to the person on your site (it is in
[`association(slug)`](https://developers.vimine.network/en/sdk/public/); 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.

## Door check-ins

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.

```js
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 }
```

```sh
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](https://developers.vimine.network/en/guides/members-without-cf/#at-the-gate-the-identity-check)).

## 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`.

## Secret rotation

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.

## Security

- 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](https://developers.vimine.network/en/guides/scopes-and-errors/)).
