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 GET endpoint 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 + pathWhat it doesPermission
POST /tenants/:id/read-only/enableFreeze the tenantTENANTS_MANAGE
POST /tenants/:id/read-only/disableThaw the tenantTENANTS_MANAGE
GET /tenants/:idResponse includes readOnly: true/falseTENANTS_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

  1. Customer is migrating their ERP system over a long weekend. They want zero config changes in M365 during the window.
  2. Friday 5pm: open the tenant, Enable read-only, reason “ERP migration change freeze”.
  3. 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.
  4. Tuesday 9am: migration complete. Disable read-only, reason “ERP migration complete”.
  5. 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.