# Events and registrations

> Publish the association's events on your site, let members register (and guests, for public events), check people in at the door with the same scanner as the cards.

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

The events an association publishes from its console are on the API with the same principle as the cards:
**headless copy** (title, summary, markdown description, images, place, dates, links) and **live availability**
per registration type — never *who* is registered. The vocabulary is the Italian Third Sector's: a
**registration** (*iscrizione*) and, when there is an amount, a **contribution** (*contributo*).

:::note[Before you start]
Your client needs the `events:read`, `registrations:read` and `registrations:write` permissions (see
[Scopes, errors and limits](https://developers.vimine.network/en/guides/scopes-and-errors/)). To try things out without touching real data use
the [sandbox](https://developers.vimine.network/en/guides/sandbox/): the twin association can publish events like the real one.
:::

## Two visibilities

| `visibility` | Who sees it | Who registers |
|---|---|---|
| `members_only` (default) | only with a member token or the association's own client | members with a valid card |
| `public` | anyone, no sign-in: `GET /v1/aps/{slug}/events` | members and — when the registration type does not require a card — **guests** |

A public event shows your site's visitors the copy and the registration types with the places left;
members-only events appear only after sign-in.

## Showing events

```js title="public page, no sign-in"
import { publicApi } from '@vimine/sdk';
const api = publicApi();
const { items } = await api.events.list('your-slug', { filter: 'upcoming' });
const event = await api.events.get('your-slug', 'jazz-night');
```

```js title="after Sign in with Vimine (members-only events included)"
const { items } = await vimine.events.list();           // events:read
const event = await vimine.events.get('jazz-night');
```

Every event carries `registration_types[]`: `amount` (0 = free), `currency`, `remaining` (places left, `null` =
unlimited), `open` and — when it is not open — `reason` (`sold_out`, `registration_not_open`,
`registration_closed`). `status` is `published`, `completed` (the date has passed) or `cancelled`.

Without writing code: the [`<vimine-events>`](https://developers.vimine.network/en/sdk/elements/) element renders the list with the
register buttons.

## Someone who is not a member yet

A public event also reaches people who are not members: it is the moment a person decides to become one.
The association's enrolment page on Vimine opens **with the event**, and the person joins and books in one
step:

```
https://app.vimine.network/enroll/your-slug?event=jazz-night
```

```js title="the same address, from your code"
import { eventEnrollUrl } from '@vimine/sdk/elements';
const url = eventEnrollUrl('https://app.vimine.network', 'your-slug', 'jazz-night', 'en');
```

What becomes of the place depends on where the membership stands, and the page tells the person:

| The membership | The place |
|---|---|
| is active at once (free card, no approval) | **confirmed**, when the event's registration is free |
| waits for approval or for the fee | for a members-only registration: **held** until the card is active, and then, when it is free, confirmed by itself. For a registration open to everybody: confirmed at once, as for a non-member |
| the event's registration asks for a contribution | **held** until the deadline the association set: confirmed when it records the contribution |
| the e-mail address is not confirmed yet | nothing is held: the place is asked for when the person confirms the address from the link in the mail, and a mail tells them how it went |

Someone who is already a member uses the same page: they sign in and book with one button, with nothing to
fill in (`&signin=1` opens the sign-in at once). It applies to published `public` events that are still
ahead: with any other slug the page is the ordinary enrolment page.

The event's public page on Vimine (`/agenda/your-slug/jazz-night`) and `<vimine-events>` already offer
visitors this way in: *Join the association and book*.

## Registering a member

Before showing the button ask the API whether the member may register — the answer says why not:

```js
const { can_register, types } = await vimine.events.eligibility('jazz-night');
// types[i].reasons: membership_required, membership_not_active, membership_not_yet_valid, sold_out, max_per_user, registration_closed, …
```

Then register them (`registrations:write`):

```js
const reg = await vimine.registrations.create('jazz-night', { registration_type: types[0].uid, note: 'Coming with my sister' });
if (reg.status === 'confirmed') drawQr(reg.token);           // free type: confirmed, QR ready
if (reg.status === 'reserved') show(reg.payment_instructions); // contribution: reserved until it is paid
```

- A **free** type confirms at once and returns `token`: the content of the QR the member shows at the door
  (draw it yourself, with the library you prefer).
- A type **with a contribution** reserves the place (`reserved_until`) and returns the association's payment
  instructions; the secretariat confirms when it records the contribution — the member gets an e-mail and from
  then on `vimine.registrations.get(id)` carries the `token`. Card payments arrive with Stripe.
- If the member's card is still awaiting activation, the registration stays `reserved` and confirms itself when
  the card becomes active.
- `422 not_eligible` carries the same reasons as the eligibility call in `errors[]`.

The member's registrations: `vimine.registrations.list({ filter: 'upcoming' })`; cancellation inside the event's
window (`registration.cancellation_hours` hours before the start): `vimine.registrations.cancel(id)` — later,
`409 window_closed`.

## Guests (public events)

A visitor without a Vimine account can register for a **public** event, on a registration type that does not
require a card, from your server (confidential client, `registrations:write`):

```js title="server"
await srv.registrations.guest('jazz-night', { email, first_name, last_name, registration_type });
// → { status: 'accepted' }, always
```

No account is created. The guest receives an e-mail with a confirmation link (valid 24 hours) that lands on the
event's page on Vimine: with a free type the place is confirmed there and the QR shown at once; with a
contribution the place stays reserved with the payment instructions. If you would rather host the confirmation
page yourself, consume the link's token with `api.confirmRegistration(token)`.

Once the place is confirmed — at once with a free type, when the contribution is recorded otherwise — the guest
receives a second e-mail with their own link: it opens the event's page with the QR to show at the door, and it
works until the event is over (a postponed event keeps it). A guest has no personal area: the link is their
place, never the QR itself (a forwarded mail is not a place). If you host that page yourself, read the place with
`api.viewRegistration(token)` (SDK since 0.3.2).

The answer is always `202 { status: 'accepted' }`: the API never discloses whether an address is already
registered. Eligibility problems (`sold_out`, `membership_required` for a members-only type) are `422`.

:::caution[Tax]
Under the Italian Third Sector Code, contributions from **non-members** are commercial proceeds: the console
says so when an event is made public, and every contribution is classified automatically from the membership
the registration had at payment time.
:::

## At the door

The registration QR is a signed token like the card's: the same scanner and the same call accept it.

```js title="server (turnstile, scanner, desk)"
const r = await srv.checkins.create(scannedToken);
if (r.kind === 'registration') {
    // r.verdict: valid | already_checked_in | not_confirmed | cancelled | event_cancelled | membership_invalid
    // r.first: true on the first pass; r.membership_verdict when the type requires a card
}
```

Public verification answers a registration token too: `GET /v1/verify/{token}` →
`{ status, kind: 'registration', event: { slug, starts_at } }`, no personal data.

## From your back office

The registrations of an event — id, status, type, membership, check-in, **no personal data** — are
`srv.registrations.listForEvent(slug, { status, limit, cursor })` (`registrations:read`, plans with API lists).
Names stay in the association's console.

## References

- [Browser SDK](https://developers.vimine.network/en/sdk/browser/#events) · [Server SDK](https://developers.vimine.network/en/sdk/server/#events) ·
  [`<vimine-events>`](https://developers.vimine.network/en/sdk/elements/) · [Types](https://developers.vimine.network/en/sdk/types/)
- HTTP API: the operations tagged `events` and `registrations` in the [reference](https://developers.vimine.network/en/api/console/).
