Fundamentals
The objects nulvless is built from, and how they relate.
Everything in nulvless hangs off a small set of objects. Once these are clear, the rest of the documentation is mostly detail.
Organization, project, environment
An organization owns billing and members. A project is one product, and lives inside an organization. An environment is a full copy of that project — its own users, sessions, providers, templates and keys.
Environments are not a flag on a row. develop and main are separate stores
that never share data, and each answers on its own issuer URL — main gets the
bare project slug, every other environment is suffixed with its own slug. Moving
configuration between them is a deliberate, recorded promotion.
organization
└── project
├── environment · main → acme.nulvless.co
├── environment · staging → acme-staging.nulvless.co
└── environment · develop → acme-develop.nulvless.coThis is why a test user can never appear in production: it was never in that store to begin with. How configuration moves forward is covered in Environments.
Users and identities
A user is one person in one environment. An identity is a way that person proves who they are — a password, a Google account, a GitHub account.
One user can hold several identities. Someone who signs up with a password and later continues with Google is still one user, with one id, and one history.
Sessions
A session is an authenticated period for one user on one device. It records the device, the location, and the methods used to reach it.
Sessions are individually revocable, and they carry whether sudo mode is currently active.
Roles, scopes, grants and contexts
Authorization is role-based, with two pieces that make it work for multi-tenant products.
- A scope is the smallest unit — one thing that may be done, like
projects:write. - A role is a named bundle of scopes, such as
admin. - A context is where a role applies: an organization, a project, or any resource of yours.
- A grant is a user holding a role inside a context.
Because the grant carries the context, the same person can be owner in one
organization and viewer in another, without your code special-casing anything.
const { data } = await nulvless.auth.verify({
resource: 'proj_16tbwq34',
roles: ['owner', 'admin'],
});Keys
Every environment has its own keys.
- A publishable key (
pk-…) is safe in the browser. It can only do what the signed-in user could already do. - A secret key (
sk-…) is server-side only. It reaches the administrative parts of the API and must never appear in client code.
Which key you hold decides which client you create — see Clients.
Tokens
Signing in returns an access token and a refresh token. The access token is a JWT your services can read directly: it carries the user, their roles and scopes, and any custom claims you configured.
That is the point of putting claims in the token — a service that already reads the token does not need a second call to know what the caller may do.
Results, not exceptions
Every SDK method returns a result object rather than throwing:
const { data, error } = await nulvless.auth.signIn({ email, password });
if (error) {
// handle it — error.code, error.message, error.status_code
return;
}The failure path is something you handle, not something you catch. See Error handling.