1.6 DNS and TLS
Outcome
All required DNS records and TLS certificates are in place so traffic flows correctly.
The platform supports six white-label brands (RCM_02 shipped); each brand has its
own tenant root domain and login UI, but they all share the same rcm-core backend.
| Host | Routes to | Notes |
|---|---|---|
admin.<env>.medsuiteone.ai | admin-app SWA (/*) + admin-service App Service (/api/*) | Platform console. One host, two origins — see below |
api.rcm.medsuite.com | rcm-core App Service | Tenant API. Platform admin no longer lives here |
edi.medsuite.com | edi-app SWA | EDI operator console |
edi-api.medsuite.com | edi-gateway App Service | X12 + SFTP gateway |
*.rcm.medsuite.com (and per-brand) | rcm-app SWA | Tenant subdomain per brand |
admin.docs.medsuiteone.ai | docs-admin SWA | This site |
tenant.docs.medsuiteone.ai | docs-tenant SWA | Tenant manual |
edi.docs.medsuiteone.ai | docs-edi SWA | EDI manual |
billing.docs.medsuiteone.ai | docs-billing SWA | Billing-services manual |
docs. labelEach manual is <site>.docs[.<env>].medsuiteone.ai — on the operator root,
not a brand root, and not a bare label on it. That keeps tenant, admin,
edi and billing usable as tenant slugs: a Static Web App custom domain at
the same depth as a tenant subdomain would silently shadow that slug for every
customer.
admin.rcm.medsuite.comPlatform administration used to be a /platform/* route prefix inside the
tenant app, served from an "admin variant" build at admin.rcm.medsuite.com.
It is now its own application pair and that host is gone:
admin.<env>.medsuiteone.ai—dev/stg, production dropping the label (admin.medsuiteone.ai). One custom domain, two Front Door origins:/api/*→admin-service,/platform/.well-known/*→admin-service(the platform JWKS document), and everything else/*→ theadmin-appSWA.- The API paths did not change. They are still
/platform/*; a browser reaches them as/api/platform/*on this same host. - Do not add a bare
/platform/*Front Door route here. Only/platform/.well-known/*is routed to the API; every other/platform/…URL is an SPA route and must fall through to the SWA.
Background: docs/architecture/adr/006-platform-admin-application.md and
docs/operations/runbook-platform.md §154 (standing the surface up) in the repo.
.rcm.medsuite.com hosts in this chapter are stale.medsuite.com has been retired as a customer domain — tenants live at
<slug>.<surface>.<brand-root> on the .ai brand roots listed below.
This pass repointed the platform console only; the api., edi. and
tenant rows in the table above — and the examples further down that use them —
still carry the retired names and are a separate correction.
Brand domains
Seven brand roots ship out of the box (RCM_02, plus susqsoft from #132). Each
brand has its own apex (and optional *.<brand> wildcard) routed to the same
SWA + Front Door origin:
| Brand key | Apex | Tenant pattern |
|---|---|---|
millin | millinmedical.ai | <slug>.<surface>.millinmedical.ai |
medsuite | medsuiteone.ai | operator surfaces only — no tenant slug |
emrbear | emrbear.ai | <slug>.<surface>.emrbear.ai |
accumedic | accumedic.ai | <slug>.<surface>.accumedic.ai |
brittco | brittco.ai | <slug>.<surface>.brittco.ai |
electrobills | electrobills.ai | <slug>.<surface>.electrobills.ai |
susqsoft | susqsoft.ai | <slug>.<surface>.susqsoft.ai |
susqsoft shares Brittco's palette and artwork for now — its own theme is
still to come — but it is a separate brand key, so a SusqSoft customer must
be provisioned with whitelabel_key = 'susqsoft', never brittco. Picking
brittco "because it looks the same" means every one of those tenants stops
being able to log in the day the real theme lands.
<surface> is rcm, emr or cm. The apexes above are the values stored in
each brand's domainRoots; brand resolution is a suffix match, so it works at
any depth (customer1.rcm.dev.millinmedical.ai → millin). medsuite.com is
retained for email only — no app host is bound to it any more.
The brand is resolved from the request host at first paint; on localhost a
whiteLabelKey localStorage override is honored. Login refuses to authenticate a
tenant whose configured brand does not match the host.
Prerequisites
- 1.5 First platform admin complete.
- Access to the DNS provider for every brand apex you intend to enable.
Topology
Steps
Provision a wildcard certificate
Two options:
- Azure-managed cert (recommended for SWA): each Static Web App can be issued a managed cert per custom domain — no manual cert handling. Wildcards are not supported here, so each tenant subdomain needs its own cert.
- Bring-your-own wildcard: a
*.rcm.medsuite.comcert from your CA, uploaded to Azure Front Door. Use this if you want one cert covering all tenant subdomains.
Most installations use Front Door + a wildcard cert because tenant subdomains are provisioned dynamically.
Add the canonical CNAME / A records
In your DNS provider:
Hostname Type Target api.rcm.medsuite.comCNAME Front Door endpoint (origin = rcm-core App Service) edi-api.medsuite.comCNAME Front Door endpoint (origin = edi-gateway App Service) edi.medsuite.comCNAME <edi-app-swa>.azurestaticapps.netadmin.<env>.medsuiteone.aiCNAME Front Door endpoint (origins = admin-app SWA + admin-service App Service) *.rcm.medsuite.comCNAME Front Door endpoint admin.docs.medsuiteone.aiCNAME <rcmdocs-platform>.azurestaticapps.nettenant.docs.medsuiteone.aiCNAME <rcmdocs-tenant>.azurestaticapps.netedi.docs.medsuiteone.aiCNAME <rcmdocs-edi>.azurestaticapps.netbilling.docs.medsuiteone.aiCNAME <rcmdocs-billing>.azurestaticapps.netBind custom domains to Static Web Apps
declare -A DOCS_HOSTNAMES=([rcmdocs-platform]=admin.docs.medsuiteone.ai[rcmdocs-tenant]=tenant.docs.medsuiteone.ai[rcmdocs-edi]=edi.docs.medsuiteone.ai[rcmdocs-billing]=billing.docs.medsuiteone.ai)for swa in "${!DOCS_HOSTNAMES[@]}"; doaz staticwebapp hostname set \--resource-group rg-rcm-prod-eastus2 \--name "$swa" \--hostname "${DOCS_HOSTNAMES[$swa]}"doneConfigure Front Door routing for
*.rcm.medsuite.comThe wildcard route inspects the
Hostheader and forwards to:api.rcm.*→ backend container app*.rcm.*(any other) → tenant SWA bundle (the sameapps/rcm-appbuild serves all tenants; the app readswindow.location.hostto pick the tenant slug).
az afd route create \--resource-group rg-rcm-prod-eastus2 \--profile-name fd-rcm-prod \--endpoint-name rcm-prod \--route-name tenant-wildcard \--custom-domains "*.rcm.medsuite.com" \--origin-group og-rcm-app \--supported-protocols Https \--link-to-default-domain Disabled \--https-redirect EnabledValidate certificates
# Platform console (operator root). Prod drops the env label.for h in admin.dev.medsuiteone.ai; doecho | openssl s_client -connect "$h:443" -servername "$h" 2>/dev/null \| openssl x509 -noout -subject -enddatedone# App / API hosts (medsuite.com)for h in api edi; doecho | openssl s_client -connect "$h.medsuite.com:443" -servername "$h.medsuite.com" 2>/dev/null \| openssl x509 -noout -subject -enddatedone# Docs hosts (docs.medsuiteone.ai)for h in admin tenant edi billing; doecho | openssl s_client -connect "$h.docs.medsuiteone.ai:443" -servername "$h.docs.medsuiteone.ai" 2>/dev/null \| openssl x509 -noout -subject -enddatedoneEach line should show a non-expired cert with the right
subjectCN/SAN.
Validation
| Check | Expected |
|---|---|
dig admin.<env>.medsuiteone.ai | Resolves to the Front Door endpoint |
dig api.rcm.medsuite.com | Resolves to Container App hostname |
dig +short '*.rcm.medsuite.com' | Resolves to Front Door |
curl -fsSL https://admin.<env>.medsuiteone.ai | Returns the console SPA login page |
curl -fsSL https://admin.docs.medsuiteone.ai | Returns this docs site |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Cert error in browser | Custom domain still validating | Wait up to 30 min after binding; check az staticwebapp hostname show. |
*.rcm resolves but returns 404 | Front Door route missing or origin not added | Re-check step 4. |
api.rcm returns 502 | Container app probe failing | Check az containerapp logs show for the backend. |