Skip to Content
ConceptsTenancy

Tenants and users

Tenant (your organization: API keys, branding) ├── Users (id · tenantKey · displayName · email · phone · locale · timezone · units) └── Flows · Sources · Contacts · Webhooks · Runs

Every record you supply (sources, adapters, flows, and users) carries the same identity pair: a displayName shown in the GUI (change it any time) and a stable tenantKey, your own identifier, unique per tenant, so your systems can keep referencing the record after a rename (tiles use their key for this). tenantKey is optional everywhere except users.

  • A tenant is your account with Growth Engine, the party that talks to the API, and the isolation boundary. Nothing is visible across tenants and there is no “all tenants” parameter anywhere in the API.
  • A user is a person you register so they can use the Growth Lab GUI. Flows, sources, contacts and contact lists belong to a user: a signed-in user sees and edits only their own, and everything they create is theirs. Your backend (a tenant key) sees every user’s records and can act for a user. You manage users; the Engine does not run sign-up, passwords, roles or groups. Whatever hierarchy your platform has (customers, regions, permissions) stays in your platform; you decide which of your users may open Growth Lab and mint them a session token when they do.
  • Each user has an Engine-assigned id (GUID) and a tenantKey, your identifier for that person (your user id), unique within your tenant. Use it to map Engine users back to your records.

What the Engine stores about a user

Only what Flow and Engine operation needs:

FieldPurpose
idEngine GUID (stable primary key)
tenantKeyyour identifier for the user, unique per tenant
displayNameshown in the GUI and in audit records
email, phone (optional)so the user can receive reports/notifications, only after verification (below)
locale, timezone, units (optional)UI language, report timestamps, °C/°F
emailVerified, phoneVerifiedverification status + timestamps
canOverrideBrandingmay this user set a personal brand for their own account (Branding)

The Engine stores no passwords and no roles.

Who owns what

RecordOwnerUser sessionTenant key / tenant session
Flows, sources, contacts, contact listsa user (ownerUserId / ownerUserKey)sees and changes only its own; creates as itselfsees all; ?userKey= narrows a list; ownerUserKey on create or PATCH assigns (or null = private to the tenant, invisible to users)
Runs, reportsfollow their flowown flows onlyall
Adapters, webhooks, branding, tiles, keys, usersthe tenantas gated todayall

A flow’s sources and contact lists, and a list’s contacts, must belong to the same user as the record you are saving (422 otherwise). tenantKey is unique per owner, so two users may both register a field called F-12. The hourly targets sync runs once per user, so synced sources belong to the user whose userKey the adapter received.

Who authenticates how

CallerCredentialPrincipalRoutes it may call
Your backendTenant API key (Authorization: Bearer glpk_…)tenanttenant- and user-gated routes (subject to the key’s scopes)
A GUI userSession cookie, obtained by exchanging a session token you minted for that useruseruser-gated routes only, on records they own
Your GUI acting as the tenantSession cookie from a tenant session token (POST /v1/session-tokens; no user, frozen to the minting key’s scopes)tenantsame as the minting key
Soiltech operationsAdmin API key (not tied to a tenant)admineverything (provisioning is admin-only)

Every route declares one of these gates; see API authentication → gates and GUI authentication for the token flow.

Verified addresses

Available.

The Engine never sends to an email address or phone number it has not verified; this applies to users and to alert contacts. Verification is triggered by you (or from the GUI):

  • POST /v1/users/{id}/verifications / POST /v1/contacts/{id}/verifications with { "channel": "email" | "sms" }.
  • Email: the Engine sends a one-time link (valid 24 h) to a branded confirmation page.
  • SMS: the Engine texts a 6-digit code (valid 10 min, 5 attempts); confirm with POST …/verifications/confirm { "code": "123456" } (from your backend or the GUI dialog).
  • On success the record shows emailVerified / phoneVerified and the Engine emits a user.verified / contact.verified webhook. Changing an address resets its verification.
  • Sends to unverified addresses are skipped and recorded (skipped_unverified), never silently dropped.