@colixsystems/identification-client
v0.1.0
Published
Scoped client for the AppStudio identification API (BankID first, provider-abstracted). Identifies a visitor who is NOT signed in. Used by widgets through the injected WidgetContext.identification.
Readme
@colixsystems/identification-client
Scoped client for the AppStudio identification API. Identifies a visitor who is not signed in — proving a real person holding a credential was present — so an app can store that as a value: an attestation on a record, a consent line, an identity check before a form submit.
BankID is the first provider; the API is provider-abstracted, so further providers become available without a client change.
Widgets do not construct this client. The host injects a built instance at WidgetContext.identification, and the SDK hook useIdentification() is the ergonomic way to drive it. Construct it directly only when you are building a host.
What this is not
| You want to… | Use |
| --- | --- |
| Identify a visitor and keep the result | this package |
| Sign a user in with BankID | the Player's auth flow / useBankIdLink() to attach BankID to an existing account |
| E-sign a file's contents | @colixsystems/filestore-client → signatures, or the SDK's useFileSignature() |
Identification creates no account and no session.
Install
npm install @colixsystems/identification-clientUsage
import { createIdentificationClient } from "@colixsystems/identification-client";
const identification = createIdentificationClient({
baseUrl: "https://api.example.com/api/v1",
// May resolve empty — an anonymous visitor is the supported case.
getToken: () => localStorage.getItem("token") ?? "",
getTenantId: () => "workspace-uuid",
});
// 1. Only offer the flow where it can actually complete.
const { available } = await identification.available();
if (!available) return;
// 2. Start an order and render the QR (+ the same-device deeplink).
const order = await identification.start({ provider: "bankid", purpose: "attest" });
// 3. Poll until terminal. A fresh QR frame arrives on every poll.
let state = order;
while (state.status === "pending") {
await new Promise((r) => setTimeout(r, 1000));
state = await identification.get(order.identification_id);
render({ qr: state.qr, message: state.message });
}
if (state.status === "complete") {
// e.g. "Anna Andersson (19900101-****) verified via BANKID 2026-08-17"
const { name, personal_number_masked, provider, identified_at } = state.identity;
await saveAttestation(
`${name} (${personal_number_masked}) verified via ${provider} ${identified_at}`,
);
}Personal data
A completed identification gives you:
name,given_name,surnamepersonal_number_masked— e.g.19900101-****subject_hash— stable for the same person, so you can recognise a returning visitoridentified_at
The raw personal number is not part of this client's surface. It is encrypted at rest and readable only through a studio-admin endpoint by a signed-in workspace member — so a widget can never read it, and a full personnummer can never end up in page JSON or a datastore column by accident. Store identification_id alongside your attestation if you need to trace back to the proof.
API
| Method | Description |
| --- | --- |
| available() | { available, providers: [{ provider, available }] }. Call before rendering a flow. |
| start({ provider?, purpose? }) | Begins an order → { identification_id, provider, purpose, status, auto_start_token, qr, expires_at }. provider defaults to "BANKID"; purpose is a ≤120-char audit label. |
| get(identificationId) | Polls → { status, hint_code, message, qr } while pending, { status: "complete", identity } on success. |
| cancel(identificationId) | Aborts a non-terminal order. |
Orders expire five minutes after start.
Wire format
snake_case in both directions, verbatim — there is no case transform anywhere. The only camelCase is the JS method names and the factory options.
Retries
get is idempotent and retried (3 backoff attempts) through a transient provider fault, so a poll loop rides out a blip. start and cancel are never retried — a retried start would open a second provider order and hand the visitor two QR codes.
Errors
IdentificationError with typed subclasses carrying .code, .status, .details:
| Status | Class | Meaning |
| --- | --- | --- |
| 400 | ValidationError | Unknown provider or bad input |
| 403 | ForbiddenError | Not permitted |
| 404 | NotFoundError | No such identification in this workspace (a cross-workspace id is indistinguishable, by design) |
| 409 | NotConfiguredError | The provider is not configured on this deployment, or the order has no verified identity |
| 429 | RateLimitedError | Rate limited — start is capped per IP and per workspace |
| 502 | UnavailableError | The provider failed. Retryable |
| 5xx | ServerError | Server error |
License
MIT
