Checking in
The gate’s side of a check-in is small: read the member’s QR code, send it to core, act on the answer. Everything else (who the member is, whether the code is valid, what a check-in earns) is core’s business.
The flow
Section titled “The flow”sequenceDiagram
autonumber
actor M as Member
box rgb(227,244,243) FINNS platform
participant APP as FINNS app
participant CORE as Core
end
box rgb(246,241,233) Gate
participant GATE as Gate
end
M->>APP: Open "Check in"
APP->>CORE: Request a one-time QR
CORE-->>APP: QR code (short-lived, single use)
APP->>M: Show QR
M->>GATE: Present QR to the scanner
GATE->>CORE: POST /api/v1/gate/check-ins {gateId, qr}<br/>Bearer gate token
CORE-->>GATE: 200 {result: "granted"} or {result: "denied", reason}
Note over GATE: nowrap:Open only on "granted"
What the gate must do
Section titled “What the gate must do”| Step | Contract |
|---|---|
| Read | Scan the QR code. Its content is opaque text starting with FINNS1:. Don’t decode or modify it. |
| Send | One POST /api/v1/gate/check-ins per scan, with {gateId, qr} and the gate token. See the API reference. |
| Act | Open only when core answers {"result": "granted"}. A denied result, an error, a timeout or an unreadable answer all mean “do not open”. |
Core decides everything else. A scan is recorded whether it is granted or denied.
What core decides
Section titled “What core decides”flowchart TB
classDef ok fill:#E3F4F3,stroke:#13686C,color:#12191B
classDef bad fill:#FFF4E8,stroke:#E9724C,color:#12191B
classDef step fill:#ffffff,stroke:#D9CFBE,color:#12191B
s["Scan: {gateId, qr}"]:::step --> f{"Well-formed<br/>FINNS1 token?"}
f -- "no" --> d1["denied · malformed"]:::bad
f -- "yes" --> k{"Known QR?"}
k -- "no" --> d2["denied · unknown"]:::bad
k -- "yes" --> u{"Already used?"}
u -- "yes" --> d3["denied · used"]:::bad
u -- "no" --> e{"Expired?"}
e -- "yes" --> d4["denied · expired"]:::bad
e -- "no" --> g["granted<br/>QR consumed · check-in recorded<br/>points credited"]:::ok
A QR works once, even if two gates scan it at the same moment. The points credit is tied to the QR, so a check-in can never be credited twice.
The QR
Section titled “The QR”| Part | Contract |
|---|---|
| Format | FINNS1: followed by a random URL-safe string |
| Meaning | None outside core. It is not signed and not decodable by the gate. |
| Lifetime | Short; the app refreshes it while it is on screen. An expired code is denied · expired. |
| Versioning | The prefix. A gate that sees a newer FINNSn: prefix it doesn’t know must not guess: it reports that it can’t read the code. |
Internal and confidential. Share partner sections only as partner builds (pnpm build:partner).

