Preferred Subdomain

Applicants going through the reseller onboarding flow can optionally propose the subdomain their tenant will be provisioned under — e.g., acme-corpacme-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:

  1. 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.
  2. Admin KYC review window — applications sit pending for hours. Holding a subdomain "reserved" for a pending application for days isn't desirable.
  3. 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:

  1. Creates the tenant with a random 8-char ID (always unique).
  2. Inserts the first Domain row for that random ID (primary, never changes).
  3. For the human-readable alias:
    • If application.preferred_domain is 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.
  4. 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

Run locally:

docker compose -p autocom-dev exec app \
  php artisan test --filter="PreferredDomain"