Preferred Subdomain
Applicants going through the reseller onboarding flow can optionally propose the subdomain their tenant will be provisioned under — e.g., acme-corp → acme-corp.autocom.app. If the name is available when provisioning runs, they get it. If not, provisioning falls back to the existing auto-generation logic and the random 8-char ID is always available as a permanent secondary address.
Why we don't enforce uniqueness at application time
You might expect the API to reject an application whose preferred_domain is already taken. It doesn't, and that's deliberate:
- Race condition — between application submit (
t=0) and provisioning (t=0+admin-review-time, typically minutes to days later), another applicant could claim the same name. Enforcing at submit creates false confidence. - Admin KYC review window — applications sit pending for hours. Holding a subdomain "reserved" for a pending application for days isn't desirable.
- Simpler rollback — if we rejected at submit, a second submit from the same user after a rejection (e.g., name-suggestion retry) would still need the same handling at provision.
So the contract is: submit = format check only, provision = availability check. The applicant is told at approval time if their preference was taken.
Applicant request
POST /api/v1/reseller-register/apply HTTP/1.1
Content-Type: application/json
{
"referral_code": "WELCOME-2026",
"name": "Jane Doe",
"email": "jane@example.com",
"password": "...",
"password_confirmation": "...",
"business_name": "Acme Corporation",
"preferred_domain": "acme-corp"
}
Validation (at submit)
The PreferredDomainValidator enforces:
| Rule | Reason |
|---|---|
| 3 ≤ length ≤ 63 characters | DNS label limits (RFC 1035) |
Only a-z, 0-9, - |
DNS label character set |
Cannot start or end with - |
DNS label syntax |
| Not in the reserved words list | Platform subdomains stay off-limits |
On the reserved list (default):
www, api, admin, app, apps, auth, login, logout, signup, register,
cdn, static, assets, media, files, mail, email, ftp, smtp, imap,
landlord, platform, super, superadmin, root,
dashboard, docs, help, support, status,
billing, invoice, payment, payments,
dev, staging, test, demo, sandbox,
blog, news, marketing, about
Extend it via config (no code change needed):
// config/reseller-admin.php
'preferred_domain' => [
'reserved' => ['my-brand', 'company-x', 'reserved-for-future'],
],
Validation failure response
// HTTP 422
{
"message": "Subdomain 'admin' is reserved for platform use.",
"errors": {
"preferred_domain": ["Subdomain 'admin' is reserved for platform use."]
}
}
Normalization
Input is lowercased and trimmed before storage. " Acme-Corp " becomes "acme-corp". This is done by the controller before the application row is created, so what's stored is the canonical form.
Provisioning-time resolution
When ProvisionResellerTenantJob runs (after admin approval), it:
- Creates the tenant with a random 8-char ID (always unique).
- Inserts the first Domain row for that random ID (primary, never changes).
- For the human-readable alias:
- If
application.preferred_domainis set AND the name is still available AND format is still valid → use it. - Otherwise → fall back to
Str::slug(business_name)with-1,-2, … suffix on collision.
- If
- Inserts the alias Domain row.
A log line is emitted when the fallback kicks in:
[2026-04-18 10:00:00] local.INFO: ProvisionResellerTenantJob:
preferred_domain 'acme' unavailable for application {uuid};
falling back to auto-generated.
Both domain rows point at the same tenant — the tenant is reachable via either subdomain.
What if the applicant wants to change it later?
Not supported in this release. The tenant's provisioned domain is fixed after approval. An admin can manually insert additional Domain rows (e.g., a custom domain app.bigcorp.com) but there's no UI for the tenant to do this themselves.
Future work tracked in the landlord domain-management admin panel (not yet built).
Tests
- Unit:
PreferredDomainValidatorTest— format rules, reserved list, normalization, config override - Feature:
PreferredDomainApplicationTest— submit-flow coverage for null + set preferred_domain paths
Run locally:
docker compose -p autocom-dev exec app \
php artisan test --filter="PreferredDomain"