Provider registration
[admin] [signup]
Self-service registration at https://admin.licensio.io/register. Registration creates one free access — no credit card, no plan selection, no portal address. A new provider is ready in under two minutes.
Note: the public marketing site at
https://licensio.io(its own Next.js appapps/marketing, separate Vercel deployment, i18n EN/DE with a flag dropdown in the header) links from the header (Sign up) and hero (Start for free →) tohttps://admin.licensio.io/register?lang=<locale>. The language is carried over into the register form. The contact section opensmailto:malte@licensio.io.
Language on the register page: resolution order is
?lang=en|de(URL param) > LocalStoragelicensio_locale> defaulten. A flag dropdown at the top right allows a manual switch; the selection is persisted in the admin LocalStorage. Form labels, buttons and error messages are all translated.
Lifecycle after registration
- Provider + auth user + tenant_users link are created in
/api/register(self-service signup, email confirmed directly). The tenant is created on the free plan, with the accepted terms version + timestamp (terms_accepted_at,terms_version). - No portal subdomain is created at registration.
tenants.slugstaysNULLuntil the provider module is activated (App Store, later); only then is a subdomain chosen and provisioned. Until then the provider works entirely in the admin. - Welcome email to the registration address (Resend,
from: noreply@licensio.io): greeting by first name ("Hi Malte,") + "Welcome to licensio.io" + CTA toadmin.licensio.io. Best-effort (registration does not fail if Resend is down). The login-OTP email (packages/auth/src/email.ts) likewise greets by first name when available. - Redirect to
/onboarding. There is no Stripe checkout on the registration path — an upgrade to a paid tier happens later inside the admin under Settings → Subscription.
Forgot password / reset password
/forgot-password(maintenance-exempt): email input →POST /api/auth/forgot-passwordcallssupabase.auth.resetPasswordForEmail(email, { redirectTo: '/reset-password' }). The response is always{ success: true }(no info leak)./reset-password(maintenance-exempt): waits for the SupabasePASSWORD_RECOVERYevent, then callsupdateUser({ password })client-side. The recovery session comes automatically via the email link; no separate server endpoint needed.
Who it is for
New providers who want to start with licensio.
Step by step
1. Fill in the account fields
The register form asks for exactly these fields — nothing else:
- First name + Last name: the first two fields (one row, two columns), both required, max 80 characters. Stored on the admin user (
tenant_users.first_name/last_name, migration 096) and used to address the provider by first name in the welcome and login-OTP emails ("Hi Malte,"). Editable later under Settings → Advanced → Profile (/settings/advanced/profile). - Company name
- Email: primary login
- Password: min. 8 characters (+ confirm)
- Company country: a profile field (
tenants.country) used for billing currency and compliance. It does not create a Stripe account and does not set a portal address at this point. - Industry (NACE Rev. 2.1): required. A two-level picker (section A–V → division, 87 divisions, DE/EN labels from the official Eurostat source). Up to 3 industries, the first is the main industry. Stored in
tenants.industry_codes(migration 093), validated server-side against thenace_divisionsreference table. - Accept terms + privacy (required checkbox)
There is no plan selection, no tier cards and no shop/subdomain field.
2. Submit
The backend (/api/register) runs:
- Validation (first/last name non-empty + max 80, email format, password length, company name ≥ 3 chars, supported country, at least one valid NACE industry code, terms accepted — see
apps/admin/lib/register-validate.ts+validateIndustryCodes) - Cloudflare Turnstile captcha check (fail-open while
TURNSTILE_SECRET_KEYis unset) - Email uniqueness check
auth.admin.createUser({ email_confirm: true }): account active directly without mail confirmationtenantsINSERT: free plan,slug: NULL,industry_codes, billing currency derived from the country, default branding (primary_color: #0070F3,button_style: rounded),terms_accepted_at+terms_versiontenant_usersINSERT withrole='admin',first_name,last_name- Redirect: with provider intent to
/onboarding/provider, otherwise/onboarding(post-register OTP e-mail confirmation in between)
On errors the auth user + tenant entry is rolled back.
3. Onboarding wizard
After a successful registration a wizard guides you through branding, Stripe Connect and the first product:
- Welcome: overview of the next steps.
- Branding: company name + primary color (logo + further colors later in the dashboard)
- Stripe Connect: a click on "Connect Stripe" starts the Express onboarding via
/api/stripe/connect/authorize?return=onboarding. After the Stripe setup you land back on/onboarding?step=4&stripe=connected. - First product: direct link to product creation (or skip)
- Done: overview of "What's next", link to the dashboard
A progress bar at the top + a step counter at the bottom show the progress. The URL parameter ?step=N jumps to the respective step (e.g. after the Stripe Connect return).
4. Plan gates in the admin
Features that are not included in the current plan are hidden rather than locked (since 2026-05-29, no more lock banner / 🔒 badge / upgrade CTA):
- The Widget + Domain tab in settings as well as the associated advanced tiles appear only if the plan has the feature
- Bulk Import, API, PrintFarm likewise: the nav entry is missing completely without the feature
- Directly opening a feature page without authorization → 404 (
notFound()) - Feature promotion runs via newsletter + app store (Phase 4), no longer via lock hints
The visibility logic is in apps/admin/lib/plans.ts (isFeatureAvailable + has_* flags). apps/admin/components/PlanGate.tsx renders null when a feature is missing (defensive content layer, future-proof). Backend API routes additionally check the plan server-side (403).
Frequently asked questions
Which fields does registration ask for? Company name, email, password, company country, industry (NACE Rev. 2.1, required) and the terms checkbox. Nothing else — no plan choice, no tier cards, no subdomain.
When do I get a portal address (<name>.licensio.io)?
When the provider module is activated (App Store, later). Only then is a subdomain chosen and provisioned. Admin actions that would send a portal link (invite customer, activate, send mail, resend registration, reset password, newsletter, send notification) are disabled until then.
Can I upgrade to a paid tier? Yes, later inside the admin under Settings → Subscription. Registration always starts on the free access.
Is data handled GDPR-compliant on account deletion?
Provider deletion runs via a support request. GDPR Art. 17 is handled by deleting tenant_users + tenants; customer data in deleted_customers is archived (anonymized).
Related
- Access & terms: what the free access includes
- Getting started: after the registration
- Branding: customize the portal