MATIH Platform is in active MVP development. Documentation reflects current implementation status.
7. Tenant Lifecycle
Tenant Management
Creating Tenants

Creating Tenants

Tenant creation is a two-phase process: first the tenant record is created in PENDING status, then asynchronous provisioning begins. The TenantController.createTenant() endpoint validates input, persists the tenant, and triggers the provisioning pipeline.


Create Tenant Request

The CreateTenantRequest DTO defines the required and optional fields:

public class CreateTenantRequest {
    @NotBlank @Size(min = 2, max = 100)
    private String name;
 
    @Pattern(regexp = "^[a-z0-9][a-z0-9-]*[a-z0-9]$")
    @Size(min = 3, max = 63)
    private String slug;
 
    @Size(max = 500)
    private String description;
 
    @NotBlank @Email
    private String adminEmail;
 
    @Size(max = 50)
    private String adminFirstName;
 
    @Size(max = 50)
    private String adminLastName;
 
    private TenantTier tier = TenantTier.FREE;  // Default
 
    @NotBlank
    private String region;
 
    private String externalOrgId;  // Optional external integration
}

Validation Rules

FieldRule
nameRequired, 2-100 characters
slugLowercase alphanumeric with hyphens, 3-63 characters, must start and end with alphanumeric
adminEmailRequired, valid email format, must not already be registered
regionRequired, valid cloud region identifier
slug uniquenessNo existing non-deleted tenant with the same slug
adminEmail uniquenessNo existing non-deleted tenant with the same admin email

Creation Flow

POST /api/v1/tenants
    |
    v
TenantValidationService.validateCreateRequest(request)
    |
    v
Check duplicate slug (tenantRepository.existsBySlugAndDeletedFalse)
    |
    v
Check duplicate admin email (tenantRepository.findByAdminEmailAndDeletedFalse)
    |
    v
TenantMapper.toEntity(request) --> Tenant entity (status = PENDING)
    |
    v
applyTierDefaults(tenant) --> Set resource limits from tier
    |
    v
tenantRepository.save(tenant) --> Persist to database
    |
    v
provisioningService.startProvisioning(tenantId) --> Async provisioning
    |
    v
Return HTTP 201 with TenantResponse

Tier Defaults

When a tenant is created, the service applies default resource limits based on the selected tier. These defaults are only applied if the field is not explicitly set in the request:

TierMax UsersMax PipelinesMax Queries/DayStorage (GB)Retention (Days)
FREE / STARTER5101,0001030
PROFESSIONAL5010010,000500365
ENTERPRISE-1 (unlimited)-1-1-1-1

Source: TenantService.applyTierDefaults() and the getTierDefault* methods.


Example: Create a Tenant

curl -X POST http://localhost:8082/api/v1/tenants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "description": "Enterprise data analytics platform",
    "adminEmail": "admin@acme.com",
    "adminFirstName": "Jane",
    "adminLastName": "Smith",
    "tier": "PROFESSIONAL",
    "region": "eastus"
  }'

Response (201 Created):

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "name": "Acme Corporation",
  "slug": "acme-corp",
  "description": "Enterprise data analytics platform",
  "tier": "PROFESSIONAL",
  "status": "PENDING",
  "region": "eastus",
  "cloudProvider": "azure",
  "environment": "dev",
  "adminEmail": "admin@acme.com",
  "maxUsers": 50,
  "maxPipelines": 100,
  "maxQueriesPerDay": 10000,
  "storageLimitGb": 500,
  "dataRetentionDays": 365,
  "deploymentType": "SHARED",
  "isTrial": true,
  "createdAt": "2026-02-12T10:30:00Z"
}

Error Responses

StatusErrorCause
400Validation errorMissing required field or invalid format
409DuplicateResourceExceptionSlug or admin email already registered
{
  "error": "DuplicateResource",
  "message": "Tenant with slug 'acme-corp' already exists",
  "field": "slug",
  "value": "acme-corp"
}

Self-Registration

The RegistrationController provides a public self-registration flow at POST /api/v1/tenants/register that does not require authentication:

@PostMapping("/register")
@Operation(summary = "Self-register a new tenant")
public ResponseEntity<RegistrationResponse> register(
        @Valid @RequestBody SelfRegistrationRequest request) {
    RegistrationResponse response = registrationService.register(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

This flow includes email verification via POST /api/v1/tenants/verify-email and GET /api/v1/tenants/verify-email?token=.... Verification emails can be resent via POST /api/v1/tenants/resend-verification?email=....


Available Plans

The GET /api/v1/tenants/plans endpoint returns all available subscription plans with pricing and feature lists. This is a public endpoint for display on signup pages:

TierPrice/MonthKey Features
FREE$03 services, 5 users, 100 queries/day, community support
PROFESSIONAL$49910 services, 25 users, 10K queries/day, email support, BI dashboards
ENTERPRISEContact salesUnlimited everything, 24/7 SLA, AI features, SSO/SAML, dedicated infra

Source Files

FilePath
Controllercontrol-plane/tenant-service/src/main/java/com/matih/tenant/controller/TenantController.java
Registrationcontrol-plane/tenant-service/src/main/java/com/matih/tenant/controller/RegistrationController.java
Servicecontrol-plane/tenant-service/src/main/java/com/matih/tenant/service/TenantService.java
Request DTOcontrol-plane/tenant-service/src/main/java/com/matih/tenant/dto/request/CreateTenantRequest.java
Entitycontrol-plane/tenant-service/src/main/java/com/matih/tenant/entity/Tenant.java