Onboarding a Tenant
This is the end-to-end flow for bringing a new tenant online. It starts with a platform owner and ends with a freshly-minted tenant admin who can manage the tenant from the portal on their own. All of it happens inside the Flightdeck — no shell access, no database edits.
This guide assumes built-in email/password authentication (the default). The OIDC and SAML modes are slightly different and covered at the end.
owner creates the tenant → owner invites the tenant admin → admin clicks email link → /register → logs in → lands at /portal → invites the rest of their team.
Prerequisites
- A platform owner account signed in to the Flightdeck. If this is your first time, run through First-run setup to create the initial owner.
- The six built-in roles and their capabilities are documented in Setup & Login → RBAC roles. This guide uses
owner(platform) andadmin(tenant). - For production, email delivery needs to be configured — see Email delivery below.
Step 1 — Create the tenant
In the sidebar, open Identity → Tenants and click New Tenant. Fill in the basics (name, region, priority tier) and submit.


When the save succeeds, a toast appears at the top-right with a call-to-action link:
✅ Tenant 'Acme Corp' created. Invite admin →
The toast stays up for seven seconds. Click Invite admin → while it's still visible and you'll be dropped on the invite form with the right context already filled in. If you miss it, the same deep link is available from the tenant's edit page (next step).
A TENANT_CREATED event lands in the audit log at this point; see it under Audit → Events.
Step 2 — Review the Team section (optional)
Open any existing tenant's edit page (Tenants → Edit on the row). A new Team section in the sidebar lists users assigned to this tenant plus two deep-link buttons:
- Invite admin — pre-selects the tenant role
admin - Invite with other role… — opens the invite form with just the tenant ID pre-filled so you can pick
developerorviewermanually


Each row shows the user's email (linked to their edit page), name, roles, and status (INVITED / ACTIVE / SUSPENDED). An INVITED user hasn't set their password yet — they're waiting to click the email link.
Step 3 — Send the invitation
Whichever path you took, you land on /users/new with the tenant ID and role already populated.


Fill in:
- Email — where the invitation will be sent.
- Name — optional display name.
- Roles — leave
adminchecked. You can assign multiple tenant roles but you cannot mix platform and tenant roles on one user.
Click Send Invitation. The server:
- Creates a
Userrecord withstatus=INVITEDand a 7-day verification token. - Renders the invitation email from a template and hands it to the configured transport (log or SMTP — see below).
Step 4 — Tenant admin accepts the invitation
The invitee receives an email with a Set your password link that points at /register?token=<uuid>. The token is single-use and expires in 7 days. When they open it:
- They're asked for a password (minimum length is enforced — see built-in auth config).
- On submit, the
Userflips fromINVITEDtoACTIVE, the token is cleared, and they're redirected to/login?registered=true. - They log in with their email and the password they just set.
Because their role is admin (a tenant role), the login flow drops them at /portal, not /.
Step 5 — Hand off to the tenant admin
At /portal the new admin sees the tenant-scoped dashboard — usage, cost, audit, API keys, provider credentials, and a Team page. From Team → Invite, they can invite their developers and viewers without involving the platform owner.


Scope limits the admin to their own tenant: they can only invite users whose role is admin, developer, or viewer, and every invitation they send is scoped to their tenant automatically.
From this point the platform owner is out of the loop unless they need to change role assignments, suspend the tenant, or manage platform-level resources.
Email delivery
The invitation email is built from a templated HTML body and delivered by one of three transports, configured with dvara.flightdeck.email.transport (env: DVARA_FLIGHTDECK_EMAIL_TRANSPORT):
log(default, for local and CI) — the entire email including the/register?token=…link is printed to the Flightdeck logs atINFO. Grep the log by the recipient address to find the token.smtp(production) — uses the standard SMTP properties (spring.mail.host,spring.mail.port,spring.mail.username,spring.mail.password) via Spring'sJavaMailSender. Setdvara.flightdeck.security.builtin.base-urlto the public Flightdeck URL — otherwise the link the user receives will point athttp://localhost:8090/register?token=…, which is exactly the kind of detail you discover only after a real user tries to click it.resend(production) — POSTs to the Resend transactional API. Setdvara.flightdeck.email.resend-api-key(re_…) and verify your sender domain in Resend, or use the sandbox senderonboarding@resend.devfor testing. Samebase-urlrequirement assmtpfor the link's host.
Token lifetime is seven days. Expired tokens render as invitation link is invalid or has expired on /register; there's no self-service resend yet, so the invite needs to be re-sent from /users/new.
Audit events
Onboarding shows up in the immutable audit trail as:
| Event type | Emitted when | Payload highlights |
|---|---|---|
TENANT_CREATED | A platform owner creates the tenant in step 1 | tenant_id, name, status, metadata, actor |
TENANT_METADATA_UPDATED | Any subsequent edits to the tenant | tenant_id, actor, changes (diff per key) |
TENANT_DELETED | The tenant is deleted | tenant_id, name, status, actor |
USER_INVITED | Step 3 succeeds — user persisted with status=INVITED and the invitation email delivered | user_id, email, tenant_id (null for platform-role invites), roles, status (INVITED), actor |
Token acceptance at /register (step 4) does not currently emit its own audit event. If you need richer audit coverage of user lifecycle, the Automation API at /v1/admin/users additionally emits USER_CREATED, USER_ROLE_ASSIGNED, USER_ROLE_REVOKED, and USER_DELETED events from Terraform or CI/CD flows.
OIDC and SAML deployments
If the Flightdeck is configured for OIDC (Enabling OIDC) or SAML, identity is owned by your IdP, not by DVARA, and this flow is slightly different:
- OIDC — users are not sent an invitation email. They exist in DVARA either by being created ahead of time via the Users page (invite-first mode) or by being auto-provisioned on first login from the JWT claims.
/registeris not used. - SAML — same shape as OIDC. With
dvara.flightdeck.security.saml.auto-provision=true, the user is created on first successful assertion usingdefaultRolesanddefaultTenantId. For CI/CD automation against/v1/admin/**, issue a personal access token (dvara_pat_…) from/settings/tokensafter a browser login.
In both modes, the Team section on the tenant edit page and the portal team page still work for role assignments; only the email + password flow is skipped.
Troubleshooting
The CTA toast disappeared before I could click it. It stays up for seven seconds. If you missed it, open the tenant's edit page — the Team section has the same deep-link buttons.
The invitee clicked the link and got invitation link is invalid or has expired. Either the 7-day TTL has passed or the invite has already been consumed. Re-send from /users/new.
The invite email never arrived. Check the email transport. In log mode, the email is in the Flightdeck logs — grep for the recipient address. In smtp mode, verify dvara.flightdeck.security.builtin.email.from and the SMTP connection properties, and confirm the SMTP host is reachable from the Flightdeck pod.
The admin logged in but landed on / instead of /portal. That means they ended up with a platform role (owner, policy-admin, billing-admin) instead of a tenant role. Edit the user and check the role assignments — mixing platform and tenant roles on one user is rejected at create time, but a role mistakenly set to owner will land the user on the platform dashboard.