Operator API
Enabled by setting security.operator_token. All operator routes require
Authorization: Bearer <token>.
Operator routes are rate limited (60 requests/minute per client key).
List sites
QUERY /api/v1/operator/sites
Optional JSON body (an empty body means default pagination):
Returns every database-tracked site merged with the operator-declared
[sites] overlay. Each origin carries a source of "verified" or
"config". Pagination and filtering use page, per_page (1-100, default
20) and optional site_id from the body; the response shape is
{ "data": [...], "meta": { "total", "page", "per_page", "total_pages" } }.
Revoke a verified origin
POST /api/v1/operator/sites/{site_id}/origin-revocations
Body: { "origin": "https://blog.example.com" }. Origins declared in the
config file cannot be revoked here — edit the config instead.
Rotate / revoke the HMAC secret
POST /api/v1/operator/sites/{site_id}/secret-rotations — returns the new secret
exactly once.
DELETE /api/v1/operator/sites/{site_id}/secret — removes the secret and falls
back to origin auth.
Both refuse to touch sites whose secret is declared in the config file.
Export an adoption snippet
GET /api/v1/operator/sites/{site_id}/config-snippet
Returns { "site_id": "...", "toml": "..." }; toml is the block to paste
into [sites] when the operator wants to move a database-tracked site into
declarative config.
Rotate the claim token
POST /api/v1/operator/sites/{site_id}/claim-token-rotations
Returns a new claim_token exactly once and invalidates the previous token.
Use this when a claim token may have leaked.
Start a site retirement
POST /api/v1/operator/sites/{site_id}/retirement
Operator mirror of the claim-token retire endpoint: marks the site
retiring immediately (writes get 410 code=site-retired) and lets the
background pass retirement the Matrix Space/rooms and clear local
projections. Returns 202 Accepted with the retirement state. See
Sites for the full flow. Sites declared in [sites]
cannot be retired this way. GET /api/v1/operator/sites/{site_id}/retirement
returns the current state while its local row still exists.
List quarantined rooms
QUERY /api/v1/operator/quarantined-rooms
Optional JSON body with the same page / per_page / site_id fields as
List sites.
Returns rooms whose adoption failed governance checks and are currently
quarantined, with the room id, site/page, quarantine reason, when the room
was first quarantined, how many consecutive adoption attempts failed, and
when the next automatic retry is scheduled (null means manual attention is
required). Quarantined rooms are retried on a 1h/6h/24h schedule; after the
fourth consecutive failure they require reinstate. A successful
re-registration clears the quarantine automatically. The same
pagination/filter fields and { "data", "meta" } shape apply.
Reinstate a room
DELETE /api/v1/operator/quarantined-rooms/{room_id}
Clears a room's quarantine and makes it the canonical room again (any other
active room for the same page is superseded). The operation is idempotent:
reinstating an already-active room also returns 204; an unknown room
returns 404.
Upgrade a comment room
POST /api/v1/operator/rooms/{room_id}/upgrades
Body: {"new_version": "12"}. Upgrades a registered active comment room via
the homeserver's native /upgrade and converges the replacement: metadata is
repaired, the room is re-linked into its site Space (the old child's via is
cleared best-effort), site roles are re-invited, and the new room becomes the
registry's active room (the old one is superseded and cleaned up). The
operation is idempotent: an existing m.room.tombstone is reused. Spaces,
unknown rooms, non-active rooms, invalid versions and versions that are not
newer than the room's current version are rejected with a 4xx problem
response. This endpoint is the operator mirror of the
site-level POST /api/v1/sites/{site_id}/pages/{page_slug}/upgrades
(claim token); both execute through the AS bot.
Inspect and recover upgrade intents
QUERY /api/v1/operator/room-upgrade-intents
Lists durable authorization for native upgrades, including the target version,
observed replacement, lifecycle state (requested, observed, adopted,
failed, manual) and error. The body supports page, per_page and an
optional status filter.
POST /api/v1/operator/room-upgrade-intents/{room_id}/recoveries
Body:
{"new_version": "13", "replacement_room": "!successor:server"}.
For a failed or manual intent, this confirms the exact reviewed successor and
completes convergence if Matrix still agrees: the old room must still carry a
tombstone naming that successor, and the successor create event must have the
expected version and predecessor. On success, the replacement becomes the
registry's active room, the old room is superseded, and the intent is marked
adopted. Mismatched input is rejected without moving local identity.
Recovery checklist
Before calling recover, verify all of the following:
- The durable intent names the same target version and replacement you intend to approve.
- You saw the native upgrade in Matrix (or in homeserver logs), not merely an unexplained room ID.
- The old room's
m.room.tombstonestill exists and itsreplacement_roomexactly matches the intent. - The replacement's
m.room.createhas the expected room version and itspredecessor.room_idpoints to the old room. - Governance state is safe to converge: the site Space still has trusted admin/manager entries, and no unrelated power-level change occurred during the failure.
Do not recover an intent whose origin is unclear, whose replacement was not
created by the Cumments-managed /upgrade call, or whose successor metadata
points to another site/page. Recovery never repairs a mismatched successor by
guessing; it fails closed and records why.
Inspect and retry projection repairs
QUERY /api/v1/operator/projection-repairs
Optional JSON body:
{ "page": 1, "per_page": 20, "status": "pending|manual|resolved" }.
Returns the durable repair queue for facts that failed closed during projection.
Rows contain only identifiers and diagnostics; the authoritative Matrix event is
fetched during repair. The standard { "data", "meta" } pagination shape applies.
GET /api/v1/operator/projection-repairs/{target_event_id}
Returns one repair row.
POST /api/v1/operator/projection-repairs/{target_event_id}/retry
Requeues a pending or manual repair immediately. Resolved repairs return
409; unknown repairs return 404. A successful requeue returns 202 Accepted
with the updated row.
Start a comment-room retirement
POST /api/v1/operator/rooms/{room_id}/retirement
Marks the registered active room Retired immediately (new writes stop),
then the background reconciler renames the Matrix room [retired], removes
its alias, leaves it as the AppService sender and every site virtual user,
and clears the local projections. This is the operator mirror of
POST /api/v1/sites/{site_id}/pages/{page_slug}/retirement (claim token);
both return 202 Accepted and use the same management use case. Unknown or
already-retired rooms return 404.
GET /api/v1/operator/rooms/{room_id}/retirement returns the current
retirement state for a room that is still in the registry.
Governance fallback
The operator can act on a site's behalf for site-level roles:
POST /api/v1/operator/sites/{site_id}/admin-claims/DELETE /api/v1/operator/sites/{site_id}/admins/{user_id}POST /api/v1/operator/sites/{site_id}/manager-claims/DELETE /api/v1/operator/sites/{site_id}/managers/{user_id}POST /api/v1/operator/sites/{site_id}/ownership-transfers— operator mirror of the claim-token ownership transfer.
These use the same handlers and response shapes as the claim-token Governance endpoints, including the pending-claim verification step — the operator registers the claim, but the target MXID must still DM the bot to activate it.