Managing People
Enrolment invitations, subjects, credentials, identification and the authentication log.
Everything that is not the sign-in itself: inviting somebody to enrol, finding them again, revoking a passkey, resolving a face to a subject at a desk, and reading what happened.
Enrolling somebody¶
One call. It creates the subject if they do not exist yet and returns the link to send them.
const invite = await gid.enrollments.invite({
externalId: "emp-1",
email: "ana@acme.com",
consentText: "Acme will process an irreversible representation of your face so you can sign in without a password.",
policyVersion: "acme-2026-01",
lawfulBasis: "art9_2_a",
returnTo: "https://app.acme.com/enrolled", // a registered origin
});
await db.save({ externalId: "emp-1", sub: invite.sub });
sendEmail("ana@acme.com", invite.url);
Store the sub. It is in every ID token and every security event, and it
is the only thing linking either back to your own record. An event names a
sub and nothing else — no email, no external id — and you cannot recover it
from a face later.
consentText, policyVersion and lawfulBasis are required and have no
defaults — the provider records the exact sentence the person was shown, and
the policy version it came from.
When the page sends them back to returnTo, check once:
await gid.enrollments.status("emp-1"); // "submitted" or "active" when done
Do not poll. A stream subscribed to credential-change is told when the
template becomes usable — see Receiving events.
Finding somebody¶
await gid.subjects.get("emp-1"); // by your own identifier
await gid.subjects.getBySub(sub); // by the one in a token or an event
getBySub is the one you need in an event handler, where a sub is all you
are given.
Listing is paged, and iteration handles the cursor for you:
for await (const subject of gid.subjects.iterate({ status: "active" })) {
…
}
Ending access¶
await gid.subjects.suspend("emp-1"); // reversible
await gid.subjects.deactivate("emp-1"); // they are gone from your org
await gid.subjects.erase("emp-1"); // the template is destroyed
These are three different things and the difference matters. suspend is a
state you can lift. erase destroys the biometric material and cannot be
undone — a person erased must enrol again from scratch, because there is
nothing left to compare against.
Passkeys are separate from the face:
await gid.credentials.list("emp-1");
await gid.credentials.revoke("emp-1", credentialId);
Never key material — a credential row says a passkey exists and when it was used, not what it is.
Identifying somebody at a desk¶
A hosted 1:N lookup, for a reception desk or a kiosk where nobody has typed an identifier.
const session = await gid.identifications.start({
returnTo: "https://app.acme.com/desk",
});
// send the operator to session.url
const result = await gid.identifications.get(session.id);
It issues no token and signs nobody in. The answer is a sub or nothing —
no candidate list, no score, no count, and no distinction between "nobody
matched" and "nobody is enrolled".
Reading the log¶
const page = await gid.audit.authentications({
externalId: "emp-1",
since: "2026-10-01T00:00:00Z",
decision: "denied",
});
No score and no threshold — not in the rows, and not as a filter.