Maintenance Mode

This guide shows you how to freeze an organization, or one tenant of it, so that reads keep working while changes are refused. Use it when you need the organization's configuration to stand still — for example before exporting an organization or tenant to another installation.

Roles that hold the all permission — including the default <organization>.admin role — are not subject to maintenance mode, because all includes the bypass permission. To freeze them too, deny the bypass to their role. See Who is subject to it.

Maintenance mode is behind the maintenance feature, which is off by default. With the feature off, the API is not registered and the per-request check returns immediately.

Prerequisites

Before you begin, ensure you have:

  • The maintenance feature listed in _features.conf, then a restart of the Enterprise API.
  • A Bearer token for an account whose role holds the enable or disable permission on the organization or tenant you will call.
_features.conf
enabledFeatures = [
    maintenance
]

What maintenance mode does

Called on an organization (/v1/waltid/...), a window covers the whole organization. Called on a tenant (/v1/waltid.tenant1/...), it covers that tenant and everything below it; the rest of the organization keeps working. An organization-wide window and tenant windows are independent. Where they overlap, the later end applies. Other organizations are never affected.

Every window has an end (enabledUntil). After that time, what it covers is writable again without anyone having to act.

RequestDuring maintenance
GET, and POSTs that only read (below)Allowed
POST, PUT, DELETERefused with 503 Service Unavailable and a Retry-After header
Ending or re-enabling maintenanceAlways allowed for anyone holding the permission
Anything, for a role with the bypass permissionAllowed

A refusal names what is in maintenance, when it ends, and the permission that bypasses it.

What counts as a change

Two layers enforce the window:

  1. Requests. POST, PUT and DELETE to what is covered are refused with 503 before the handler runs. POSTs that only read are let through: credential store queries, query-by-metadata, the trust registry resolve/* endpoints, external role resolution, event queries (/events/query), and the wallet's resolve, match, preview and view steps.
  2. Storage. Every write to a covered tree entry is refused, whichever route or job it comes from. This catches protocol endpoints that change state, routes outside the service API, background jobs, and requests aimed at one place that write into another — for example deleting a tenant that contains a frozen one. A recursive delete checks every entry before deleting any, so it is refused as a whole rather than half done.

So an export taken during maintenance is a consistent snapshot of the organization tree under the window.

Not covered:

  • Events and the eIDAS audit chain, which are appended rather than changed.
  • Accounts, logins and role assignments, host aliases, and the organization record itself (profile, configuration).
  • Session state of the OpenID4VCI and OpenID4VP protocols, and objects outside the database, such as status lists in object storage.
  • Expiry of short-lived records by the database itself (TTL indexes).

Wallet-facing endpoints

The endpoints that wallets call without logging in — OpenID4VCI par, token, credential and direct_post, the verifier's request and response, and the OIDC bridge's sessions/create and token — change state, so they are refused like any other write while the issuer or verifier they belong to is covered. Issuance and verification sessions in progress fail with 503 until the window ends. Plan the window accordingly, or limit it to a tenant that has no live issuance or verification.

Who is subject to it

Every account whose roles do not grant the bypass permission (organization-management-maintenance-override) on the path being written. The permission is checked for each path written, so a bypass on one tenant does not open another. A superadmin's own request is never refused; background jobs that run as the system user are refused like any other writer.

The all permission includes the bypass. The default <organization>.admin role holds all on the organization, so organization admins — and every role copied from it — keep writing during maintenance. To subject a role to the window, deny the bypass to it with a REMOVE rule. A deny rule wins over all:

curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{roleId}/roles-api/permissions/permissions/add-to-role' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "target": "{orgID}",
  "action": "organization-management-maintenance-override",
  "operation": "REMOVE"
}'

Keep a separate role that holds the bypass, the enable and the disable permissions for whoever runs the maintenance.


Enable maintenance

Requires organization-management-maintenance-enable on the organization or tenant in the path.

Give at most one of enabledFor or enabledUntil. With neither, maintenance lasts 1 day. It may last at most 30 days. Giving both, an end in the past, or a longer window is rejected with 400.

CURL

Endpoint: POST /v1/{target}/organization-management-api/maintenance/enable | API Reference

Example Request
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/organization-management-api/maintenance/enable' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "enabledFor": "PT2H"
}'

Path Parameters

  • orgID: String (required) - Organization host. If your organization is waltid, the sandbox host is waltid.enterprise-sandbox.waltid.dev.
  • target: resourceIdentifier (required) - The organization (waltid) or tenant (waltid.tenant1) to freeze.

Header Parameters

  • Authorization: String (required) - Bearer token for Enterprise Stack authentication. Format: Bearer {token}.

Body Parameters

  • enabledFor: ISO-8601 duration (optional) - How long maintenance lasts, for example PT2H. Do not send this together with enabledUntil.
  • enabledUntil: ISO-8601 timestamp (optional) - When maintenance ends. Do not send this together with enabledFor.

Example Response
{
  "enabled": true,
  "enabledUntil": "2026-09-29T14:00:00Z"
}

Response Fields:

  • enabled: Boolean - Whether the window is on.
  • enabledUntil: Instant - When the window ends.

End maintenance

Requires organization-management-maintenance-disable on the organization or tenant in the path. Always allowed during maintenance, so the mode can be ended without the bypass permission.

Ending a window only ends what the path was called on. An organization-wide window and a tenant window are separate.

CURL

Endpoint: POST /v1/{target}/organization-management-api/maintenance/disable | API Reference

Example Request
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/organization-management-api/maintenance/disable' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{}'

Path Parameters

  • orgID: String (required) - Organization host.
  • target: resourceIdentifier (required) - The organization or tenant whose window to end.

Header Parameters

  • Authorization: String (required) - Bearer token for Enterprise Stack authentication. Format: Bearer {token}.

Example Response
{
  "enabled": false
}

Response Fields:

  • enabled: Boolean - false after the window is ended.

Permissions

PermissionGrants
organization-management-maintenance-enableEnabling maintenance
organization-management-maintenance-disableEnding maintenance
organization-management-maintenance-overrideWriting during maintenance

Assign these through Access & Permissions.

Behaviour across several pods

The organization's windows are cached for 5 seconds per pod, because the check runs on every change.

  • With the feature off, the check returns at once.
  • For an organization without an active window — including one whose windows have ended or expired — a check is one in-memory map read.
  • When the cache entry expires, one read of the organization refreshes it, however many requests arrive at that moment.

Enabling or ending maintenance takes effect at once on the pod that handled the request, and on the other pods within 5 seconds. When you freeze something to export it, wait those 5 seconds after enabling and before exporting. Until then, another pod may still accept a write that the export should not miss or include.

Logging

  • Enabling and ending maintenance are logged at INFO, with the target, the end time, and the account that made the change.
  • Refused requests are logged at WARN, at most once per minute per organization.

Next Steps

  • Organizations — Organization structure the window can cover.
  • Tenants — Tenant-scoped windows.
  • Features — Other optional Enterprise features.
Last updated on October 14, 2026