Skip to main content
Version: Latest

5. Registration API

5.1 Endpoint

POST api/Tenant/Registration/Register
  • Controller: TenantRegistrationController ([Route("api/Tenant/Registration")], [AllowAnonymous]).
  • Request → RegisterNewTenantRequest → MediatR → RegisterNewTenantRequestHandlerITenantService.RegisterAsync(request, cancellationToken).
  • FluentValidation: RegisterNewTenantRequestValidator runs 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

  1. Generates a random 6-digit TenantId (collision-checked against existing tenants).
  2. Creates the Tenant entity — constructor sets IsActive = true; IsVerified defaults to false.
  3. Sets CountryId/RegionId/CityId/Language (normalized) on the tenant.
  4. Checks SubscriptionSettings.Enable_Tenant_Registration — fails the request if self-registration is disabled.
  5. Checks whether a demo subscription was already requested for this email (idempotency guard).
  6. Resolves the SubscriptionPlanPackage by PlanPackageId (.Include(x => x.PackageFk)) — fails if not found.
  7. Begins a repository transaction.
  8. 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.
  9. Generates a second 6-digit code (via the same GetTenantIdAsync helper) as the OTP/verification token and saves it via tenant.SaveVerificationToken(otp).
  10. Saves the tenant, publishes NewTenantRegisteredEvent.
  11. Registers a demo subscription record.
  12. Commits the transaction.

5.6 What NewTenantRegisteredEvent does — and does not — do

Four notification-only handlers run on this event:

  • NotifyAdministrationAboutRegisteredTenantHandler
  • SendRegistrationWhatsAppOtpHandler
  • SendVerificationTokenThroughSmsHandler
  • SendVerificationTokenThroughEmailHandler

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

ConditionResponse
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 SubscriptionPlanPackage400/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