Tenant and Onboarding
Every data plane the EDK ships is multi-tenant. The issuer, verifier, AS, DID resolver, and KMS containers all run as single processes that serve any number of tenants. Tenant is the unit of routing, the unit of authorization, the unit of persistence isolation, and the unit of configuration. A set of EDK tenant modules and the services-tenant-rest admin surface is what makes that work.
This section covers the parts a deployment actually touches: the tenant model itself, the registration journeys an operator or prospective tenant goes through to land in the system, the resolver chain that maps an incoming HTTP request to a tenant, the public-endpoint binding the data planes consult when they advertise URLs, the per-tenant configuration storage, and the application tenant that owns the control plane.
The Module Stack
The tenant subsystem is published as a set of Maven artifacts under the com.sphereon.edk group. The stack is:
lib-tenant-publiccarries the models, inputs, and SPIs:Tenant,TenantStatus,TenantDomain,TenantDomainKind,TenantPublicEndpoint,TenantPublicEndpointServiceType,TenantSlug,TenantRoles,TenantBootstrapStatus,TenantSignupRequest,TenantSignupStatus,TenantRegistrationStep,TenantOnboardingStatus,License,LicenseLimits,LicenseFeatures,RegisterTenantArgs,OwnerInput(sealed:Local,Federated,Hybrid),OnboardingChannel,TenantResolutionSettings,TenantConfigSecretClassifier,HttpHostTenantInput, and theTenantInvalidationBrokerinterface.lib-tenant-servicecarries the service commands and the policy SPIs:RegisterTenantServiceCommand,ReconcileTenantRegistrationServiceCommand,BootstrapTenantServiceCommand,GetTenantOnboardingStatusServiceCommand,TenantCrudServiceCommands,TenantDomainServiceCommands,TenantPublicEndpointServiceCommands,AsInstanceAdminCommands, the signup command set, the onboarding-policy SPI (TenantOnboardingPolicywithFailClosedTenantOnboardingPolicyas the default), the signup-policy SPIs (TenantSignupPolicy,SignupChallengeVerifier,SignupAuthorizationPolicy,SignupTokenHasher), the license and quota services (LicenseService,TenantQuotaService,TenantServiceInstancePolicy), the audit emitter, the schema initializer, the per-tenant isolation strategy selector, and the resolution settings reader.lib-tenant-persistence-apiandlib-tenant-persistence-postgresqlcarry the repositories. The Postgres impl is the production binding:TenantRoutingRepository,TenantRoutingResolver,TenantDomainRepository,TenantPublicEndpointRepository,TenantConfigPropertyRepository,TenantBootstrapRepository,TenantSignupRequestRepository,TenantRegistrationLogRepository, plus theTenantRegistryDatabaseSQLDelight schema (tenant_routing,tenant_domain,tenant_public_endpoint,tenant_config_property,tenant_bootstrap,tenant_signup_request,tenant_registration_log,tenant_registration_step_log).lib-tenant-provisioningcarries theTenantProvisionerand the JDBC maintenance executor that creates per-tenant schemas, databases, or hosts when a tenant is provisioned with non-shared isolation.lib-tenant-resolution-implcarries the resolver chain:PlatformSubdomainTenantResolver,CustomDomainTenantResolver,EdkRoutableSlugLookup, theTenantResolutionSettingsBinder, and the in-memory resolver cache.lib-tenant-config-sourcecarries theTenantConfigPropertySourceand its source-contribution wiring; together with the IDK property resolver chain this gives the runtime the App, Tenant, and Principal config scopes.lib-tenant-federation/{public,service}carries the per-tenant IDP registry SPIs (TenantIdpRepository,TenantIdpSecretStore,TenantIdpConnectivityProbe) and their default impls.services-tenant-restmounts theTenantAdminHttpAdapterat/api/v1/tenantswithTenantPathPolicy.Noneso the path's{tenantId}is the operation target rather than the acting tenant. The acting tenant always comes from the JWT.services-application-restmounts theApplicationAdminHttpAdapterat/api/v1/applicationfor control-plane operations (application tenant bootstrap, license upload and verification, secret backend selection, onboarding policy).
The Tenant Model
The EDK Tenant is a superset of the IDK projection at com.sphereon.data.store.party.model.Tenant. The IDK projection stays smaller so the open-core SDK exposes the minimal surface mobile and standalone consumers need; the server EDK uses the richer model.
The fields hierarchical multi-tenant routing requires, beyond what the IDK projection has:
slugis the globally unique URL-safe label that doubles as the platform subdomain label and as the peelable path segment in the resolver dispatcher. Lowercase, hyphen-separated, must match^[a-z][a-z0-9-]{0,62}$, no consecutive hyphens.parentTenantIdis the self-reference that gives the parent/child hierarchy.nullfor root tenants. Cycle detection runs at insert time by walking upparent_tenant_id.statusis the explicit lifecycle marker:ACTIVE(fully provisioned and reachable),SUSPENDED(registry rows present but the resolver rejects requests withtenant_suspended), orPENDING_VERIFICATION(registered but a required step is outstanding, such as owner email verification or a custom-domain DNS challenge).systemis a generic flag for "this is not a customer tenant". System tenants are excluded from default listings and from slug-based and parent-based resolution. Higher layers (VDX) use it to materialise their control-plane tenant; pure-EDK consumers can use it for their own system-tenant patterns. The EDK itself does not assign privileges to the flag.tenantTypemirrors the IDK projection (ORGANIZATION,INDIVIDUAL, and so on).
A tenant has zero or more TenantDomain rows. Two kinds:
PLATFORM_SUBDOMAIN. The SaaS-owned host of the form<slug>.<platform-base>. Auto-created at tenant registration. Always verified at insert time, because the platform controls the DNS for<platform-base>.CUSTOM_DOMAIN. A host the customer brings (wallet.acme.com). Inserted unverified. The resolver chain skips unverified custom-domain rows, so the customer can preserve them in the registry while completing the DNS verification flow.
A tenant has zero or more TenantPublicEndpoint rows. Each row binds a (tenant, serviceType) to the host, pathPrefix, and wellKnownPath under which that service is reachable for that tenant. The service types are OID4VCI_ISSUER, OID4VP_VERIFIER, and OAUTH2_AUTHORIZATION_SERVER. The data planes consult these rows when they advertise URLs in metadata, in credential_offer_uri, in OID4VP request_uri_base, in OAuth issuer claims, and so on.
Registration Goes Through One Place
RegisterTenantServiceCommand is the single command every tenant registration goes through. Whether the registration arrives via the admin REST, via an admin invite, via self-service signup, or via the one-shot BootstrapTenantServiceCommand, the work to actually create the tenant lives in the same command implementation. That command is responsible for the full atomic bootstrap:
- Authorising the call through the
TenantOnboardingPolicy(the first thing it does, before any DB query, so an unauthorised caller cannot enumerate slug uniqueness through a registration probe). - Validating slug, parent existence, cycle absence, and owner shape.
- Inserting the
tenant_routingsidecar row and the initial platform subdomain (ROUTING_INSERTED). - Provisioning the per-tenant storage container per the configured isolation strategy (
ISOLATION_PROVISIONED). - Ensuring the per-tenant domain schemas (
TENANT_SCHEMAS_ENSURED) and the user/group/role schema (USER_SCHEMA_ENSURED). - Provisioning the default Authorisation Server instance when
hostedAs = true(AS_PROVISIONED). - Creating the owner User row and minting the owner invitation token for local owners (
OWNER_PROVISIONED,OWNER_INVITATION_MINTED).
Each step is recorded into tenant_registration_log as a TenantRegistrationStepRecord. The reconcile janitor (ReconcileTenantRegistrationServiceCommand) walks the STANDARD_COMPENSATION_ORDER in reverse on failure and calls the matching deprovision delegate for each step that completed. Higher layers extend compensation by recording their own step ids and supplying their own reverse logic.
The shape of "where did this call come from" is carried out-of-band on a session-scoped OnboardingChannelHolder. The calling adapter (REST endpoint, signup-confirm command, bootstrap command) populates the holder with one of three OnboardingChannel variants before delegating:
Operatorfor an authenticated admin call. Carries the session tenant id and the roles extracted from the validated JWT.SelfServiceSignupfor the materialisation of a confirmed signup request. Carries only the signup request id; the policy loads the row and derives parent, slug, and email from there.Bootstrapfor the one-shot first-tenant claim. OnlyBootstrapTenantServiceCommandproduces this channel, and only after it has atomically claimed the durabletenant_bootstrapgate.
The fail-closed EDK default (FailClosedTenantOnboardingPolicy) rejects every Operator and SelfServiceSignup channel. Production deployments bind a real TenantOnboardingPolicy that interprets roles and signup state. A pure-EDK build that forgets to bind a real policy fails closed rather than fails open.
Four Registration Journeys
Four distinct user-facing journeys lead into RegisterTenantServiceCommand. Each is documented in detail on its own page; the high-level shape is:
- Admin direct creation. An authenticated admin posts to
/api/v1/tenants. The tenant is created immediately. The only journey that works when no SMTP / email service is configured. See Registration Journeys. - Admin invite by email. Same shape as admin direct creation, except after the tenant is created the EDK hands the owner invitation token to the
enterprise-email-serviceserver-side and the owner receives an activation email. Requires SMTP. See Registration Journeys. - Self-service signup. A prospective tenant owner posts to
/api/v1/tenants/signup/request. The flow walks throughPENDING_EMAIL,PENDING_APPROVAL(when policy requires operator approval),CONFIRMED, and finallyREGISTERED. Requires SMTP and is gated by theself-signuplicense feature. See Registration Journeys. - Bootstrap. The one-shot first-tenant claim through
BootstrapTenantServiceCommand. Used to bring up the very first real tenant on a fresh deployment. See Application Tenant and Bootstrap.
License gates apply across all four journeys: maxRootTenants, maxTotalTenants, maxHierarchyDepth, subtenantsAllowed, and the subtenants / self-signup / custom-domains / federation feature flags. Pure-EDK builds bind UnboundedLicenseService so registration is unconstrained unless a real license-service impl replaces it.
What This Section Covers
- Tenant Model. The
Tenantshape, status lifecycle, hierarchy, slugs, system tenants, and how the EDK Tenant relates to the IDK projection. - Tenant Resolution. The layered resolver chain (JWT, subdomain, custom domain, path slug), the well-known URL forms, the in-memory cache, and the cross-replica invalidation channel.
- Domains and Public Endpoints.
TenantDomain(platform subdomain and verified custom domain),TenantPublicEndpoint(per-service URL binding), and how the data planes consult them. - Registration Journeys. The four ways a tenant lands in the system: admin direct, admin invite, self-service signup, and bootstrap. End-to-end flows, web screens, license gates, and the signup state machine.
- Application Tenant and Bootstrap. The control-plane tenant, the durable bootstrap gate, the application admin REST under
/api/v1/application, license activation and snapshot loading. - License, Quota, and Policy.
License,LicenseLimits,LicenseFeatures, the onboarding-policy SPI, the signup-policy SPIs, and how a deployment overlays its own. - Per-Tenant Configuration. The
tenant_config_propertytable, theTenantConfigSecretClassifier, secret references through the selected backend, and the App / Tenant / Principal scope chain. - Tenant Isolation. Row-level vs per-tenant database isolation, the
TenantProvisioner, per-tenant signing keys, encryption at rest, and how a tenant admin's reach is constrained.
What This Section Does Not Cover
- Multi-instance fan-out per tenant (N issuer instances per tenant for N credential programs). This is a VDX capability built on the party model.
- Operator dashboards and UIs. The EDK exposes the REST surface; the UI is a layer above.
- Cross-tenant catalog projection. VDX.
- Durable workflow execution beyond the EDK's reconcile janitor. The janitor handles registration step compensation; longer-running sagas live in VDX.
- License-format details (signed JSON, JWT). EDK only exposes the parsed snapshot; concrete formats live in VDX.