5. Registration API
5.1 Endpoint
POST api/Tenant/Registration/Register
- Controller:
TenantRegistrationController([Route("api/Tenant/Registration")],[AllowAnonymous]). - Request →
RegisterNewTenantRequest→ MediatR →RegisterNewTenantRequestHandler→ITenantService.RegisterAsync(request, cancellationToken). - FluentValidation:
RegisterNewTenantRequestValidatorruns before the handler (includes the location validation described in Chapter 6).
5.2 Request contract
public class RegisterNewTenantRequest
{
public Guid PlanPackageId { get; set; }
public Guid CountryId { get; set; }
public Guid RegionId { get; set; }
public Guid CityId { get; set; }
public string Name { get; set; } // organization name, Arabic
public string FName { get; set; } // organization name, English
public string AdminEmail { get; set; }
public string? AdminPhoneNumber { get; set; }
public string? Language { get; set; } // "ar" | "en", free text, normalized server-side
}
All four ID fields (PlanPackageId, CountryId, RegionId, CityId) are GUIDs, not names or
codes. The current contract does not accept location or package names — if a client only has display
names, it must resolve them to IDs via the prerequisite APIs first.
There is no name-based fallback and none should be added silently (see
Chapter 6 §6.6).
5.3 Sample payload (verified dev scenario)
{
"planPackageId": "1c970000-d8b7-001d-dabd-08dd5d047b83",
"countryId": "1c970000-d8b7-001d-fb7d-08dd5d047db3",
"regionId": "029e7df5-76ca-4546-b12a-6863d8d9a214",
"cityId": "2c303996-cdbf-4ec2-9216-a7bf9d46aa1a",
"name": "شركة اوروبا تكس للأقمشة",
"fName": "UROP E TEX",
"adminEmail": "mohamed.alqlisi+location-test@gmail.com",
"adminPhoneNumber": "966543755627",
"language": "ar"
}
5.4 Success response
{
"data": "342580",
"statusCode": 200,
"succeeded": true,
"messages": ["..."]
}
data is the newly-generated 6-digit TenantId (e.g. "342580") — collision-checked, randomly
generated, used as both the tenant's identifier and (with the TenantId HTTP header) the login-time
tenant selector.
5.5 What happens inside TenantService.RegisterAsync
- Generates a random 6-digit
TenantId(collision-checked against existing tenants). - Creates the
Tenantentity — constructor setsIsActive = true;IsVerifieddefaults tofalse. - Sets
CountryId/RegionId/CityId/Language(normalized) on the tenant. - Checks
SubscriptionSettings.Enable_Tenant_Registration— fails the request if self-registration is disabled. - Checks whether a demo subscription was already requested for this email (idempotency guard).
- Resolves the
SubscriptionPlanPackagebyPlanPackageId(.Include(x => x.PackageFk)) — fails if not found. - Begins a repository transaction.
- Assigns a database connection strategy: a dedicated
Shumoul.Saas.MultiTenancyApi-generated DB name (CreateTenantDBNameAsync) for a separated-DB tenant, or a shared-DB connection string. - Generates a second 6-digit code (via the same
GetTenantIdAsynchelper) as the OTP/verification token and saves it viatenant.SaveVerificationToken(otp). - Saves the tenant, publishes
NewTenantRegisteredEvent. - Registers a demo subscription record.
- Commits the transaction.
5.6 What NewTenantRegisteredEvent does — and does not — do
Four notification-only handlers run on this event:
NotifyAdministrationAboutRegisteredTenantHandlerSendRegistrationWhatsAppOtpHandlerSendVerificationTokenThroughSmsHandlerSendVerificationTokenThroughEmailHandler
None of these provision the tenant's database. Registration only creates rows in the shared/root
database (Tenant, subscription records) — the tenant's own application database is not created or
seeded at this point. See Chapter 7 for when provisioning actually happens.
5.7 Error responses
| Condition | Response |
|---|---|
| Location invalid (country/region/city missing, inactive, or mismatched) | 400 with a specific validation message — see Chapter 6 |
PlanPackageId does not resolve to an active SubscriptionPlanPackage | 400/failure result, no tenant created |
Self-registration disabled (Enable_Tenant_Registration = false) | Failure result, no tenant created |
| Duplicate email (demo subscription already requested) | Failure result, no tenant created |
