Chapter 5

Accounts and tenants

How accounts and tenants relate, the roles inside a tenant, and how tenants are kept apart.

An account is not a tenant

An account belongs to a person; a tenant is an organisation, a team or a project. A membership joins the two, and one account can belong to several tenants. The role is decided per membership, so the same person can be an owner in one tenant and an ordinary member in another.

Accountone per personOWNERTenant AMEMBERTenant BMEMBERTenant Cmembership carries the role
The role is a property of the membership, not of the account. The same person can own one tenant and simply belong to another.
An account with no tenant at all is a normal state. It is where somebody lands straight after signing up, and no membership exists until they create a tenant or accept an invitation.

Roles inside a tenant

The role is per membership and comes back as TenantService/ListMyTenants in tenants[].myRole.

ValueCalledWhat it means
OWNEROwnerRuns the tenant: invites, role changes, removals, settings and API keys
MEMBERMemberBelongs to the tenant. Looks after their own settings
This is not the same as user.role on the account. user.role carries the operator's administrative rights and is an empty string for ordinary users. Earlier versions put USER_PRIMARY or USER_SECONDARY there; the role became a per-tenant value and left the account. Code branching on a role should read myRole instead.

Tenants are separate

Only the tenant the session has selected is ever operated on. The selection lives on the session and is changed with TenantService/SelectTenant. Other tenants' data does not appear in listings and cannot be fetched by naming an id directly.

The boundary is applied at the database query, so there is nothing to filter on your side. If the membership goes away, the selection reads as "none" from the next request onwards.

# Tenants this account belongs to, and its role in each
curl -X POST https://api.albos.org/auth.v1.TenantService/ListMyTenants \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ca_..." -d '{}'

{
  "tenants": [
    { "id": "...", "name": "A社", "myRole": "OWNER",
      "memberCount": 12, "maxMembers": 50, "memberLeaveMode": "FREE" }
  ],
  "selectedTenantId": "...",
  "remainingTenantAllowance": 2
}

What each role can do

OperationOwnerMember
Signing in to the dashboard○○
Authenticating through the API○○
Inviting members○—
Changing roles and removing members○—
Changing the tenant name and how members leave○—
Setting the security policy○—
Issuing and revoking API keys○—
Deleting the tenant○—
Posting announcements to the tenant○—
Leaving the tenant○○
Editing your own profile○○
Managing your own second factor and sessions○○
Deleting your own account○○
A tenant does not own the accounts in it. Whatever the role, your profile, your authentication settings and the account itself are yours to manage. Leaving a tenant leaves the account intact, and deleting the account does not affect the other members.
An owner leaving, and a tenant being deleted, both have conditions. The only owner cannot leave — the tenant would have nobody to run it. A tenant can only be deleted once no other member remains. Deletion is logical: the data is kept, and the name goes back into circulation.

Tenant limits

ItemDefaultCode when exceeded
Tenants one account may create3resource_exhausted
Members in one tenant50resource_exhausted
Outstanding invitations50 per accountresource_exhausted
Outstanding invitations take up seats. In a tenant of 50 with 45 members and 5 invitations waiting, no more can be sent — so that accepting them cannot push the tenant past its limit. Limits can be raised per account and per tenant.

Inviting members

An owner invites by entering an email address in the dashboard. The invited person joins by signing in with the Google account for that address. No password is set.

StepWhat happens
1. IssueThe owner enters an address; the link appears straight away
2. Hand overSent by email when delivery is configured; otherwise pass the link on yourself
3. JoinThe invited person signs in with Google and joins as a member
4. Promote (if needed)The owner changes them to OWNER from the member list
An invitation can only grant MEMBER. Passing OWNER to CreateInvitation returns invalid_argument. To add an owner, change their role with UpdateMemberRole once they have joined — so that handing over control is decided against a real account rather than against an address.
Somebody who already has an account can be invited too. Accepting adds that account to this tenant as well; their existing memberships stay, so accepting costs them nothing.
If the address on the account that signs in does not match the one invited, joining is refused. An address already in the tenant cannot be invited. Invitation links last seven days.

Joining by QR code

Every tenant has a QR code, and whoever scans it can ask to join. Unlike an invitation sent to a named address, joining this way needs the owner's approval.

StepWhat happens
1. ShowThe owner shows or sends the QR code from tenant settings
2. ScanThe join page opens and names the tenant
3. RequestThey sign in with Google and apply; an account is created if they have none
4. ApproveThe owner approves the request from the member list

The code carries nothing but the tenant's id and is not a secret. Because approval sits in the way, a code that reaches the wrong hands produces a request, not a membership. It therefore has no expiry and no usage limit; stop accepting requests from tenant settings when you want it closed.

RouteApprovalWhy
Invited by the ownerNot neededNaming the address is already the owner deciding who it is
QR codeNeededNobody knows who scanned it, so the owner decides against a real account
Join requests do not take up seats. An invitation reserves one because the owner sent it; a request can arrive from anybody, and letting those reserve seats would let strangers fill the tenant. The limit is checked at approval. The approving owner also has to have cleared a second factor.
Google refuses sign-in from embedded browsers (disallowed_useragent). When sharing a QR code or link through a messaging app, tell people to open it in a normal browser rather than the in-app one. The join page detects this and says so as well.

The tenant's security policy

From "Tenant settings" in the dashboard, an owner can set rules that apply to the members of their own tenant. Everything starts at the default, and nothing changes until it is set.

ItemWhat happensWhen it applies
Permitted email domainsOnly addresses on those domains may joinWhen an invitation is issued and accepted
Password requirementsMinimum length, upper case, digits, symbolsWhen a password is set, changed or reset
Session lifetimeIn days (14 by default)At sign-in
A policy can only be set stricter than the system default — a minimum password length below 8 is not accepted. Somebody who belongs to several tenants gets the strictest of all of them, because the account has one password and one session. Existing members' passwords are held to the new requirement from their next change onwards.

Where a second factor is asked for

Signing in says who somebody is. It does not say that the person at the keyboard right now is still them. For the screens that reach other people's names and contact details, and for the operations that cannot be undone, the account is asked for its authenticator code once more.

WhenWhat is asked
Opening the member list, tenant settings or API keysA code, once per sign-in
Deleting a tenant, handing over ownership, issuing a key, changing a passwordA code from within the last 15 minutes
Inviting a member, approving a join requestA sign-in the provider vouched for, or a password sign-in that cleared a code
It is asked for when you first open one of those screens, not at sign-in. Most sessions never open them, and charging everybody for what few of them do is how a safeguard becomes something people route around.
Signing in with Google or Apple already counts for inviting. The provider carries the protection, so an owner who signs in that way does not need an authenticator of their own to invite. Opening the protected screens is a different question and does need one.
An API key can never satisfy this. There is nobody to ask for a code, so calls that need one are refused outright rather than asked to retry.

Calling as an API key

A key is fixed to the tenant it was issued for. Unlike a person's session it cannot switch tenants, so a leaked key cannot reach around into another one. Issue a separate key per tenant.

RuleDetail
What it may doREAD_ONLY by default — verifying tokens and reading membership. READ_WRITE has to be chosen when the key is issued.
How long it lasts30 days if you do not say, 90 at the most. There is no key without an expiry.
Replacing oneIssue the new key, move the integration over, then revoke the old one. Every plan allows at least two at once so this needs no downtime.
Rate limitCounted per key, separately from the per-address limit.

Issuing and revoking keys, and changing the issuer's own password or deleting their account, cannot be done with a key.

Support by the operator

The service operator holds separate administrative accounts for incidents and enquiries. Those accounts do not belong to any tenant. They cannot create one and cannot accept an invitation, so nobody supervising a tenant is also party to it.

Nobody at the operator can sign in as you. Acting on another account's behalf was removed: it carried that account's permissions wholesale, which made every other rule on this page unenforceable while it was in use.
Your own details are yours. Your name, address, phone number and password cannot be changed by the operator at all — only by you, from your own settings. Whether an address has been confirmed is set by the confirmation flow and by nothing else.

What the operator does to an account is recorded with the target, the time and who did it, and cannot be deleted.