Skip to main content
Some products put the assistant in front of their own users rather than in front of the public: a learner asking about their course, a parent asking how their child is doing, a rep asking about the accounts they own. That is a different problem from a website visitor. A visitor is anonymous and every answer is public. A named user has material that is theirs, and material that belongs to somebody else standing right next to them.
If your product has end users who must not see each other’s data, read this page and Permissions before you connect anything. The default for a workspace is that everything in it is answerable — which is correct for a shop and wrong for a school.

A principal is one of your users

You register them by your id. The platform stores no vertical vocabulary:
kind is a slug you choose. The platform never branches on its value, so student, vice_principal and account_manager are all equally ordinary. A school with six staff levels and a CRM with two do not need different releases.
attributes is for prompting and reporting only. It is never consulted to decide what someone may see — that is what a grant is for. A JSON blob that quietly becomes a permission system is a permission system nobody can audit.

Principals are not leads

The platform already models a customer (a person) and a lead (a sales record) — see Identity. A principal is neither. A lead requires a phone number, dedupes on it, and carries do_not_call. Registering eight hundred students as leads would poison every one of those behaviours. They stay separate, and a principal may optionally point at a customer when the same human is both — a parent who also enquired about fees.

One person, many channels

An identity maps a channel handle to a principal, so the same person reached on WhatsApp and on the web is one principal rather than two.
An unverified identity never contributes a grant. Anyone can claim a phone number; only an identity your backend asserted over an authenticated call, or one the platform actually verified, may stand behind access to somebody’s records.

The session token

Your backend mints a short-lived token for the user whose browser is about to talk to the assistant. The browser presents it; it cannot mint its own.
Deliberately not the Authorization header, so a session token can never be mistaken for a workspace key (tgcc_…) or a partner key (tgpk_…), and a mix-up cannot silently escalate.

It carries no entitlements

The token names a person. What that person may reach is read from the database on every request instead.
This is the decision the rest of the design hangs on, and the reason is revocation: a grant withdrawn at 10:00 stops applying at 10:00, not whenever a token happens to expire. A learner enrolled in forty units would also carry forty ids in a header on every request, and a credential sitting in a browser should have the smallest blast radius available. “Names a person” is smaller than “enumerates their access”.
A token does carry the user’s role names, for readable logs. Those are telemetry and are never read to decide access.

Refreshing cannot widen access

A live token can be exchanged for a fresh one until the refresh ceiling. The replacement is built from the stored record, never from the presented claims — so a withdrawn grant does not survive a refresh, and a tampered token that somehow verified would still only get back what the database says.

Asking about somebody else

A parent’s session is about their child. The asker and the subject are different people, and both matter:
  • Knowledge is filtered by the asker. A parent reads the parent handbook, not the marking scheme.
  • Records are reachable for the subject, and only where an explicit grant says so.
The two never merge. A parent allowed to ask how their child is doing does not thereby inherit the child’s coursework. If you want that, grant it explicitly — one line in your sync, and auditable afterwards.

How access is actually decided

Grants, roles, deny rules, and what retrieval does with them.