Read-only tenant mode
Read-only mode freezes a tenant at the Manage365 level — reads still work, but nothing in Manage365 can write to that tenant until you turn read-only off. It's a safety rail for situations where you want visibility without the risk of accidental change.
When to use it
- Change freeze — customer has a critical system going live; you don't want a scheduled standards push or drift remediation touching anything during the window
- Handover audit — inheriting a tenant from another MSP; you want to see what's there before you start making changes (and you want the customer to see you're not making silent tweaks)
- New-tenant observation — freshly onboarded tenant; you want a week of data before you push the full standards baseline
- Regulatory investigation — evidence preservation during an incident; you don't want a well-meaning tech running a remediation that destroys forensic state
What's blocked
Any HTTP verb other than GET against any endpoint that targets the read-only tenant:
POST,PATCH,PUT,DELETE
Blocked calls return 409 Conflict:
409 Conflict
{
"message": "Tenant is in read-only mode.
Disable read-only mode before making changes.",
"tenantId": "<uuid>"
}The guard runs before the controller, so nothing leaks through — no standards get applied, no users get disabled, no CA policies get touched.
What still works
- Reads — every
GETendpoint works normally. Compliance scans, drift detection (read-only half), user search, licence reports, Defender incidents — all normal. - Tenant lifecycle — suspend, reactivate, and hard-delete remain available at the MSP level. Read-only is a write-to-tenant guard, not a write-to-Manage365 guard.
- Toggling the flag itself — enabling and disabling read-only is a Manage365 write, not a tenant write, so it's always allowed.
Scheduler behaviour
Three schedulers respect read-only:
- Compliance scheduler — scan job skips read-only tenants with a logged reason. Historical scan data stays accessible.
- Standards (built-in) drift sweeper — reads drift but skips the re-apply step. Drift findings still log so you can see what changed; remediation waits.
- MSP Library scheduler — assignments skip pushes + migration-plan execution. Drift detection still runs (read-only), migration plans still get generated (also read-only), so you can review them for when you come off read-only.
The GDAP auto-renewal sweep ignores read-only — renewal invites are an MSP-level write, not a tenant-level write, and you don't want to lose Graph access just because you froze the tenant.
UI
- Banner — a yellow banner across the tenant page with a Disable read-only button
- Badge — a “Read-only” pill next to the tenant name in the tenant list
- Action buttons disabled — write actions on the tenant page (Apply standard, Remediate, Disable user, etc.) are greyed out with the banner as the reason
Endpoints
| Method + path | What it does | Permission |
|---|---|---|
POST /tenants/:id/read-only/enable | Freeze the tenant | TENANTS_MANAGE |
POST /tenants/:id/read-only/disable | Thaw the tenant | TENANTS_MANAGE |
GET /tenants/:id | Response includes readOnly: true/false | TENANTS_READ |
Every enable / disable is audited as a tenant.read_only.enable or tenant.read_only.disable event with the actor, reason (required — a short free-text string), and timestamp.
End-to-end example: change freeze
- Customer is migrating their ERP system over a long weekend. They want zero config changes in M365 during the window.
- Friday 5pm: open the tenant, Enable read-only, reason “ERP migration change freeze”.
- Over the weekend: scheduled drift sweeps run read-only. One drift is detected (a service account CA policy was modified during the migration) — it's logged but not re-applied.
- Tuesday 9am: migration complete. Disable read-only, reason “ERP migration complete”.
- Review the drift findings from the freeze window; remediate the genuine drifts; accept the intentional ones.
Common pitfalls
- Forgetting to take it off — set a calendar reminder. There's no auto-expiry yet, so a tenant can stay frozen forever if nobody thaws it. An alert fires at 7 days and 30 days to nudge you.
- Expecting reads to block too — they don't. Read-only is about the Manage365 → tenant direction. Read calls, telemetry, incident ingest all keep going.
- Assuming playbooks respect it — they do. Any playbook action that would write to the read-only tenant is blocked by the same guard. The playbook execution record shows the step as skipped with the 409 message.
- GDAP renewal stops working — it doesn't. Renewal is an MSP-level Partner Center call, not a tenant-level Graph call.
FAQ
Can I read-only a single module without freezing the whole tenant? Not yet. Read-only is per-tenant.
Does read-only affect the client portal? Yes — client-portal users can still view their dashboard but any action that would hit a write endpoint (password self-service against their own account, for example) is blocked the same way.
What if a tech tries to force a change? There's no override flag. The only way to make a change is to disable read-only, which is audited — so the “I just wanted to quickly…” shortcut is visible after the fact.
Can I combine read-only with change requests? Yes — change requests aimed at a read-only tenant are accepted but blocked from execution, with the tenant state shown on the request record.