Skip to content

API Reference

API Reference

All admin endpoints are mounted under /admin/system/ and require a valid admin session JWT (role === 'admin', tier Admin). Each endpoint additionally requires a specific permission resolved from the caller’s admin role in ADMIN_DB.

The full surface is published in the live OpenAPI spec at GET /api/openapi.json (and the Scalar UI at /docs) under the Admin System tag, and cataloged in the machine-readable manifest docs/admin/admin-manifest.json (regenerate with deno task admin:manifest). Both are derived from a single source (worker/routes/admin-system.openapi.ts), and CI fails if a mounted admin route is missing from either — so the panel, API, and docs stay in lockstep (epic · G).

Base URL: https://your-worker.workers.dev

Authentication: All requests require Authorization: Bearer <token>.

API keys: admin operations are also reachable with a blq_ API key, but access is opt-in — the key must carry the admin scope (a key owned by an admin without that scope is rejected with 403 "API key missing required scope: admin"). Keys are a permissioned, audited alternative to a session, not a bypass; destructive operations still additionally require Cloudflare Access.


Roles

List Roles

GET /admin/system/roles

Permission: admin:read

Response 200:

{
"success": true,
"roles": [
{
"id": 1,
"role_name": "viewer",
"display_name": "Viewer",
"description": "Read-only access to admin dashboards and logs",
"permissions": "[\"admin:read\",\"audit:read\",\"metrics:read\",\"config:read\",\"users:read\",\"flags:read\"]",
"is_active": 1,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
]
}

Create Role

POST /admin/system/roles

Permission: roles:write

Request Body:

FieldTypeRequiredDescription
role_namestringUnique role identifier (e.g. flag-manager)
display_namestringHuman-readable name
descriptionstringRole description
permissionsstring[]Array of permission strings (min 1)
Terminal window
curl -X POST /admin/system/roles \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"role_name": "flag-manager",
"display_name": "Flag Manager",
"description": "Manages feature flags only",
"permissions": ["admin:read", "flags:read", "flags:write"]
}'

Response 201:

{
"success": true,
"role": { "id": 4, "role_name": "flag-manager", "..." : "..." }
}

Update Role

PATCH /admin/system/roles/:id

Permission: roles:write

Path Parameters: id — Role ID (integer)

Request Body (all fields optional):

FieldTypeDescription
display_namestringUpdated display name
descriptionstringUpdated description
permissionsstring[]Replace permission array (min 1)
is_activebooleanEnable/disable the role

Response 200:

{
"success": true,
"role": { "id": 4, "role_name": "flag-manager", "..." : "..." }
}

List Role Assignments

GET /admin/system/roles/assignments

Permission: admin:read

Query Parameters (all optional):

ParamTypeDescription
clerk_user_idstringFilter by user
role_namestringFilter by role
Terminal window
curl "/admin/system/roles/assignments?role_name=editor" \
-H "Authorization: Bearer $JWT"

Response 200:

{
"success": true,
"assignments": [
{
"id": 1,
"clerk_user_id": "user_2abc123",
"role_name": "editor",
"assigned_by": "user_2xyz789",
"assigned_at": "2025-01-15T10:30:00",
"expires_at": null
}
]
}

Assign Role

POST /admin/system/roles/assign

Permission: roles:assign

Request Body:

FieldTypeRequiredDescription
clerk_user_idstringClerk user ID to assign the role to
role_namestringRole to assign (viewer, editor, super-admin, or custom)
expires_atstringISO 8601 expiry date (null = never expires)
Terminal window
curl -X POST /admin/system/roles/assign \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"clerk_user_id": "user_2abc123",
"role_name": "editor",
"expires_at": "2025-12-31T23:59:59Z"
}'

Response 200:

{
"success": true,
"assignment": {
"clerk_user_id": "user_2abc123",
"role_name": "editor",
"assigned_by": "user_2xyz789",
"expires_at": "2025-12-31T23:59:59Z"
}
}

Uses INSERT ... ON CONFLICT ... DO UPDATE — re-assigning updates the existing assignment.


Revoke Role

DELETE /admin/system/roles/revoke

Permission: roles:assign

Request Body:

FieldTypeRequiredDescription
clerk_user_idstringUser to revoke from
role_namestringRole to revoke
Terminal window
curl -X DELETE /admin/system/roles/revoke \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"clerk_user_id": "user_2abc123",
"role_name": "editor"
}'

Response 200:

{ "success": true, "message": "Role revoked" }

KV cache for the user is invalidated automatically.


My Context

Get My Context

GET /admin/system/my-context

Permission: None required (returns the caller’s own context)

Returns the full dashboard context for the authenticated admin, including role, permissions, tiers, scopes, flags, and active announcements.

Terminal window
curl /admin/system/my-context -H "Authorization: Bearer $JWT"

Response 200:

{
"success": true,
"context": {
"clerk_user_id": "user_2abc123",
"role_name": "super-admin",
"permissions": ["admin:read", "admin:write", "..."],
"expires_at": null
}
}

Get My Permissions

GET /admin/system/my-permissions

Permission: None required

Returns only the caller’s resolved permissions array.


Tiers

List Tiers

GET /admin/system/tiers

Permission: admin:read

Response 200:

{
"success": true,
"tiers": [
{
"id": 1,
"tier_name": "anonymous",
"order_rank": 0,
"rate_limit": 10,
"display_name": "Anonymous",
"description": "Unauthenticated user — basic access",
"features": "{\"maxSources\": 3, \"maxBatchSize\": 1}",
"is_active": 1,
"created_at": "2025-01-01T00:00:00",
"updated_at": "2025-01-01T00:00:00"
}
]
}

Update Tier

PUT /admin/system/tiers/:name

Permission: config:write

Path Parameters: name — Tier name (e.g. free, pro)

Request Body (all fields optional):

FieldTypeDescription
rate_limitnumberRequests per minute (0 = unlimited)
display_namestringHuman-readable name
descriptionstringTier description
featuresobjectJSON feature flags/capabilities
is_activebooleanEnable/disable the tier
Terminal window
curl -X PUT /admin/system/tiers/pro \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"rate_limit": 500,
"features": {"maxSources": 100, "maxBatchSize": 50, "priorityQueue": true}
}'

Response 200:

{ "success": true, "tier": { "tier_name": "pro", "..." : "..." } }

Delete Tier

DELETE /admin/system/tiers/:name

Permission: config:write

Path Parameters: name — Tier name

Response 200:

{ "success": true, "message": "Tier deleted" }

Scopes

List Scopes

GET /admin/system/scopes

Permission: admin:read

Response 200:

{
"success": true,
"scopes": [
{
"id": 1,
"scope_name": "compile",
"display_name": "Compile",
"description": "Compile and download filter lists",
"required_tier": "free",
"is_active": 1
}
]
}

Update Scope

PUT /admin/system/scopes/:name

Permission: config:write

Path Parameters: name — Scope name (e.g. compile, rules)

Request Body (all fields optional):

FieldTypeDescription
display_namestringHuman-readable name
descriptionstringScope description
required_tierstringMinimum tier required (anonymous, free, pro, admin)
is_activebooleanEnable/disable the scope
Terminal window
curl -X PUT /admin/system/scopes/compile \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{ "required_tier": "pro" }'

Delete Scope

DELETE /admin/system/scopes/:name

Permission: config:write


Endpoint Auth Overrides

Per-endpoint authentication requirements that override the global defaults.

List Endpoint Overrides

GET /admin/system/endpoints

Permission: admin:read

Response 200:

{
"success": true,
"endpoints": [
{
"id": 1,
"path_pattern": "/api/rules/*",
"method": "*",
"required_tier": "pro",
"required_scopes": "[\"rules\"]",
"is_public": 0,
"is_active": 1
}
]
}

Create Endpoint Override

POST /admin/system/endpoints

Permission: config:write

Request Body:

FieldTypeRequiredDescription
path_patternstringURL pattern (e.g. /compile, /api/rules/*)
methodstringHTTP method or * for all (default: *)
required_tierstringTier override (null = use default)
required_scopesstring[]Scope overrides (null = use default)
is_publicbooleantrue = no auth required
Terminal window
curl -X POST /admin/system/endpoints \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"path_pattern": "/api/public/health",
"method": "GET",
"is_public": true
}'

Update Endpoint Override

PUT /admin/system/endpoints/:id

Permission: config:write

Path Parameters: id — Override ID (integer)

Request Body (all fields optional):

FieldTypeDescription
required_tierstringUpdated tier requirement
required_scopesstring[]Updated scope requirements
is_publicbooleanToggle public access
is_activebooleanEnable/disable the override

Delete Endpoint Override

DELETE /admin/system/endpoints/:id

Permission: config:write


Configuration Import / Export

The entire runtime-editable configuration — tiers, scopes, endpoint auth overrides, announcements, admin roles, user groups, and API groups — can be exported as a single bundle, edited offline, and re-imported. ADMIN_DB is the source of truth; a bundle is a point-in-time snapshot that round-trips (export → edit → import) to reproduce configuration. Bundles serialize to JSON, YAML, or TOML (select with ?format=; TOML omits null-valued fields since it has no null literal).

Export Config

GET /admin/system/config/export?format=json

Permission: config:read

Streams the bundle as a file download (Content-Disposition: attachment). format is one of json (default), yaml, toml.

Terminal window
curl "/admin/system/config/export?format=yaml" \
-H "Authorization: Bearer $JWT" -o bloqr-admin-config.yaml

Import Config (dry-run + apply)

POST /admin/system/config/import?format=json[&dryRun=false]

Permission: config:write

The request body is the serialized bundle (content matching format). By default this is a dry run returning a per-section diff (added / removed / changed / unchanged) against the live config — nothing is written:

{ "success": true, "dryRun": true, "diff": { "tiers": { "added": ["pro"], "removed": [], "changed": ["free"], "unchanged": 0 }, "...": {} } }

Add ?dryRun=false to apply. Before writing, the current config is captured as a snapshot (so the change can be reverted), then every section is upserted by its natural key (create when absent, update when present). Volatile columns (id, created_at, updated_at) are ignored. A malformed bundle is rejected with 422 and no writes occur.

Defense-in-depth — applying a bundle (and reverting, below) rewrites the whole config, so when Cloudflare Access is configured these ops additionally require a valid CF-Access-JWT-Assertion on top of config:write (403 otherwise). Dry-run previews are exempt. In deployments without CF Access this check no-ops.

{ "success": true, "dryRun": false, "snapshotId": 42, "applied": { "tiers": { "created": ["pro"], "updated": ["free"], "failed": [] }, "...": {} } }
Terminal window
curl -X POST "/admin/system/config/import?format=json&dryRun=false" \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
--data-binary @bloqr-admin-config.json

The natural keys are: tier_name (tiers), scope_name (scopes), path_pattern + method (endpoints), id (announcements), role_name (roles), group_name (groups and api-groups).

List Snapshots

GET /admin/system/config/snapshots

Permission: config:read

Returns snapshot metadata (id, created_at, created_by, reason), newest first. Bundle bodies are omitted.

Get Snapshot

GET /admin/system/config/snapshots/:id

Permission: config:read

Returns a single snapshot with its full bundle. 404 if not found.

Revert to Snapshot

POST /admin/system/config/snapshots/:id/revert

Permission: config:write

Applies the snapshot’s bundle. The current config is itself snapshotted first (reason: pre-revert:<id>) so the revert can be undone. Returns the apply report plus preRevertSnapshotId.

Terminal window
curl -X POST "/admin/system/config/snapshots/42/revert" \
-H "Authorization: Bearer $JWT"

Data-Subject Export (GDPR)

Export a User’s Admin Data

GET /admin/system/users/:userId/data-export

Permission: users:read

Aggregates everything the admin subsystem (ADMIN_DB) holds about a single user — role assignments, group memberships, and the audit trail of actions they performed — into one bundle, plus a data_locations manifest (store + table + row count) answering “where does this user’s data reside?”. Read-only; the export itself is audit-logged (user.data_export). Audit rows are paged in and capped, with audit_log_truncated set when older entries are omitted.

This covers the admin-domain slice only; the user’s primary profile, subscription, and API-usage records live in the application database and are exported by that subsystem (see the notes field in the response).

{
"success": true,
"export": {
"user_id": "user_123",
"exported_at": "2026-07-13T…",
"data_locations": [
{ "store": "ADMIN_DB", "table": "admin_role_assignments", "record_count": 1, "description": "" },
{ "store": "ADMIN_DB", "table": "admin_group_members", "record_count": 2, "description": "" },
{ "store": "ADMIN_DB", "table": "admin_audit_logs", "record_count": 37, "description": "" }
],
"role_assignments": [ ],
"group_memberships": [ ],
"audit_log_entries": [ ],
"audit_log_truncated": false,
"notes": [ ]
}
}
Terminal window
curl "/admin/system/users/user_123/data-export" \
-H "Authorization: Bearer $JWT" -o user_123-admin-data.json

Erase a User’s Admin Data (delete-or-explain)

POST /admin/system/users/:userId/data-erase

Permission: users:manageplus Cloudflare Access (destructive defense-in-depth) when CF Access is configured.

Removes the erasable admin-domain data for the user — role assignments and group memberships — and explains what is deliberately retained. The audit log (admin_audit_logs) is append-only and kept for security / accountability / legal compliance, so it is reported under retained, not deleted. The erasure is itself audit-logged (user.data_erase) and the user’s role-cache entry is invalidated.

{
"success": true,
"erasure": {
"user_id": "user_123",
"erased_at": "2026-07-13T…",
"deleted": { "role_assignments": 1, "group_memberships": 2 },
"retained": [
{ "store": "ADMIN_DB", "table": "admin_audit_logs", "record_count": 37, "reason": "Audit log is append-only and retained …" }
],
"notes": [ ]
}
}

The user’s primary profile, subscription, and usage records live in the application database and are erased by that subsystem.


Feature Flags

Feature flags are managed via Cloudflare Flagship. These routes are a thin proxy over the Flagship management API (worker/handlers/admin-flagship.ts) and return 503 if CF_ACCOUNT_ID, FLAGSHIP_APP_ID, or FLAGSHIP_API_TOKEN aren’t configured. Flag evaluation inside the Worker does not use these routes — see the [[flagship]] binding.

List Flags

GET /admin/config/feature-flags

Permission: admin:read

Response 200:

{
"success": true,
"total": 1,
"items": [
{
"key": "new-parser-v2",
"enabled": true,
"default_variation": "off",
"variations": { "off": false, "on": true },
"rules": [
{
"conditions": [{ "attribute": "plan", "operator": "in", "value": ["pro", "admin"] }],
"priority": 0,
"serve_variation": "on",
"rollout": { "percentage": 25 }
}
],
"description": "New AGTree v2 parser",
"type": "boolean",
"updated_at": "2026-06-24T10:30:00Z",
"updated_by": "admin@bloqr.dev"
}
]
}

Create Flag

POST /admin/config/feature-flags

Permission: flags:write

Request Body:

FieldTypeRequiredDescription
keystringUnique flag identifier (letters, numbers, hyphens, underscores)
enabledbooleanDefault: false
default_variationstringVariation served when no rule matches or the flag is disabled
variationsobjectMap of variation name → value; all values must share the same type
rulesarrayTargeting rules, evaluated in ascending priority. Default: []
descriptionstringHuman-readable description
typestringboolean | string | number | json; inferred from variations if omitted
Terminal window
curl -X POST https://api.bloqr.dev/admin/config/feature-flags \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"key": "streaming-api-beta",
"enabled": true,
"default_variation": "off",
"variations": { "off": false, "on": true },
"rules": [{ "conditions": [], "priority": 0, "serve_variation": "on", "rollout": { "percentage": 10 } }],
"description": "Server-sent events streaming API"
}'

Update Flag

PATCH /admin/config/feature-flags/:key

Permission: flags:write

Path Parameters: key — Flag key (string)

Request Body: Same fields as create (minus key), all optional — only the provided fields change. Reads the current definition first and merges your payload on top of it, since Flagship’s own update endpoint replaces the entire flag; this means a plain {"enabled": false} toggle never wipes out existing rules or variations.


Delete Flag

DELETE /admin/config/feature-flags/:key

Permission: flags:write

Permanently deletes the flag. Cannot be undone.


Audit Logs

Query Audit Logs

GET /admin/system/audit

Permission: audit:read

Query Parameters (all optional):

ParamTypeDefaultDescription
actor_idstringFilter by Clerk user ID
actionstringFilter by action (e.g. tier.update)
resource_typestringFilter by resource type (e.g. feature_flag)
resource_idstringFilter by specific resource ID
statusstringsuccess, failure, or denied
sincestringISO 8601 start date
untilstringISO 8601 end date
limitnumber50Results per page (max 100)
offsetnumber0Pagination offset
Terminal window
curl "/admin/system/audit?action=tier.update&since=2025-01-01T00:00:00Z&limit=20" \
-H "Authorization: Bearer $JWT"

Response 200:

{
"success": true,
"logs": [
{
"id": 42,
"actor_id": "user_2abc123",
"actor_email": "admin@example.com",
"action": "tier.update",
"resource_type": "tier_config",
"resource_id": "pro",
"old_values": "{\"rate_limit\": 300}",
"new_values": "{\"rate_limit\": 500}",
"ip_address": "203.0.113.1",
"user_agent": "Mozilla/5.0...",
"status": "success",
"metadata": null,
"created_at": "2025-01-15T10:30:00"
}
],
"total": 142,
"limit": 20,
"offset": 0
}

Announcements

List Announcements

GET /admin/system/announcements

Permission: admin:read

Response 200:

{
"success": true,
"announcements": [
{
"id": 1,
"title": "Scheduled Maintenance",
"body": "The platform will be briefly unavailable on Jan 20 from 02:00-04:00 UTC.",
"severity": "warning",
"active_from": "2025-01-18T00:00:00",
"active_until": "2025-01-20T04:00:00",
"is_active": 1,
"created_by": "user_2abc123",
"created_at": "2025-01-15T10:30:00",
"updated_at": "2025-01-15T10:30:00"
}
]
}

Create Announcement

POST /admin/system/announcements

Permission: config:write

Request Body:

FieldTypeRequiredDescription
titlestringAnnouncement title
bodystringAnnouncement body (supports markdown)
severitystringinfo, warning, error, or success (default: info)
active_fromstringISO 8601 start time (null = immediately)
active_untilstringISO 8601 end time (null = no expiry)
Terminal window
curl -X POST /admin/system/announcements \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"title": "New Parser Available",
"body": "AGTree v2 is now available for all Pro users.",
"severity": "success",
"active_until": "2025-02-01T00:00:00Z"
}'

Update Announcement

PATCH /admin/system/announcements/:id

Permission: config:write

Path Parameters: id — Announcement ID (integer)

Request Body: Same fields as create, all optional. Additionally:

FieldTypeDescription
is_activebooleanEnable/disable the announcement

Delete Announcement

DELETE /admin/system/announcements/:id

Permission: config:write


Local Auth Users

CRUD endpoints for managing local JWT auth users. These endpoints are available when the Worker is configured with a D1 DB binding and JWT_SECRET. They mirror Clerk’s Backend API for easy migration.

Authentication required: Admin tier + admin role (enforced by checkRoutePermission).

Note: Password hashes are never returned. The api_disabled flag can be toggled to block a user’s API access without deleting the account.


List Local Auth Users

GET /admin/local-users

Query Parameters:

ParameterTypeDefaultDescription
limitinteger50Max results per page (1–100)
offsetinteger0Pagination offset

Response:

{
"success": true,
"users": [
{
"id": "uuid",
"identifier": "user@example.com",
"identifier_type": "email",
"role": "user",
"tier": "free",
"api_disabled": 0,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}

Get Local Auth User

GET /admin/local-users/:id

Path Parameters: id — User UUID

Response: { "success": true, "user": { ... } } — same user object shape as the list items above.


Create Local Auth User

POST /admin/local-users

Request Body:

FieldTypeRequiredDescription
identifierstringEmail or phone number
passwordstringPlaintext password (hashed server-side with PBKDF2)
rolestringuser or admin (default: user)
tierstringOverride tier; defaults to the role’s default tier

Response: 201 with the created user object.

Errors:

  • 409 Conflict — identifier already registered

Update Local Auth User

PATCH /admin/local-users/:id

Request Body (at least one field required):

FieldTypeDescription
rolestringNew role (user or admin)
tierstringNew tier (free, pro, admin)
api_disabled0 | 11 blocks all API access for this user

Tier and role are independent. Setting role without tier auto-derives the default tier for that role. Setting both allows overrides (e.g. { "role": "user", "tier": "pro" } for a Pro-rate-limited non-admin user).

Response: Updated user object.


Delete Local Auth User

DELETE /admin/local-users/:id

Response: { "success": true, "message": "User deleted" }

Errors:

  • 404 Not Found — user does not exist

API Usage

Query per-user API usage statistics stored in KV. Useful for monitoring, abuse detection, and billing.

Authentication required: Admin tier + admin role.


Get User Usage

GET /admin/usage/:userId

Path Parameters: userId — User UUID (local auth user ID)

Query Parameters:

ParameterTypeDefaultDescription
daysinteger30Lookback window in days (1–90)

Response:

{
"success": true,
"userId": "uuid",
"total": {
"count": 1234,
"firstSeen": "2025-01-01T00:00:00Z",
"lastSeen": "2025-03-15T12:00:00Z"
},
"days": [
{ "date": "2025-03-15", "count": 42, "routes": { "/compile": 38, "/validate": 4 } }
],
"lookbackDays": 30
}

Endpoint Summary

#MethodPathPermission
1GET/admin/system/rolesadmin:read
2POST/admin/system/rolesroles:write
3PATCH/admin/system/roles/:idroles:write
4GET/admin/system/roles/assignmentsadmin:read
5POST/admin/system/roles/assignroles:assign
6DELETE/admin/system/roles/revokeroles:assign
7GET/admin/system/my-context(none)
8GET/admin/system/my-permissions(none)
9GET/admin/system/tiersadmin:read
10PUT/admin/system/tiers/:nameconfig:write
11DELETE/admin/system/tiers/:nameconfig:write
12GET/admin/system/scopesadmin:read
13PUT/admin/system/scopes/:nameconfig:write
14DELETE/admin/system/scopes/:nameconfig:write
15GET/admin/system/endpointsadmin:read
16POST/admin/system/endpointsconfig:write
17PUT/admin/system/endpoints/:idconfig:write
18DELETE/admin/system/endpoints/:idconfig:write
19GET/admin/config/feature-flagsadmin:read
20POST/admin/config/feature-flagsflags:write
21PATCH/admin/config/feature-flags/:keyflags:write
22DELETE/admin/config/feature-flags/:keyflags:write
23GET/admin/system/auditaudit:read
24GET/admin/system/announcementsadmin:read
25POST/admin/system/announcementsconfig:write
26PATCH/admin/system/announcements/:idconfig:write
27DELETE/admin/system/announcements/:idconfig:write
28GET/admin/local-usersAdmin role
29GET/admin/local-users/:idAdmin role
30POST/admin/local-usersAdmin role
31PATCH/admin/local-users/:idAdmin role
32DELETE/admin/local-users/:idAdmin role
33GET/admin/usage/:userIdAdmin role
34GET/admin/system/config/exportconfig:read
35POST/admin/system/config/importconfig:write
36GET/admin/system/config/snapshotsconfig:read
37GET/admin/system/config/snapshots/:idconfig:read
38POST/admin/system/config/snapshots/:id/revertconfig:write
39GET/admin/system/users/:userId/data-exportusers:read
40POST/admin/system/users/:userId/data-eraseusers:manage + CF Access

⁺ Live and wired into worker/routes/admin.routes.ts today. The /admin/system/* routes are mounted via worker/routes/admin-system.routes.ts, which adapts the worker/handlers/admin-handlers.ts handlers (roles, tiers, scopes, endpoint overrides, audit, announcements) and the native config import/export handlers onto the Hono router behind the coarse /admin/* entry gate.

Note — user groups (/admin/system/groups/*) and API groups (/admin/system/api-groups/*) are also mounted (epic · B) but not yet enumerated in this table; their OpenAPI documentation lands with epic · F.