Unified Security Control Plane (USCP) — Administrator Guide
Audience: the platform/security administrator who operates USCP after it is deployed. This guide assumes no prior knowledge of the product. Every task is a numbered, explicit procedure that tells you what it does, why, the exact console location, the exact API endpoint, the expected result, and how to undo it. A novice admin should be able to run the platform from this document alone.
This guide is not the install guide. Standing up the binary + PostgreSQL + licensing is covered by
codes/docs/site/docs/how-to/deploy.md. This document begins at "the service is running and answeringGET /healthz."Accuracy note. Every endpoint, role, entitlement key, request field, and flag in this guide is quoted from the source of truth:
codes/ci/entitlements.yaml(the 389-route boundary manifest),codes/web/admin-shell/src/catalog.ts(the console nav + sample bodies),codes/internal/app/authorize.go(roles), and the individual Go handlers undercodes/internal/app/andcodes/internal/identity/. Every value in this guide is pinned to source; where a behaviour is environment-supplied (e.g. an IdP's own MFA policy) the guide says so.
Table of contents
- Admin overview
- Getting started as admin
- Tenant administration
- Identity & access
- Licensing
- Governance & trust
- Security operations administration
- Estate management
- Observability & operations
- Resilience & business continuity
- Routine operations & runbooks
- Security hardening checklist
- Admin troubleshooting
- Complete admin operation reference
Conventions used in every procedure
- Console path is written
Sidebar group → Domain → action. The console is served from
the root URL (https://<your-host>/). Every capability is also reachable from the Command palette (press ⌘K / Ctrl-K) and the generic endpoint runner (see §2.4), so if a purpose-built screen does not exist for a capability, you still operate it from the UI.
- API call is written
METHOD /pathwith an example JSON body. All examples assume you have
a valid session cookie (uscp_session) or a service-account bearer token (Authorization: Bearer uscp_sa_…). Replace https://<host> with your deployment URL.
- Entitlement is the license key that gates the route (from
entitlements.yaml). If your
license does not include it, the route fails closed with 402/403 before the handler runs.
- Undo tells you the exact reverse operation. Where an action is irreversible, the guide
says so explicitly and in bold.
{id},{name},{subject}etc. are path parameters — substitute the real value.
1. Admin overview
1.1 What USCP is, from an operator's chair
USCP is a single self-contained Go binary that embeds a React admin console and talks to one PostgreSQL database. There is no separate web server, no separate app tier, and no message broker to babysit — the binary serves the API, the console SPA, the metrics endpoint, and the background workers (retention, webhook/notification delivery, forward-queue) in-process.
It is a multi-tenant control plane: every row in PostgreSQL is tagged with a tenant and isolated by Row-Level Security (RLS), so one tenant can never read another's data even if a query is wrong. Tenants are created and destroyed through the API (§3).
It exposes 90 capabilities (20 GA, 70 Beta — see codes/docs/capability-matrix.md) across 389 HTTP routes. Each capability is a licensed module; the license you loaded determines which ones answer.
1.2 The console layout
The sidebar is organised into nav groups, and each group contains domains (screens). This is defined in catalog.ts (NAV_GROUPS) and is exactly what you will see:
| Nav group | Domains (screens) |
|---|---|
| Identity & Access | Identity & session, Identity governance (IGA), Tenants |
| Security Operations | Agentic SOC, Detection hub, CTEM/exposure, Data lake/intel/SOAR, Threat Analytics, Deception, Detonation, Digital Risk |
| Estate Management | Estate plane, OT/ICS, Fleet & Provisioning |
| Governance & Trust | Policy plane, Audit & evidence, Lockbox & HYOK, SLA & private trust, Privacy & DSAR, Hardening & Drift |
| Platform | Licensing & updates, Integrations, Public & self-serve, System health, Release & Supply Chain, Suite & MSP |
Two universal tools sit on top of these screens:
- Command palette (
⌘K) — fuzzy-search any endpoint by name and jump straight to it. - Generic endpoint runner — a built-in form that can call any of the 389 routes with a
method, path, and JSON body (pre-filled from the sample in catalog.ts). This is your escape hatch for capabilities that have no dedicated screen — it is a first-class, supported way to operate the platform, not a debug back door.
1.3 Roles — who can do what
Roles are enforced in internal/app/authorize.go. There are four:
| Role | What it can do | Enforced by |
|---|---|---|
| admin | Everything: tenant lifecycle, credential/token issuance, break-glass setup, RBAC role management, geo-fence, release channels, MSP. | requireAdmin → 403 ADMIN_REQUIRED if missing |
| operator | Management/write actions (create channels, webhooks, collectors, set log level, IP allowlist, RBAC settings). Cannot do admin-only actions above. | requireManage (admin or operator) → 403 MANAGE_REQUIRED |
| viewer | Read-only. | Rejected by requireManage/requireAdmin |
| auditor | Read-only, and additionally time-boxed + role-gated for the evidence locker. An auditor only sees the locker while a live grant exists (§6.7). | auditor.HasAuditorRole + active grant |
Two independent gates protect every privileged action:
- Entitlement — does the license include this capability? (e.g.
control_plane.shell). - Role — does the caller hold
admin/operator? The entitlementcontrol_plane.shellis
held by nearly every role, so it is never sufficient on its own for a destructive or management action; the handler additionally calls requireAdmin/requireManage.
Some sensitive actions add a third gate — step-up re-authentication (§1.5).
1.4 Authentication model
- Interactive humans log in with SSO (OIDC or SAML) and complete **phishing-resistant
MFA** (WebAuthn passkey, or TOTP/email OTP depending on tenant auth policy). The session is a cookie named uscp_session. Anonymous/password-only access is forbidden by design; local password accounts are an admin-enabled fallback (§4.1).
- Machines/automation use service-account bearer tokens (
uscp_sa_<prefix>_<secret>),
which carry a scoped role and an explicit scope list (§2.5).
- SSO is multi-tenant. Home-realm discovery (
GET /auth/sso?email=…) matches the email domain
to a registered IdP across all tenants and lands the user in the tenant that owns that IdP — not the bootstrap tenant.
- SCIM 2.0 (
/scim/v2/..., bearer-authed) lets your IdP provision and deprovision users.
1.5 Step-up re-authentication (sensitive-action gate)
A small set of high-blast-radius actions require you to have re-authenticated with your passkey within the last 5 minutes (the step-up window). This is a property of your session, not a header you send — there is no X-Step-Up field. If your last passkey re-auth is older than 5 minutes, the call returns HTTP 401 with problem code STEP_UP_REQUIRED ("re-authenticate with your passkey within the step-up window to perform this sensitive action"). Re-authenticate (tap your passkey when the console prompts) and retry — you then have a fresh 5-minute window.
Actions that require step-up (confirmed in source):
POST /v1/tenants/{id}/export— bulk data egressDELETE /v1/tenants/{id}— right-to-erasure (also destroys keys)POST /v1/estate/killswitchandPOST /v1/estate/killswitch/resetPOST /v1/ai/models/{id}/promoteandPOST /v1/ai/models/{id}/retire
Window length: the step-up freshness window is 5 minutes (
stepUpMaxAge = 5 * time.Minuteininternal/identity/service.go, enforced byService.StepUpFresh). Re-running your passkey login resets the clock. There is no configuration flag to change this in the shipped build.
1.6 How entitlements gate everything
Every one of the 389 routes resolves an entitlement decision at the boundary, before the handler runs (entitlements.yaml). A route is exactly one of:
entitlement: <key>— fail-closed. No license → the call is rejected.public: "<reason>"— un-gated table-stakes (health probes, licensing posture, break-glass
invoke, SSO bootstrap, the console SPA itself).
You can see your live entitlement posture any time at GET /v1/licensing/status (public) and the full capability list at GET /.well-known/api-capabilities (public).
2. Getting started as admin
2.1 Procedure — become the first administrator
What/why: a brand-new install has no users. USCP mints the first admin automatically so you never run manual SQL.
- Confirm the deployment set the environment variable
USCP_BOOTSTRAP_ADMINto your email
(this is done at deploy time; see the deploy guide). This email is JIT-granted admin + operator on its first SSO login into the bootstrap tenant. Every other first-time user gets only operator. The same email in a non-bootstrap tenant is not elevated.
- Browse to
https://<host>/. You are redirected to your IdP (GET /auth/login). - Authenticate with SSO, then complete MFA.
- You land in the console. Confirm your identity and roles: Command palette → "Who am I", or
GET /v1/me → expect {"tenant_id":…,"user_id":…,"email":"you@…","roles":["admin","operator"]}.
- Expected result: you hold
admin; the sidebar shows every nav group your license enables. - Undo: to remove your own elevation later, have a second admin edit your role assignment, or
deprovision via SCIM/local-user delete (§4.1). Do not leave USCP_BOOTSTRAP_ADMIN set to a personal address long-term — rotate it to a break-glass identity after onboarding.
Alternative (no IdP yet): if the deployment set USCP_LOCAL_ADMIN_USER / USCP_LOCAL_ADMIN_PASSWORD, you can sign in via POST /auth/local/login and then enable SSO from inside the console. Treat local admin as a bootstrap-only path and disable it once SSO works (§4.1).
2.2 Procedure — enrol your MFA passkey (WebAuthn)
What/why: step-up actions and phishing-resistant login require a passkey.
- Console path: top-right user menu → Security → Add passkey (drives the WebAuthn ceremony).
- API (the console performs these for you):
POST /auth/webauthn/register/beginthen
POST /auth/webauthn/register/finish.
- Follow the browser/OS prompt (Touch ID, security key, platform authenticator).
- Expected result: the passkey is bound to your account; future step-up prompts succeed with a
tap. Register a second authenticator (spare) so you are never locked out of step-up.
- Undo: remove a passkey from the same Security screen.
2.3 Procedure — enrol TOTP (alternative MFA)
POST /v1/iam/totp/enrollwith body{"username":"you@example.com"}→ returns `{"secret":…,
"otpauth_uri":"otpauth://totp/USCP:…"}`.
- Scan the
otpauth_uriinto your authenticator app. POST /v1/iam/totp/verifywith{"username":"you@example.com","code":"123456"}→
{"verified":true,"enabled":true}.
- Expected result: TOTP is enabled for that account.
- Undo: re-enrol (a fresh enroll resets
totp_enabled=falseuntil re-verified) or delete/rotate
the account.
2.4 Procedure — navigate the console and command palette
- Sidebar — pick a nav group, then a domain screen.
- Command palette — press
⌘K/Ctrl-K, type part of an operation name (e.g. "kill switch",
"auditor grant"), press Enter to jump to it.
- Generic endpoint runner — open any domain, choose an endpoint, edit the pre-filled JSON
body, and Run. This calls the real API with your session and shows the raw response — use it for any capability without a bespoke screen.
- Expected result: you can reach all 389 routes without leaving the browser.
2.5 Procedure — mint, scope, and rotate a service-account token
What/why: automation (CI, SIEM pollers, scripts) authenticates with a bearer token instead of a human session. Tokens carry a role and a scope list (which entitlement keys they may reach).
Console path: Identity & Access → Identity & session → Service tokens (or command palette "service token"). Entitlement: control_plane.shell. Role: admin (requireAdmin).
- Mint —
POST /v1/iam/service-tokens
{ "name": "siem-poller", "scopes": ["control_plane.siem_hub","control_plane.shell"], "role": "viewer", "expires_days": 90 }
scopesdefaults to["*"](all).roledefaults tooperator.expires_days: 0means no
expiry (avoid for production).
- You cannot mint a token with a role you do not hold, or an admin token as a non-admin →
403 SCOPE_ESCALATION.
- Response
201returns the plaintext token once: `{"id":…,"name":…,"prefix":…,
"token":"uscp_sa_…","scopes":[…],"role":"viewer"}`. Copy it now — it is never shown again (only a SHA-256 hash is stored).
- Use — send
Authorization: Bearer uscp_sa_<prefix>_<secret>. The token may only call routes
whose entitlement key is in its scopes (* = all); anything else → 403 SCOPE_DENIED.
- List —
GET /v1/iam/service-tokens→ metadata only (no secret, no hash). - Rotate —
POST /v1/iam/service-tokens/{id}/rotaterevokes the old secret and returns a new
plaintext token (same name/role/scopes/expiry). Update your automation immediately.
- Expected result: scoped, auditable machine credentials.
- Undo / revoke:
DELETE /v1/iam/service-tokens/{id}→ soft-revokes (revoked_at=now()).
Rotate is itself the safe way to replace a possibly-leaked token.
2.6 Procedure — call the API by hand (curl)
# Public posture (no auth):
curl -s https://<host>/v1/licensing/status
# Authenticated read with a service token:
curl -s https://<host>/v1/me -H "Authorization: Bearer uscp_sa_ab12_…"
# A write:
curl -s -XPOST https://<host>/v1/tenants \
-H "Authorization: Bearer uscp_sa_ab12_…" -H "Content-Type: application/json" \
-d '{"slug":"acme","name":"Acme Corp"}'
The machine-readable contract is always available: GET /openapi.yaml (OpenAPI 3.1), GET /v1/openapi.json (for the in-app explorer), GET /asyncapi.yaml (events).
3. Tenant administration
Tenants are the isolation unit. Entitlement for all tenant lifecycle routes: control_plane.shell. Note on scoping: the suspend/resume/export/delete handlers enforce that the {id} in the path equals your own tenant (self-service lifecycle); acting on another tenant returns 403 FORBIDDEN. Cross-tenant management is done through the MSP/federation planes (§10.7) or by operating within each tenant.
3.1 Procedure — create a tenant
Console path: Identity & Access → Tenants → Create a tenant. Role: admin.
POST /v1/tenants
{ "slug": "acme", "name": "Acme Corp" }
- Note the returned tenant id.
- Expected result: a new isolated tenant with its own RLS scope.
- Undo: delete the tenant (§3.5) — but read the crypto-erase warning first; deletion is
irreversible. To take a tenant offline reversibly, suspend it instead (§3.2).
3.2 Procedure — suspend a tenant
What/why: freeze all activity (non-payment, investigation, offboarding hold) without deleting anything. A middleware rejects every request from a suspended tenant with 403 TENANT_SUSPENDED.
Console path: Identity & Access → Tenants → Suspend. Role: admin. Step-up: not required.
POST /v1/tenants/{id}/suspend(no body).
- Expected result:
200 {"status":"suspended"}; the tenant's users are locked out. - Undo: resume (§3.3). The
/resumepath is deliberately exempt from the suspension block so a
suspended tenant can be revived.
3.3 Procedure — resume a tenant
Console path: Identity & Access → Tenants → Resume. Role: admin.
POST /v1/tenants/{id}/resume(no body).
- Expected result:
200 {"status":"active"}; access restored. This reverses suspend. - Undo: suspend again.
3.4 Procedure — export a tenant (data portability)
What/why: produce a JSON snapshot of the tenant for portability/DR/compliance. This is bulk data egress, so it is a sensitive action.
Console path: Identity & Access → Tenants → Export. Role: admin or operator (requireManage). Step-up: REQUIRED (passkey re-auth).
- Ensure your passkey step-up is fresh (§1.5).
POST /v1/tenants/{id}/export(no body).
- Expected result: `200 {"export":{ "tenant_id":…, "generated_at":…, "users":[…],
"notification_channels":[…], "service_tokens":[…], "soar_cases":[…] (≤10 000), "audit_log":[…] (≤5 000), "metering_events":[…] (≤50 000) }}`. It is a non-destructive point-in-time JSON dump.
- Undo: none needed — read-only. Store the export securely; it contains tenant data.
3.5 Procedure — delete a tenant (right-to-erasure + crypto-erase)
What/why: permanently erase a tenant for right-to-erasure. This does two things: (a) crypto-erase — destroys the tenant's HYOK key-encryption key (KEK) so encrypted-at-rest data, including backups and replicas, becomes unrecoverable; and (b) cascade-deletes all tenant rows. A retained erasure certificate is written to an installation-wide ledger that survives the delete.
Console path: Identity & Access → Tenants → Delete. Role: admin. Step-up: REQUIRED.
- Confirm you truly intend this. It is IRREVERSIBLE — there is no undo, no restore, no un-erase.
- Ensure passkey step-up is fresh.
DELETE /v1/tenants/{id}with body:
{ "confirm_slug": "acme" }
confirm_slug must exactly equal the tenant's slug, or you get 422 CONFIRM_REQUIRED and nothing is deleted (this is the guard rail — mistyping is safe).
- Expected result:
200withdeleted:true,crypto_erase:true,keys_destroyed:<n>,
erasure_certificate:<id>, and the note that HYOK material was destroyed and encrypted-at-rest data (incl. backups) is unrecoverable. The certificate id is your proof of erasure for auditors.
- Undo: NONE. Irreversible by design. If you might need the data, run an export (§3.4)
first, or suspend (§3.2) instead of delete.
3.6 Procedure — pin data residency (data-sovereignty)
What/why: constrain where a tenant's data may live for sovereignty regimes (EU, etc.).
Console path: Estate Management → Fleet & Provisioning → Sovereign topology. Entitlement: platform.sovereign_topology_api.
- Inspect regions/topology:
GET /v1/sovereignty/regions,GET /v1/sovereignty/topology. - Pin:
PUT /v1/sovereignty/residency
{ "region": "eu" }
- Expected result: the tenant's data-path is pinned to the chosen region.
- Undo:
PUTagain with the previous region.
4. Identity & access
4.1 Local accounts and auth policy
Local accounts are an admin-enabled fallback to SSO. Entitlement: control_plane.shell.
4.1.1 Set the tenant auth policy
Console path: Identity & Access → Identity & session → Auth settings.
- Read:
GET /v1/iam/auth-settings. - Update:
PUT /v1/iam/auth-settings
{ "local_enabled": false, "oidc_enabled": true, "mfa": "webauthn" }
mfa∈none | totp | webauthn | email | either. Choosingemailrequires SMTP to be
configured (USCP_SMTP_HOST) or you get 400 EMAIL_OTP_UNAVAILABLE.
- You cannot disable both local and OIDC → 400
NO_LOGIN_METHOD(prevents lockout).
- Undo:
PUTagain with the prior values. Best practice: once SSO works, set
local_enabled:false and mfa:"webauthn".
4.1.2 Create / delete a local user
Console path: Identity & Access → Identity & session → Local users. Role: admin path in practice (user management).
- Create —
POST /v1/iam/local-users
{ "username": "svc-break-glass", "email": "bg@acme.com", "password": "at-least-8-chars", "roles": ["operator"], "source": "local" }
rolesdefaults to["operator"]. Password ≥8 chars forsource:"local"else 400
WEAK_PASSWORD. source ∈ local | ad-ldap | radius. Toxic role combinations are rejected 403 SOD_CONFLICT (§6). Seat cap enforced → 402 LICENSE_LIMIT_EXCEEDED.
- Set password —
POST /v1/iam/local-users/{username}/password{"password":"…"}(also revokes
that user's sessions).
- Undo / offboard:
DELETE /v1/iam/local-users/{username}— deletes the account and bumps
the session epoch so live sessions die within ≤60s.
4.1.3 External auth backends (AD/LDAP/RADIUS)
- Read:
GET /v1/iam/external-backends. - Replace-all:
PUT /v1/iam/external-backends
{ "backends": [ { "type":"ad-ldap","name":"corp","host":"dc1.corp","port":636,"base_dn":"dc=corp","realm":"CORP","bind_ref":"vault://ad-bind","enabled":true } ] }
bind_ref is a non-secret reference to a vaulted credential — never a plaintext password.
- Undo:
PUTthe list again without the entry (this endpoint replaces the whole list).
4.2 Roles, RBAC, and role mappings
4.2.1 Coarse roles (admin/operator/viewer/auditor)
GET /v1/rbac/roles,POST /v1/rbac/roles,DELETE /v1/rbac/roles/{name}manage the coarse
role catalog. Entitlement: control_plane.shell, role: admin.
4.2.2 Fine-grained RBAC (action+resource permissions)
Console path: Identity & Access → Identity & session → RBAC. Role: operator/admin (requireManage).
- Toggle:
GET /v1/iam/rbac/settingsthenPUT /v1/iam/rbac/settings{"fine_grained":true}. - Inspect grants:
GET /v1/iam/rbac/grants→{"roles":{"analyst":[{"action":"read","resource":"cases"}]}}. - Set a role's permissions:
PUT /v1/iam/rbac/roles/analyst
{ "permissions": [ {"action":"read","resource":"cases"}, {"action":"write","resource":"detections"} ] }
- Verify yourself:
GET /v1/iam/rbac/effective.
- Undo:
DELETE /v1/iam/rbac/roles/analystreverts that role to its built-in defaults.
4.2.3 Role mappings (IdP group / email-domain → platform role)
GET /v1/iam/role-mappings, then replace-all:PUT /v1/iam/role-mappings
{ "mappings": [ {"match_type":"group","match_value":"SecOps","role":"operator"}, {"match_type":"email_domain","match_value":"acme.com","role":"viewer"}, {"match_type":"default","match_value":"","role":"viewer"} ] }
match_type ∈ group | email_domain | default. These drive JIT role assignment on SSO login. Role changes bump the session epoch (≤60s propagation).
- Undo:
PUTthe list without the mapping.
4.3 SSO — OIDC IdP registration (multi-tenant)
Console path: Identity & Access → Identity & session → OIDC IdPs. Entitlement: control_plane.shell.
- Register —
POST /v1/iam/idps
{ "label": "Okta (Acme)", "issuer": "https://acme.okta.com", "client_id": "0oa…", "client_secret": "…", "email_domains": ["acme.com","acme.co.uk"] }
The client_secret is sealed at rest and never echoed back.
- List —
GET /v1/iam/idps(no secret returned). - Users at
acme.comnow reach this IdP via home-realm discovery (GET /auth/sso?email=jane@acme.com)
and land in the tenant that owns this IdP.
- Undo:
DELETE /v1/iam/idps/{id}(also evicts the IdP cache).
4.4 SSO — SAML 2.0 IdP registration
Console path: Identity & Access → Identity & session → SAML IdPs. Entitlement: control_plane.shell.
- Give your IdP the SP metadata:
GET /auth/saml/metadata(public). The SP entity id is that
metadata URL; the ACS is /auth/saml/acs.
- Register the IdP —
POST /v1/iam/saml-idps
{ "label": "ADFS (Acme)", "idp_metadata_xml": "<EntityDescriptor …>…</EntityDescriptor>", "email_domains": ["acme.com"], "allow_idp_initiated": false }
You supply the IdP's metadata XML (which itself carries entity id / SSO URL / signing cert) — not separate fields. Invalid XML → 400 INVALID_METADATA. Leave allow_idp_initiated:false unless you specifically need IdP-initiated flows (SP-initiated is safer).
- List —
GET /v1/iam/saml-idps(returns SP metadata + registered providers).
- Undo:
DELETE /v1/iam/saml-idps/{id}.
4.5 SCIM 2.0 provisioning
What/why: let your IdP create/update/deactivate users automatically. Auth: bearer (a service token, §2.5) with entitlement control_plane.shell.
- Point your IdP's SCIM connector at
https://<host>/scim/v2with the bearer token. It will probe
GET /scim/v2/ServiceProviderConfig, /ResourceTypes, /Schemas first.
- Provisioning operations your IdP will call:
POST /scim/v2/Users{"userName":"jane@acme.com","active":true,"name":{"givenName":"Jane","familyName":"Doe"}}GET /scim/v2/Users,GET /scim/v2/Users/{id}PATCH /scim/v2/Users/{id}{"Operations":[{"op":"replace","path":"active","value":false}]}(deactivate)DELETE /scim/v2/Users/{id}(deprovision)POST /scim/v2/Groups{"displayName":"operators"},GET/PATCH /scim/v2/Groups/{id}
- Undo: SCIM is declarative — set
active:falseorDELETEto remove access; recreate to restore.
4.6 Session management and revocation
Console path: Identity & Access → Identity & session → Sessions. Entitlement: control_plane.shell.
- List active sessions —
GET /v1/iam/sessions(your own is flaggedcurrent:true; token hashes
never shown).
- Revoke one —
DELETE /v1/iam/sessions/{id}→{"revoked":true,"id":…}. - Sign out all of your other sessions —
POST /v1/iam/sessions/revoke-others→
{"revoked_count":n} (keeps your current session).
- Undo: none — the user simply logs in again. Absolute session TTL is enforced by the identity
layer (documented as ~12h); it is not a per-request field here.
4.7 IP allowlist (tenant network fence)
Console path: Identity & Access → Identity & session → IP allowlist. Role: operator/admin (requireManage).
GET /v1/iam/ip-allowlist→{"cidrs":[…]}.PUT /v1/iam/ip-allowlist{"cidrs":["203.0.113.0/24","198.51.100.7"]}.- Self-lockout guard: your current IP must be inside a non-empty list or you get 422
SELF_LOCKOUT. An empty list means "no restriction".
- Requests from outside the list are rejected 403
IP_NOT_ALLOWED.
- Undo:
PUT {"cidrs":[]}to remove the restriction.
5. Licensing
USCP is fail-closed: capabilities you are not licensed for are simply unavailable. Licensing has two modes, chosen by the deploy-time env var LICENSING_MODE (airgap default, or online) — it is not changed via the API.
5.1 See your licensing posture (no auth)
Console path: Platform → Licensing & updates.
GET /v1/licensing/status— overall posture (mode, entitlements, continuity claim). Public.GET /v1/crypto/status— crypto manifest, FIPS mode, PQC readiness. Public.GET /.well-known/api-capabilities— the full 90-capability matrix. Public.
5.2 Air-gap vs online activation
- Air-gap (
LICENSING_MODE=airgap): a signed offline bundle (USCP_AIRGAP_BUNDLE) is
verified against trusted keys (USCP_LICENSE_JWKS). Posture: GET /v1/licensing/airgap (entitlement platform.airgap_licensing) → {mode, airgap_active, host_binding, status_url}. To update entitlements, distribute a new signed bundle and restart/reload (air-gap bundle distribution, §10.8).
- Online (
LICENSING_MODE=online): the binary callshttps://licensing.doublelogic.org
(configured via ENTITLEMENT_SERVICE_BASE_URL, USCP_LICENSE_TENANT, USCP_INSTALL_KEY). Posture: GET /v1/licensing/online (entitlement platform.online_licensing) → {mode, online_active, server_configured}.
- mTLS profile (online):
GET /v1/licensing/mtls-profile(entitlementplatform.mtls_license_profile).
- Undo: licensing mode is an environment change (redeploy), not an API toggle.
5.3 Seat limits and capability entitlements
- Seats: creating local users / SCIM users / service tokens is capped by license; over-cap
operations return 402 LICENSE_LIMIT_EXCEEDED. Free seats by deprovisioning users (§4) or revoking tokens (§2.5).
- Capabilities: each of the 90 keys (§14) is individually licensed. If a whole nav group is
missing, the capability is not in your license — check GET /.well-known/api-capabilities.
5.4 Continuity / escrow (vendor-failure protection)
See §10.6 for the full activation procedure. Posture is public at GET /v1/licensing/continuity; the escrow marker capability is platform.continuity_escrow.
6. Governance & trust
6.1 Policy plane (RBAC + ABAC, two-person publish, simulator)
Console path: Governance & Trust → Policy plane. Entitlement: control_plane.shell.
USCP evaluates policy with a deny-wins precedence engine (RBAC + ABAC layers). Policies are drafted, then published by a distinct second approver (two-person rule).
- Simulate a decision before you change anything —
POST /v1/policy/simulate
{ "subject": { "roles": ["operator"] }, "action": "estate.killswitch", "resource": "target/123" }
- Draft a policy —
POST /v1/policies
{ "name": "deny-prod-delete", "effect": "deny", "actions": ["estate.killswitch"], "resources": ["*"] }
- Publish with a second approver —
POST /v1/policies/{id}/approve
{ "approver": "second.operator@example.com" }
The approver must be distinct from the drafter (four-eyes).
- List —
GET /v1/policies.
- Undo: publish a new policy version that reverses the rule, or draft+approve a superseding
policy. Always simulate first. The digital-twin/regression tooling (§7.10) lets you test a policy change against scenarios before publishing.
6.2 Audit log (tamper-evident) and verification
Console path: Governance & Trust → Audit & evidence. Entitlement: control_plane.shell.
The audit log is a hash-chained, tamper-evident ledger. Verify its integrity regularly.
- Verify the chain —
GET /v1/_audit/verify. Expect an OK/valid result; a break indicates
tampering — treat as a SEV incident (§11).
- Activity timeline —
GET /v1/_audit/activity(last 24h + recent events). - Export tenant logs —
GET /v1/logs/export.
- Undo: the audit log is append-only/WORM by design — you cannot and must not edit it.
6.3 Auditor role — time-boxed grants
What/why: give an external/internal auditor read-only access to the evidence locker for a bounded window. Access requires both the auditor role and a live grant.
Console path: Governance & Trust → Audit & evidence → Auditor grants. Role: admin.
- Grant —
POST /v1/audit/auditor-grants
{ "subject": "auditor@big4.com", "ttl_days": 30 }
ttl_days defaults to 30, maximum 90 (>90 → 422 TTL_TOO_LONG). Re-granting the same subject extends/replaces the window.
- List —
GET /v1/audit/auditor-grants(each showsactive= not yet expired). - The auditor then uses
GET /v1/audit/evidence-lockerand
GET /v1/audit/evidence-locker/export?framework=<caiq|sig|ccm|800-53|oscal> (entitlement platform.customer_auditor_role).
- Undo / early revoke:
DELETE /v1/audit/auditor-grants/{subject}→{"revoked":true}. Access
also lapses automatically at expiry.
6.4 Lockbox — customer approval of vendor access
What/why: vendor/support access to your data is default-deny; each access is a request you approve or deny (Access Approval). Entitlement: platform.customer_lockbox.
Console path: Governance & Trust → Lockbox & HYOK.
- See requests —
GET /v1/lockbox/requests. - Raise a request (typically vendor-initiated) —
POST /v1/lockbox/requests
{"reason":"support","scope":"tenant-data","ttl":"1h"}.
- Decide —
POST /v1/lockbox/requests/{id}/decision{"decision":"approve"}(or"deny").
- Undo: deny future access; an approval is time-boxed by its
ttl. Pair with HYOK (§6.5) so the
vendor cannot decrypt without your key grant.
6.5 HYOK / BYOK — hold-your-own-key management
What/why: encrypt tenant data under a key you control, so the vendor cannot decrypt without your grant. Revoking the key renders data undecryptable by construction. Entitlement: platform.hyok (BYOK envelope posture: platform.byok_envelope).
Console path: Governance & Trust → Lockbox & HYOK.
- Provision the tenant KEK —
POST /v1/lockbox/hyok/provision(no body). Generates a 32-byte
KEK wrapped by your configured KMS provider. Refuses to overwrite an active key → 409 ALREADY_PROVISIONED (revoke first). KMS unreachable → 502 KMS_UNAVAILABLE.
- Status —
GET /v1/lockbox/hyok/statusandGET /v1/lockbox/byok(mechanism, provider,
external-KMS flag, manage URL).
- Encrypt / decrypt (envelope ops) —
POST /v1/lockbox/hyok/encrypt{"plaintext":"…"}→
{"envelope":…}; POST /v1/lockbox/hyok/decrypt {"envelope":…} → {"plaintext":"…"}.
- Revoke —
POST /v1/lockbox/hyok/revoke(no body). After this, all decrypts fail 403
KEY_REVOKED — the vendor cannot read the data.
- Undo: Revoke is NOT reversible for existing data — revoked data stays undecryptable
(that is the point). You may provision a new KEK afterward, but it does not restore access to data encrypted under the old (revoked) key. Treat revoke as a last-resort control.
6.6 Privacy — consent receipts and DSAR
Entitlement: control_plane.shell. Console path: Governance & Trust → Privacy & DSAR.
- Consent receipt —
POST /v1/privacy/consent{"Subject":"jane@acme.com","Purpose":"marketing","Basis":"consent","Granted":true}.
If the request carries Sec-GPC: 1 or DNT: 1 and the purpose isn't "essential", Granted is forced false (binding opt-out; gpc_honored:true in the response). List: GET /v1/privacy/consent. Undo: record a new receipt with Granted:false (append-only; no delete).
- DSAR export (right of access) —
GET /v1/privacy/dsar/exportreturns the caller's identity
bundle as an attachment. Read-only.
- DSAR erase (right to erasure) —
POST /v1/privacy/dsar/erase{"confirm":true}(must be
true, else 400 CONFIRM_REQUIRED). Deletes the user + sessions + WebAuthn creds + role assignments + any local account; WORM audit entries are retained under legal-basis exemption. Undo: NONE — irreversible.
- DSAR register —
GET /v1/privacy/dsar/requests(auditable log of all DSAR actions).
6.7 DLP — egress governance
What/why: inspect outbound content (e.g. webhook payloads) and allow / redact / block it. Entitlement: control_plane.dlp. Console path: Governance & Trust → Hardening & Drift (DLP is operated via the endpoint runner / DLP screen).
- See rules —
GET /v1/dlp/rules(built-in + tenant rules). - Add a rule —
POST /v1/dlp/rules
{ "ID": "no-ssn", "Name": "Block SSN", "Pattern": "\\d{3}-\\d{2}-\\d{4}", "Action": "block", "Severity": "high" }
ID and Pattern (a valid regexp, else 422 BAD_PATTERN) are required. Action ∈
allow | redact | block (default redact). Severity default medium.
- Test content —
POST /v1/dlp/scan{"Content":"my ssn is 123-45-6789"}→ verdict + findings +
redacted copy.
- Review egress events —
GET /v1/dlp/egress-events(last 200; block/redact are recorded).
- Behavior: on outbound webhooks,
blocksuppresses delivery,redactsends a masked copy,
allow sends as-is. Rule-load faults fail open to built-ins.
- Undo:
DELETE /v1/dlp/rules/{id}, orPOSTthe sameIDto overwrite.
6.8 Sanctions floor and per-tenant geo-fencing
What/why: the OFAC comprehensive-embargo floor is ALWAYS ON and cannot be disabled — requests from CU, IR, KP, SY are blocked pre-auth regardless of tenant policy (451 GEO_FENCED). On top of that, each tenant can add its own allow/deny country list.
Console path: Governance & Trust → Hardening & Drift (Geo-fence). Entitlement: control_plane.shell; PUT requires admin.
- Read —
GET /v1/geo-fence→{"mode":"off","countries":[…],"sanctions_floor":["CU","IR","KP","SY"]}. - Configure —
PUT /v1/geo-fence
{ "mode": "allow", "countries": ["US","GB","DE"] }
mode ∈ off | allow | block. allow permits only the listed countries; block rejects the listed ones; allow/block require ≥1 country (else 422 COUNTRIES_REQUIRED). Codes are ISO-3166 alpha-2, upper-cased.
- Undo:
PUT {"mode":"off"}to remove the tenant fence. **The OFAC floor cannot be turned off
by any endpoint.**
7. Security operations administration
All SecOps domains are operated from the Security Operations and Governance & Trust nav groups (plus the endpoint runner). Each has its own entitlement key — if a screen is empty, confirm the capability is licensed (§5.3).
7.1 Detection hub (detection-as-code)
Entitlement: control_plane.detection_hub. Console path: Security Operations → Detection hub.
- List/author detections —
GET /v1/detections,POST /v1/detections
{"name":"impossible-travel","logic":"sigma://…","enabled":true}.
- Rules lifecycle —
POST /v1/detect/rules,POST /v1/detect/rules/install-starters,
POST /v1/detect/rules/import-sigma, POST /v1/detect/rules/test, GET /v1/detect/rules, DELETE /v1/detect/rules/{id}.
- Findings —
GET /v1/detect/findings, escalatePOST /v1/detect/findings/{id}/escalate. - ATT&CK coverage —
GET /v1/detections/coverage(entitlementcontrol_plane.attack_coverage_map). - Tuning workflow: author →
POST /v1/detect/rules/test(dry-run) → enable. Undo: disable
or DELETE the rule. Follow the detection-lifecycle.md runbook (author→dry-run→canary→deploy).
7.2 CTEM / exposure management
Entitlement: control_plane.exposure_mgmt (+ attack_path, validation_bas). Console path: Security Operations → CTEM/exposure.
- Exposures/scope/vectors —
GET /v1/ctem/exposures,GET /v1/ctem/scope,GET /v1/ctem/vectors. - Run an assessment —
POST /v1/ctem/assess; resultsGET /v1/ctem/assessments. - Attack path —
POST /v1/ctem/attack-path{"from":"internet","to":"crown-jewel"}. - BAS validation —
POST /v1/ctem/validate{"technique":"T1078","target":"…"}(safe, scoped;
see ctem-validation.md — never destructive payloads).
- CVE feed —
POST /v1/ctem/cve-feed,POST /v1/ctem/cve-feed/fetch.
7.3 SOAR, cases, and playbooks
Entitlement: control_plane.case_management (cases) / control_plane.soar (playbooks). Console path: Security Operations → Data lake/intel/SOAR.
- Cases —
POST /v1/soar/cases{"title":"Suspicious login","severity":"SEV-3"},GET/`PATCH
/v1/soar/cases/{id}, notes GET/POST /v1/soar/cases/{id}/notes, transition/assign/priority/ escalate via the corresponding /v1/soar/cases/{id}/…` routes.
- Playbooks —
POST /v1/soar/playbooks/{id}/run{"inputs":{}}. - CACAO import/export —
POST /v1/soar/cacao/export,POST /v1/soar/cacao/import. - Undo: transition a case back / reassign; playbook runs are logged. Follow
soar.md.
7.4 Agentic SOC (autonomous agents + kill-switch)
Entitlement: control_plane.agentic_soc. Console path: Security Operations → Agentic SOC.
- Investigate —
POST /v1/soc/agents/investigate{"alertId":"…","scope":"tenant"}. - Execute a remediation —
POST /v1/soc/agents/execute{"caseId":"…","action":"isolate-host"}. - Halt all agents —
POST /v1/soc/agents/killswitch{"reason":"manual override"}. This is
fail-safe: tripping stops autonomous action immediately.
- Reset the kill-switch —
POST /v1/soc/agents/killswitch/reset. - Scorecards / model —
GET /v1/soc/agents/scorecards,GET /v1/soc/agents/model. - Undo: the kill-switch reset re-enables agents. Follow
soc-agent-action.md/
agent-budget-breach.md for runaway-agent handling.
7.5 Threat analytics — UEBA, ITDR, correlation, posture, risk, SIEM, AIOps
Console path: Security Operations → Threat Analytics. Each has its own entitlement.
- UEBA (
control_plane.ueba):GET /v1/ueba/entities,POST /v1/ueba/scan({}),
GET /v1/ueba/anomalies, GET /v1/ueba/entities/{entity}.
- ITDR (
control_plane.itdr):GET /v1/itdr/signals,POST /v1/itdr/scan,GET /v1/itdr/detections. - Correlation (
control_plane.correlation):POST /v1/correlation/run,GET /v1/correlation/live,
GET /v1/correlation/incidents.
- Posture score (
control_plane.posture_score):GET /v1/posture/score,
POST /v1/posture/snapshot, GET /v1/posture/history.
- Quantitative risk / FAIR (
control_plane.risk_quant):GET /v1/risk/portfolio,
POST /v1/risk/scenarios {"name":"ransomware","asset_value":500000,"threat_freq":0.5,"vuln":0.4}, GET /v1/risk/scenarios, DELETE /v1/risk/scenarios/{id}.
- SIEM hub (
control_plane.siem_hub):GET /v1/siem/overview,GET /v1/siem/alerts. - AIOps (
control_plane.aiops):GET /v1/aiops/health,POST /v1/aiops/scan,GET /v1/aiops/anomalies. - Tuning: the
POST …/scancalls persist anomalies; run them on a schedule (a service token +
cron is the usual pattern). Undo: these are analytic reads; delete risk scenarios to remove.
7.6 Deception (honeytokens)
Entitlement: control_plane.deception. Console path: Security Operations → Deception.
- Plant —
POST /v1/deception/honeytokens{"Name":"decoy-cred","Kind":"credential","Placement":"vault"}.
Kind ∈ credential | url | file | dns | api_key | host. The token value (ht-<kind>-<hex>) is returned once — place it where an intruder would find it.
- List —
GET /v1/deception/honeytokens. Report/observe hits —POST /v1/deception/trip
{"token":"ht-credential-…","source_ip":"10.0.0.9"}; feed — GET /v1/deception/trips. Any trip is high-signal (severity critical).
- Undo:
DELETE /v1/deception/honeytokens/{id}.
7.7 Detonation (sandbox)
Entitlement: control_plane.detonation. Console path: Security Operations → Detonation.
- Provider is environment-configured, not via API:
USCP_DETONATION_PROVIDER=
simulation (default) or http; USCP_DETONATION_ENDPOINT (SSRF-guarded) for the http provider. Inspect it: GET /v1/detonation/provider → {provider, endpoint_configured, simulation}.
- Submit —
POST /v1/detonation/submit{"sample_ref":"https://x/payload.bin","sample_hash":"…","sample_type":"file"}
(provide sample_ref or sample_hash). Jobs/verdicts — GET /v1/detonation/jobs.
- Undo: none (analysis only). Configure a real sandbox by setting the env vars and restarting.
7.8 OT / ICS ingestion (read-only)
Entitlement: control_plane.ot_visibility / control_plane.ot_readonly. Console path: Estate Management → OT/ICS. USCP is read-only toward OT (no control writes).
- Ingest an asset (sensor push) —
POST /v1/ot/assets
{"name":"plc-1","asset_type":"plc","protocol":"modbus","vendor":"Siemens","ip":"10.1.0.5","zone":1}.
asset_type ∈ plc | rtu | hmi | scada | sensor | historian; zone is a Purdue level 0-5.
- Inventory/zones/safety —
GET /v1/ot/assets,GET /v1/ot/zones,GET /v1/ot/safety-posture. - UAT seed —
POST /v1/ot/simulate-seed(labels assetssource=simulation). - Undo: none via API (visibility only). Follow
network-fabric-config.mdfor fabric changes.
7.9 Digital Risk Protection (DRP)
Entitlement: control_plane.drp. Console path: Security Operations → Digital Risk.
- Monitor an asset —
POST /v1/drp/assets{"Kind":"domain","Value":"acme.com"}(Kind∈
domain | brand | keyword | email_domain).
- Ingest a finding from your DRP feed —
POST /v1/drp/ingest
{"asset":"acme.com","category":"typosquat","severity":"high","detail":{}}.
- Findings —
GET /v1/drp/findings; UAT —POST /v1/drp/simulate. - Undo: re-
POSTan asset to overwrite (upsert); findings age out by retention.
7.10 Threat intel, data lake, hardening/drift, collective defense, policy twin
- Threat intel (TIP) (
control_plane.threat_intel):POST/GET /v1/intel/indicators,
suppress/unsuppress POST /v1/intel/indicators/{id}/suppress|unsuppress, TAXII GET /v1/intel/collections/{id}/objects. Follow threat-intel.md / intel-feed-anomaly.md.
- Data lake (
control_plane.security_data_lake/federated_search):POST /v1/lake/ingest,
POST /v1/lake/query.
- Config drift (
control_plane.config_drift): baselinePOST /v1/config-drift/baselines,
check POST /v1/config-drift/check, view GET /v1/config-drift/drifts. Undo: re-baseline.
- Telemetry fabric (
control_plane.telemetry_fabric):GET/POST /v1/telemetry/routes,
DELETE /v1/telemetry/routes/{id}, POST /v1/telemetry/evaluate.
- Collective defense (
control_plane.collective_defense):GET /v1/collective/status,
opt-in POST /v1/collective/optin {"opted_in":true}, IOCs GET/POST /v1/collective/iocs. Undo: optin {"opted_in":false}.
- Policy twin (
control_plane.policy_twin):POST /v1/policy-twin/simulate,
suites POST/GET /v1/policy-twin/suites, run POST /v1/policy-twin/suites/{id}/run — use this to regression-test RBAC changes before publishing (§6.1).
7.11 IGA — segregation-of-duties, certification, joiner/leaver
Console path: Identity & Access → Identity governance (IGA).
- SoD check (
control_plane.sod):POST /v1/iga/sod/check{"user":"jane@acme.com","proposedRole":"approver"}. - Access certification (
control_plane.access_certification):POST /v1/iga/certify,
campaigns GET /v1/iga/campaigns.
- Joiner/leaver (
control_plane.iga):POST /v1/iga/joiner,POST /v1/iga/leaver
{"user":"jane@acme.com"} (deprovisions; ≤60s session revocation). Follow iga-governance.md.
- Undo: re-provision via joiner/SCIM.
8. Estate management
The estate plane manages the machines/devices you operate through USCP (Linux/Windows hosts, network gear, BMCs). Sessions are brokered and recorded (WORM); credentials are vaulted and injection-only. Follow estate-session-anomaly.md, vault-credential-anomaly.md, hostkey-attestation-failure.md.
8.1 Enrol targets and agents
Entitlement: control_plane.estate_inventory. Console path: Estate Management → Estate plane.
- Register a target —
POST /v1/estate/targets{"kind":"linux-host","address":"10.0.0.10","labels":{"env":"prod"}}. - List/inventory —
GET /v1/estate/targets,GET /v1/estate/capabilities,GET /v1/estate/topology. - Agents —
GET /v1/estate/agents, enrolPOST /v1/estate/agents/enroll.
8.2 Vault credentials (PAM)
Entitlement: control_plane.estate_pam.
- Vault —
POST /v1/estate/credentials
{"cred_id":"root-key","kind":"ssh-key","username":"root","secret":"…","source":"platform"}.
source ∈ platform | cyberark | hashicorp-vault | delinea | ad-ldap; platform requires secret, external sources require a reference. Secrets are injection-only (never copied out).
- List (metadata only) —
GET /v1/estate/credentials. Providers/connectors —
GET /v1/estate/credential-providers, GET/POST/DELETE /v1/estate/pam-connectors[/{id}].
- Undo / rotate:
DELETE /v1/estate/credentials/{id}; followestate-credential-rotation.md.
8.3 Brokered, recorded sessions
Entitlement: control_plane.session_broker.
- Open —
POST /v1/estate/sessions{"target_id":"…","cred_id":"…","protocol":"ssh","allowlist":["^uptime$"]}
→ {"session_id":…,"protocol":…,"recorded":true} (recording is mandatory).
- Exec —
POST /v1/estate/sessions/{id}/exec{"command":"uptime"}(blocked commands → 403
COMMAND_BLOCKED).
- Recording —
GET /v1/estate/sessions/{id}/recording→{events, chain_valid, broke_at_seq}
(chain_valid proves the WORM recording is untampered).
8.4 Host-key pinning
Entitlement: control_plane.estate_inventory.
- View pins —
GET /v1/estate/host-keys. Reset (on legitimate rekey) —POST /v1/estate/host-keys/reset. - Why: an unexpected host-key change is a spoofing signal — see
hostkey-attestation-failure.md.
8.5 Firewall policy management
Entitlement: control_plane.estate_policy. Console path: Governance & Trust → Policy plane (host firewall) / endpoint runner.
GET /v1/policy/firewall, liveGET /v1/policy/firewall/live, importPOST /v1/policy/firewall/import,
create POST /v1/policy/firewall, patch PATCH /v1/policy/firewall/{id}, delete DELETE /v1/policy/firewall/{id}, duplicate POST /v1/policy/firewall/{id}/duplicate.
- Undo:
DELETEthe rule orPATCHit back.
8.6 Policy packs and fan-out
Entitlement: control_plane.estate_policy (packs) / control_plane.estate_response (fan-out).
- Plan → apply —
POST /v1/estate/packs/plan{"pack":"cis-baseline","scope":{"env":"prod"}}then
POST /v1/estate/packs/apply {"planId":"…"}.
- Fan-out —
POST /v1/estate/fanout{"pack":"linux-stig","targets":[…],"canary_count":1,"ring_count":3,"abort_on_canary_failure":true}.
Refused 503 ESTATE_HALTED if the estate kill-switch is tripped. Pack values include
linux-stig | windows-stig | osquery | ngfw-connector.
8.7 Estate kill-switch (step-up + four-eyes) — THE emergency stop
Entitlement: control_plane.estate_response. Console path: Estate Management → Estate plane → Kill-switch.
- Trip (halt all estate response) —
POST /v1/estate/killswitch. Step-up REQUIRED. Tripping
is single-admin and fail-safe → {"kill_switch":"tripped"}. All fan-out is now refused.
- Reset (re-enable) is deliberately hard — it needs step-up AND two admins:
- Admin #1:
POST /v1/estate/killswitch/reset{"reason":"incident resolved"}(step-up
required, admin role). This does not reset directly — it opens a dual-control pending request → 202 {"dual_control":"pending","request_id":…}.
- Admin #2 (distinct): approve it —
POST /v1/dualcontrol/{request_id}/approve. Only now does
the reset actually run. (Reject with POST /v1/dualcontrol/{request_id}/reject; list pending with GET /v1/dualcontrol.)
- Undo: trip is undone only by the two-admin reset above; a reset is undone by tripping again.
8.8 Fleets and zero-touch provisioning
- Fleets (
control_plane.fleet_mgmt): createPOST /v1/fleets{"name":"prod-fleet","tags":["prod"]},
list GET /v1/fleets, members POST /v1/fleets/{id}/members {"target_id":"…"}, delete DELETE /v1/fleets/{id}. (No delete-member route — delete the fleet to drop membership.)
- ZTP (
control_plane.ztp): mint enrol tokenPOST /v1/ztp/tokens
{"name":"edge","ttl_hours":24,"max_uses":1,"template":{"role":"agent"}} (token ztp-<hex> returned once), list GET /v1/ztp/tokens, revoke DELETE /v1/ztp/tokens/{id}, redeem POST /v1/ztp/redeem {"token":"ztp-…"}.
- Undo:
DELETEthe fleet / revoke the ZTP token.
9. Observability & operations
9.1 Metrics
GET /metrics— Prometheus scrape endpoint. **Public at the app layer — protect it at the
network layer** (do not expose it to the internet). Point Prometheus/Grafana at it.
9.2 Health probes
GET /healthz,GET /livez(liveness),GET /readyz(readiness — DB + migrations),
GET /healthz/deep (per-component dependency health). Wire these into your load balancer and uptime checks. All public.
9.3 SRE dashboards
GET /v1/sre/dashboards(entitlementplatform.sre_dashboards) — vendor-neutral dashboard specs
you can import into Grafana. Console path: Platform → Release & Supply Chain.
9.4 Log retention (config + regulatory floors)
What/why: control how long each log class is kept across hot/warm/cold tiers. Some classes have regulatory floors you cannot go below. Entitlement: control_plane.shell. Console path: Governance & Trust → Audit & evidence (retention) / endpoint runner.
- Read —
GET /v1/observability/retention→ each class showshot_seconds,warm_seconds,
cold_seconds, floor_cold_seconds, legal_hold, overridden.
- Override —
PUT /v1/observability/retention
{ "overrides": [ { "class": "security", "hot_seconds": 2592000, "warm_seconds": 15552000, "cold_seconds": 63072000 } ] }
Validation: warm ≥ hot, cold ≥ warm (when set), and cold may never be below the class floor → 422 RETENTION_FLOOR. Duration helper: 30d=2 592 000s, 1y=31 536 000s, 7y=220 752 000s.
Regulatory cold-tier floors (cannot be set lower):
| Class | Floor | Class | Floor |
|---|---|---|---|
audit | 7y | incident | 7y |
security | 1y | policy_change | 7y |
access | 1y | license_event | 7y |
Classes not listed have no floor. audit and incident are legal-hold classes — never deleted by the retention worker regardless of horizon.
- Undo: there is no delete-override endpoint;
PUTthe class again with the built-in defaults
(or any values ≥ the floor).
9.5 Log forwarding — collectors, DLQ, replay
What/why: ship logs to your SIEM/lake with durable retry and a dead-letter queue. Entitlement: control_plane.shell. Console path: Platform → Integrations / endpoint runner.
- Collectors —
GET /v1/logs/collectors; add (requireManage) —POST /v1/logs/collectors
{ "name": "splunk-prod", "type": "splunk_hec", "endpoint": "https://splunk:8088", "config": {"index":"sec"}, "auth": {"token":"…"} }
type ∈ s3 | syslog | kafka | https | otel | splunk_hec. Endpoints are SSRF-guarded; auth is sealed at rest. Delete (requireManage) — DELETE /v1/logs/collectors/{id}.
- Forward-queue health —
GET /v1/observability/forward-queue→{pending, dead, oldest_pending_at}. - Replay the DLQ —
POST /v1/observability/forward-queue/replay→{"replayed":n}(requeues
this tenant's dead batches). Run this after fixing a collector outage.
- Undo:
DELETEthe collector.
9.6 Telemetry routing
See §7.10 — control_plane.telemetry_fabric routes events by source/severity to destinations.
9.7 Notifications / alerting channels
What/why: send alerts to Slack, PagerDuty, email, or a webhook, with a durable delivery log + replay. Entitlement: control_plane.shell. Console path: Platform → Integrations.
- List —
GET /v1/notifications/channels. - Add (
requireManage) —POST /v1/notifications/channels
{ "kind": "slack", "name": "SOC on-call", "target": "https://hooks.slack.com/…", "events": ["detection.finding","estate.killswitch"] }
kind ∈ slack | pagerduty | email | webhook (else 400 BAD_REQUEST). The target secret is sealed at rest.
- Test (
requireManage) —POST /v1/notifications/channels/{id}/test→{"delivered":true}
(channel error → 502 CHANNEL_ERROR).
- Deliveries + replay —
GET /v1/notifications/deliveries,POST /v1/notifications/deliveries/{id}/replay. - Undo:
DELETE /v1/notifications/channels/{id}.
9.8 Outbound webhooks (with DLQ)
Entitlement: control_plane.shell. Console path: Platform → Integrations.
- Create (
requireManage) —POST /v1/webhooks{"url":"https://api.acme.com/hook","events":["case.created"],"secret":"whsec_…"}
(both url and secret required; URL is SSRF-guarded; secret sealed). Deliveries are HMAC-signed (X-USCP-Signature, X-USCP-Event), at-least-once, up to 6 attempts then dead.
- List/test —
GET /v1/webhooks,POST /v1/webhooks/test. - Delivery log (DLQ) —
GET /v1/webhooks/deliveries?status=dead, replay
POST /v1/webhooks/deliveries/{id}/replay (requireManage; resets a dead/failed delivery to pending).
- Undo: there is no delete-webhook route exposed; disable by removing its
eventsor rotating
the endpoint. (Verify with engineering if you need hard-delete — see uncertainties.)
9.9 Runtime log level and tracing
- Log level (
control_plane.shell):GET /v1/admin/log-level; set (requireManage)
PUT /v1/admin/log-level {"level":"debug"} (level ∈ debug | info | warn | error; applies at runtime, no restart). Undo: PUT back to info.
- Events stream / emit (for integration testing):
GET /v1/events/stream,POST /v1/events/emit. - Tracing is emitted via the OTel collector (suite
GET /v1/suite/otel,suite.shared_otel_collector).
10. Resilience & business continuity
10.1 Break-glass — the emergency admin path (§98)
What/why: SSO + MFA are mandatory, so if your only identity path (the IdP) is down, every operator is locked out — often during the SSO change that broke it. Break-glass is the drilled way back in. It is IdP-independent, dual-control (M-of-N recovery codes), time-boxed, loud, and never entitlement-gated. Full runbook: codes/runbooks/break-glass.md (RB-F).
10.1.1 Procedure — provision/re-seal break-glass codes (day-0 and after every use)
Console path: Platform → (break-glass setup) / endpoint runner. Entitlement: control_plane.shell; must be admin, in the bootstrap tenant (else 412 NO_BOOTSTRAP_TENANT / 403 WRONG_TENANT).
POST /admin/break-glass/setup
{ "threshold": 3, "ttl_minutes": 60 }
threshold (M-of-N quorum) defaults to 2; ttl_minutes defaults to 60. This generates exactly 10 recovery codes (bg-<hex>), replaces any prior codes, and stores only their hashes.
- Response
201returns all 10 plaintext codes ONCE. Distribute one code to each of several
distinct custodians; store them offline (safe/HSM/sealed envelope) — never in USCP, never in the IdP.
- Expected result: a sealed, deny-by-default emergency path exists.
- Undo / rotate: re-run
setup— it replaces all codes (old codes are invalidated). **Re-seal
after every activation** (codes are single-use).
10.1.2 Procedure — activate break-glass during an outage
This endpoint is public (POST /admin/break-glass) — never entitlement-gated — so it works when the IdP and licensing paths are down. Requires the quorum, from distinct custodians.
- Confirm this is genuinely an identity-path failure (not licensing fail-closed — break-glass
does not fix licensing; see §10.6). Mint INC-BG-YYYYMMDD-NN.
POST /admin/break-glass
{ "reason": "IdP unreachable during SAML cert rotation", "codes": ["bg-aaa…","bg-bbb…","bg-ccc…"] }
reason is required. Codes are de-duplicated by hash (distinct custodians). If fewer than threshold distinct valid unused codes are presented, nothing is consumed and you get 403 BREAK_GLASS_QUORUM.
- On success (
200): a time-boxed admin session is minted (TTL =ttl_minutes), MFA marked
complete, session cookie set; the presented codes are burned (single-use). A tenant-wide banner, SEV-2 audit, and security+billing notifications fire for the whole window.
- Scope discipline: use break-glass only to restore identity (re-point IdP, re-upload
SAML/OIDC metadata + certs, re-enable users). It cannot disable audit, recording, or the break-glass controls themselves.
- Undo / closeout: the session auto-expires at
ttl_minutes; you cannot silently extend it.
Afterward, re-seal (§10.1.1) and run the mandatory post-use review (owner ≠ the operator who used it).
10.1.3 Estate break-glass (privileged-session emergency access, §90)
Separate from §98. Entitlement: control_plane.session_broker. Dual-control, least-scope, time-boxed:
- Request —
POST /v1/estate/breakglass/request
{"target_id":"…","justification":"incident","ttl_seconds":3600,"required_approvals":2,"scope":"target_only"}
(scope ∈ target_only | read_only | full).
- Approve (distinct approver) —
POST /v1/estate/breakglass/{id}/approve(requester cannot approve
own; grant activates when distinct approvers ≥ required_approvals).
- List —
GET /v1/estate/breakglass. - Undo: the grant auto-expires at
ttl_seconds. Followvendor-access-approval.md.
10.2 Backup and restore
- Database is your source of truth — back up PostgreSQL with your standard tooling (PITR/WAL
archiving recommended). Crucially: because tenant delete crypto-erases the HYOK KEK, a DB backup alone cannot restore a deleted tenant's encrypted data — the key is gone (§3.5). Back up KMS/HYOK key material per your KMS provider's DR guidance.
- Tenant-level export (
POST /v1/tenants/{id}/export, §3.4) is a portable JSON snapshot for
point-in-time capture.
- Self-service data ops (
platform.self_service_data_ops):POST /v1/data-ops/export-snapshot,
POST /v1/data-ops/purge, GET /v1/data-ops/jobs.
- Verify restores in a non-prod environment; confirm
GET /readyzandGET /v1/_audit/verify
after a restore.
10.3 DR / region failover
Follow codes/runbooks/region-failover.md (RB-B) — cell-based failover, per-tenant home-region promotion, status-page + comms. Rehearse quarterly. Use GET /v1/sovereignty/topology to confirm region placement and GET /healthz/deep to confirm per-component health post-failover.
10.4 Continuity / escrow (vendor-failure) — §97
What/why: if the vendor disappears, activate an escrowed, signed "frozen build" license so you keep running (preservation-only — it grants nothing you did not already hold). Entitlement: platform.continuity_escrow; admin + dual-control. Follow vendor-continuity.md (RB-E).
- Ensure escrow is provisioned: env
USCP_ESCROW_JWKS(+USCP_LICENSE_JWKS) set, else 412
ESCROW_NOT_PROVISIONED. Check posture: GET /v1/licensing/continuity (public).
- Activate —
POST /v1/licensing/continuity/activate
{ "bundle": "<compact-JWS>", "approvers": ["you@acme.com","second.admin@acme.com"] }
Your own email must be in approvers (else 403 APPROVER_IDENTITY); ≥2 distinct approvers required (else 400 DUAL_CONTROL). The escrow bundle's signing key must be trusted in the escrow JWKS or you get 422 CONTINUITY_REFUSED.
- Expected result:
200 {"activated":true,"frozen_at":…}; a SEV-1 audit event is written. - Undo: NONE — this is an append-only, one-way emergency action. It is preservation-only, so
it is safe to leave active.
10.5 Release channels + module hot-reload
- Release channels (
platform.release_channels), admin: channel names are **`stable |
beta | edge** (not "canary"). Pin a channel version — PUT /v1/release/channels/{name} {"Version":"1.4.2","Notes":"…"}; assign this tenant — PUT /v1/release/assignment {"Channel":"stable"}. Read: GET /v1/release/channels, GET /v1/release/assignment. **Undo:** PUT` the prior channel/version.
- Updates (
control_plane.shell):GET/POST /v1/updates/check,GET /v1/updates/status,
POST /v1/updates/apply {"version":""}. Updates are cosign/SLSA-verified before apply (update-verification-failure.md).
- Module hot-reload (
platform.module_hot_reload): registerPOST /v1/modules/register
{"name":"soar","version":"1.0","ui_mount":"/soar"}, list GET /v1/modules, reload POST /v1/modules/{name}/reload (runtime, no restart).
10.6 Air-gap bundle distribution (parent→children)
Entitlement: suite.airgap_bundle_distribution. POST/GET /v1/suite/airgap-bundles — a parent control plane distributes signed offline update bundles to child installations. Pair with platform.self_hosted_release_parity (GET /v1/release/parity) so managed and air-gap run the same stream.
10.7 MSP / co-managed SOC (§49)
Entitlement: control_plane.msp / control_plane.comanaged_soc. Console path: Platform → Suite & MSP, admin.
- Add a managed tenant —
POST /v1/msp/managed{"managed_tenant":"cust-1","scope":"soc"}
(scope ∈ full | soc | readonly; self-manage → 422 SELF_MANAGE).
- List —
GET /v1/msp/managed; co-managed SOC view —GET /v1/msp/soc. - Undo:
DELETE /v1/msp/managed/{id}(soft-revokes the relationship).
10.8 Parent-suite federation (§37/§49)
Entitlement: suite.federation_core. Run a parent that federates children: POST/GET /v1/federation/children, GET /v1/federation/children/{id}, heartbeat POST /v1/federation/children/{id}/heartbeat, DELETE /v1/federation/children/{id}, health/posture GET /v1/federation/health, GET /v1/federation/posture. Follow federation-health.md / federation-assertion-anomaly.md. Undo: DELETE the child registration.
11. Routine operations & runbooks
USCP ships an operator runbook library at codes/runbooks/ (index: runbooks/INDEX.md). Every STRIDE detection signal has a matching playbook (RB-01…RB-27) plus mandatory playbooks (first-24h, cred-rotation, region-failover, break-glass, vendor-continuity, RACI, estate-credential -rotation). Playbooks unrehearsed >180 days fail CI — schedule the drills.
11.1 Daily checklist
- [ ]
GET /healthz/deepall green;GET /readyzOK. - [ ]
GET /v1/_audit/verifyreturns a valid chain (tamper check). - [ ] Forward-queue drained:
GET /v1/observability/forward-queue→dead:0(replay if not, §9.5). - [ ] Webhook/notification DLQ clear:
GET /v1/webhooks/deliveries?status=dead,GET /v1/notifications/deliveries. - [ ] Review new SIEM alerts (
GET /v1/siem/alerts), UEBA/ITDR anomalies, deception trips
(GET /v1/deception/trips — any trip = investigate now).
- [ ] Confirm no unexpected kill-switch state (agentic + estate).
11.2 Weekly checklist
- [ ] Run UEBA/ITDR/AIOps/correlation scans (
POST …/scan,POST /v1/correlation/run). - [ ] Snapshot posture:
POST /v1/posture/snapshot; reviewGET /v1/posture/scoretrend. - [ ] Review auditor grants (
GET /v1/audit/auditor-grants) — revoke stale ones (§6.3). - [ ] Review service tokens (
GET /v1/iam/service-tokens) — rotate/expire unused (§2.5). - [ ] Review sessions for anomalies (
GET /v1/iam/sessions;auth-session-anomaly.md). - [ ] Config-drift check on golden configs (
POST /v1/config-drift/check).
11.3 Monthly / quarterly
- [ ] Generate SLA report:
POST /v1/sla/reports/generate{"period":"2026-07"}(platform.sla_reporting). - [ ] Rotate estate credentials + signing keys (
cred-rotation.md,estate-credential-rotation.md). - [ ] Break-glass drill (
break-glass.md) — provision, activate in a test window, re-seal. - [ ] Region-failover drill (
region-failover.md). - [ ] Tabletop/game-day (
tabletop-game-day.md). - [ ] Access-certification campaign (
POST /v1/iga/certify); leaver reconciliation. - [ ] Verify retention floors unchanged (§9.4) and legal-hold classes intact.
11.4 Incident response (P1+)
Follow first-24h.md (RB-00): T+0 detect + classify (P1 = customer-impacting >50% tenants or data integrity), status page → investigating within 5 min; T+0–15 page on-call, assign IC + comms, customer notice within 15 min; T+15–60 contain with two-person approval, status update every 30 min; T+60–24h recover; PIR within 5 business days. Evidence retained 10 years, locked WORM once PIR is published. Snapshot forensic evidence with GET /v1/_audit/activity + session recordings. Use the SEV matrix + RACI in raci.md and comms templates in customer-comms-templates.md.
12. Security hardening checklist
- [ ] TLS/mTLS — terminate TLS in front of USCP (or via
USCP_TLS_CERT/USCP_TLS_KEY); require
mTLS for the licensing/control path where supported (USCP_TLS_CLIENT_CA, GET /v1/licensing/mtls-profile). Confirm tls_min_version ≥ 1.2 (GET /v1/crypto/status).
- [ ] Protect
/metricsat the network layer — it is app-public; never expose it publicly (§9.1). - [ ] SSO + phishing-resistant MFA mandatory — set
mfa:"webauthn",local_enabled:falseonce
SSO works (§4.1). Disable the bootstrap local admin.
- [ ] Break-glass provisioned and drilled (§10.1); custodians distinct; codes offline.
- [ ] IP allowlist for admin origins where feasible (§4.7).
- [ ] Sanctions floor confirmed active (CU/IR/KP/SY — always on) and per-tenant geo-fence set to
your policy (§6.8).
- [ ] Rate limits / WAF / CSRF — front USCP with a WAF; the app enforces SSRF guards on all
outbound targets (webhooks, collectors, detonation, OIDC/SAML). Confirm your reverse proxy adds rate limiting and standard security headers.
- [ ] Secrets — no plaintext secrets: all secrets from vault/KMS (env
*_ref/JWKS), sealed at
rest; service tokens scoped + expiring (§2.5).
- [ ] RLS verification — periodically prove cross-tenant isolation (see
cross-tenant-audit.md);
the binary ships RLS isolation tests.
- [ ] HYOK/BYOK provisioned for restricted-data tenants (§6.5); lockbox default-deny (§6.4).
- [ ] Audit chain verified on a schedule (
GET /v1/_audit/verify); retention floors enforced (§9.4). - [ ] Signed releases — verify before apply:
POST /v1/release/verify; SBOM/VEX/SLSA pack
GET /v1/release/pack.
- [ ] Sender-auth for email notifications (SPF/DKIM/DMARC on your SMTP domain;
internal/identity/senderauth).
13. Admin troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 STEP_UP_REQUIRED | Passkey step-up is stale for a sensitive action (tenant export/delete, estate killswitch, AI promote/retire). | Re-authenticate with your passkey when prompted, then retry (§1.5). |
403 ADMIN_REQUIRED | You hold operator/viewer, not admin. | Have an admin perform it, or get your role elevated (§4.2). |
403 MANAGE_REQUIRED | You are viewer/auditor (read-only) on a write action. | Use an operator/admin account (§1.3). |
403 SCOPE_ESCALATION (token mint) | Trying to mint a role/scope you don't hold. | Mint with a role ≤ your own (§2.5). |
403 SCOPE_DENIED (service token) | Token's scopes don't include this route's entitlement. | Rotate/mint a token whose scopes include the needed key (§2.5). |
402 LICENSE_LIMIT_EXCEEDED | Seat/token cap reached, or capability not licensed. | Free seats/tokens (§5.3), or update the license bundle (§5.2). |
Whole nav group missing / 403 on a capability | That capability isn't in your license. | Check GET /.well-known/api-capabilities; obtain the entitlement. |
403 TENANT_SUSPENDED | The tenant is suspended. | Resume it (§3.3) — /resume works even while suspended. |
422 CONFIRM_REQUIRED (tenant delete) | confirm_slug didn't match the slug. | Provide the exact slug (§3.5) — this guard protects you. |
422 RETENTION_FLOOR | Cold retention set below a regulatory floor. | Raise cold_seconds to ≥ the class floor (§9.4). |
422 SELF_LOCKOUT (IP allowlist) | Your current IP isn't in the new list. | Include your IP/CIDR, or PUT {"cidrs":[]} (§4.7). |
451 GEO_FENCED | Request from an OFAC-sanctioned country, or blocked by your geo-fence. | Sanctions floor cannot be lifted; adjust your own fence if it's yours (§6.8). |
403 BREAK_GLASS_QUORUM | Fewer than threshold distinct valid codes presented. | Gather the quorum from distinct custodians (§10.1.2). |
409 ALREADY_PROVISIONED (HYOK) | An active KEK already exists. | Revoke first (irreversible!), then provision (§6.5). |
403 KEY_REVOKED on decrypt | The HYOK key was revoked — data is intentionally undecryptable. | This is by design; provision a new key for new data (§6.5). |
| Locked out — IdP down, no one can log in | SSO/MFA path failure. | Activate break-glass POST /admin/break-glass (public, works when IdP is down) (§10.1.2). |
GET /readyz failing | DB unreachable or migrations pending. | Check DB connectivity and migration state; see GET /healthz/deep. |
| Webhook/notification not arriving | Delivery in DLQ (dead after 6 attempts). | Inspect GET /v1/webhooks/deliveries?status=dead, fix endpoint, replay (§9.7–9.8). |
| Logs not reaching SIEM | Collector down; batches dead-lettered. | GET /v1/observability/forward-queue; fix collector; replay (§9.5). |
502 KMS_UNAVAILABLE | HYOK KMS provider unreachable. | Restore KMS connectivity; retry provision (§6.5). |
| Audit chain verify fails | Possible tampering. | Treat as SEV incident; follow first-24h.md + cross-tenant-audit.md. |
14. Complete admin operation reference
Every route registered by the binary (codes/ci/entitlements.yaml), grouped by domain. Auth column: public = un-gated; otherwise the value is the entitlement key that gates it. Where a handler adds a role gate, it is noted [admin] (requireAdmin) or [manage] (requireManage) or [step-up].
System, health & discovery (public)
| Method | Path | Gate | What it does |
|---|---|---|---|
| GET | /healthz, /livez, /readyz, /healthz/deep | public | Liveness / readiness / deep dependency health |
| GET | /metrics | public | Prometheus scrape (protect at network layer) |
| GET | /v1/licensing/status | public | Licensing posture |
| GET | /v1/crypto/status | public | Crypto manifest, FIPS, PQC readiness |
| GET | /openapi.yaml, /v1/openapi.json, /asyncapi.yaml | public | API/event contracts |
| GET | /.well-known/api-capabilities | public | 90-capability matrix |
| GET | /.well-known/security.txt, /openapi.json, /federation | public | RFC 9116 / contract / federation topology |
| GET | / | public | Admin console SPA + assets |
Auth & bootstrap (public / pre-auth)
| Method | Path | Gate | What it does | ||
|---|---|---|---|---|---|
| GET | /auth/login, /auth/callback | public | OIDC start / callback | ||
| POST | /auth/local/login | public | Local-account login (admin-enabled) | ||
| GET | /auth/sso | public | Email-domain home-realm discovery → per-tenant IdP | ||
| GET | /auth/saml/metadata, /auth/saml/{id}/login | public | SP metadata / start SAML | ||
| POST | /auth/saml/acs | public | SAML assertion consumer | ||
| POST | /auth/logout | public | Session teardown | ||
| POST | /auth/webauthn/register/begin\ | finish, /auth/webauthn/login/begin\ | finish | public | MFA enrolment / verification |
| POST | /admin/break-glass | public | §98 break-glass invoke (never gated) |
Tenants & control-plane shell
| Method | Path | Gate | What it does | |
|---|---|---|---|---|
| POST | /v1/tenants | control_plane.shell | Create a tenant | |
| GET | /v1/tenants/{id} | control_plane.shell | Get a tenant | |
| POST | /v1/tenants/{id}/suspend | control_plane.shell [admin] | Suspend | |
| POST | /v1/tenants/{id}/resume | control_plane.shell [admin] | Resume | |
| POST | /v1/tenants/{id}/export | control_plane.shell [manage][step-up] | Export (portability) | |
| DELETE | /v1/tenants/{id} | control_plane.shell [admin][step-up] | Delete + crypto-erase (irreversible) | |
| GET | /v1/me | control_plane.shell | Who am I | |
| GET | /v1/_audit/verify, /v1/_audit/activity | control_plane.shell | Audit chain verify / activity | |
| POST | /admin/break-glass/setup | control_plane.shell [admin, bootstrap tenant] | Provision break-glass codes | |
| GET | /v1/dualcontrol | control_plane.shell [admin] | List pending dual-control | |
| POST | /v1/dualcontrol/{id}/approve\ | reject | control_plane.shell [admin] | Four-eyes approve/reject |
Identity, IAM & RBAC
| Method | Path | Gate | What it does | |
|---|---|---|---|---|
| GET/PUT | /v1/iam/auth-settings | control_plane.shell | Read/set auth policy (mfa, local/oidc) | |
| GET/POST | /v1/iam/local-users | control_plane.shell | List/create local users | |
| DELETE | /v1/iam/local-users/{username} | control_plane.shell | Delete user (kills sessions) | |
| POST | /v1/iam/local-users/{username}/password | control_plane.shell | Reset password | |
| POST | /v1/iam/totp/enroll\ | verify | control_plane.shell | TOTP enrol/verify |
| GET/PUT | /v1/iam/external-backends | control_plane.shell | AD/LDAP/RADIUS backends (replace-all) | |
| GET/PUT | /v1/iam/role-mappings | control_plane.shell | Group/domain→role mappings | |
| GET/POST | /v1/iam/idps | control_plane.shell | List/register OIDC IdP | |
| DELETE | /v1/iam/idps/{id} | control_plane.shell | Remove OIDC IdP | |
| GET/POST | /v1/iam/saml-idps | control_plane.shell | List/register SAML IdP | |
| DELETE | /v1/iam/saml-idps/{id} | control_plane.shell | Remove SAML IdP | |
| POST/GET | /v1/iam/service-tokens | control_plane.shell [admin] | Mint/list service tokens | |
| POST | /v1/iam/service-tokens/{id}/rotate | control_plane.shell [admin] | Rotate token | |
| DELETE | /v1/iam/service-tokens/{id} | control_plane.shell [admin] | Revoke token | |
| GET/PUT | /v1/iam/ip-allowlist | control_plane.shell ([manage] on PUT) | Tenant IP fence | |
| GET | /v1/iam/sessions | control_plane.shell | List sessions | |
| DELETE | /v1/iam/sessions/{id} | control_plane.shell | Revoke a session | |
| POST | /v1/iam/sessions/revoke-others | control_plane.shell | Revoke my other sessions | |
| GET/PUT | /v1/iam/rbac/settings | control_plane.shell ([manage] on PUT) | Fine-grained RBAC toggle | |
| GET | /v1/iam/rbac/grants, /v1/iam/rbac/effective | control_plane.shell | Inspect grants / self | |
| PUT/DELETE | /v1/iam/rbac/roles/{role} | control_plane.shell [manage] | Set/reset role permissions | |
| GET/POST | /v1/rbac/roles | control_plane.shell [admin] | Coarse role catalog | |
| DELETE | /v1/rbac/roles/{name} | control_plane.shell [admin] | Delete coarse role | |
| POST/GET/GET/PATCH/DELETE | /scim/v2/Users[/{id}] | control_plane.shell (bearer) | SCIM user provisioning | |
| POST/GET/PATCH | /scim/v2/Groups[/{id}] | control_plane.shell (bearer) | SCIM groups | |
| GET | /scim/v2/ServiceProviderConfig, /ResourceTypes, /Schemas | control_plane.shell | SCIM discovery | |
| POST | /v1/org/invite, /v1/org/children | control_plane.shell | Org invite / child orgs | |
| GET | /v1/org/children, /v1/org/rollup, /v1/org/parent | control_plane.shell | Org hierarchy | |
| DELETE | /v1/org/children/{id} | control_plane.shell | Remove child org |
Policy & governance
| Method | Path | Gate | What it does |
|---|---|---|---|
| POST | /v1/policy/simulate | control_plane.shell | Simulate a decision |
| GET/POST | /v1/policies | control_plane.shell | List / draft policy |
| POST | /v1/policies/{id}/approve | control_plane.shell | Publish (second approver) |
| GET/GET/POST/POST/PATCH/DELETE/POST | /v1/policy/firewall[...] | control_plane.estate_policy | Host firewall policy CRUD + import/live/duplicate |
| GET/PUT | /v1/geo-fence | control_plane.shell ([admin] on PUT) | Geo-fence (OFAC floor always on) |
| GET/POST/DELETE | /v1/dlp/rules[/{id}] | control_plane.dlp | DLP rules |
| POST | /v1/dlp/scan | control_plane.dlp | Scan content |
| GET | /v1/dlp/egress-events | control_plane.dlp | Egress event log |
| POST/GET | /v1/privacy/consent | control_plane.shell | Consent receipts (GPC-aware) |
| GET | /v1/privacy/dsar/export | control_plane.shell | DSAR export |
| POST | /v1/privacy/dsar/erase | control_plane.shell | DSAR erase (irreversible) |
| GET | /v1/privacy/dsar/requests | control_plane.shell | DSAR register |
Audit, auditor & lockbox/HYOK
| Method | Path | Gate | What it does | |||
|---|---|---|---|---|---|---|
| GET | /v1/audit/evidence-locker[/export] | platform.customer_auditor_role | Evidence locker (role+grant) | |||
| POST/GET | /v1/audit/auditor-grants | control_plane.shell [admin] | Grant/list auditor time-box | |||
| DELETE | /v1/audit/auditor-grants/{subject} | control_plane.shell [admin] | Revoke grant | |||
| POST/PATCH/POST | /v1/status/incidents, /components, /maintenance | control_plane.shell | Status-page writes | |||
| GET/POST | /v1/lockbox/requests | platform.customer_lockbox | Lockbox requests | |||
| POST | /v1/lockbox/requests/{id}/decision | platform.customer_lockbox | Approve/deny access | |||
| GET | /v1/lockbox/hyok/status | platform.hyok | HYOK key status | |||
| POST | /v1/lockbox/hyok/provision\ | encrypt\ | decrypt\ | revoke | platform.hyok | HYOK key lifecycle (revoke irreversible) |
| GET | /v1/lockbox/byok | platform.byok_envelope | BYOK envelope posture | |||
| GET | /v1/metering/rollup, /v1/metering/events | control_plane.shell | Metering | |||
| POST | /v1/metering/export | platform.customer_auditor_role | Export metering |
Observability & platform ops
| Method | Path | Gate | What it does |
|---|---|---|---|
| GET | /v1/logs/export | control_plane.shell | Export tenant logs |
| POST/GET | /v1/logs/ingest, /search, /flows, /topology, /seed | control_plane.shell | Log ingest/search/flows/topology |
| GET/POST/DELETE | /v1/logs/collectors[/{id}] | control_plane.shell ([manage] on POST/DELETE) | Log collectors |
| GET/POST/DELETE | /v1/logs/investigations[/{id}] | control_plane.shell | Investigations |
| GET/POST | /v1/observability/forward-queue[/replay] | control_plane.shell | Forward-queue DLQ + replay |
| GET/PUT | /v1/observability/retention | control_plane.shell | Retention config (regulatory floors) |
| GET/PUT | /v1/admin/log-level | control_plane.shell ([manage] on PUT) | Runtime log level |
| GET/POST | /v1/events/stream, /v1/events/emit | control_plane.shell | Event stream / emit |
| GET/POST | /v1/collab/{doc}[...] | control_plane.shell | Collaborative docs |
| GET/POST/POST/DELETE/GET/POST | /v1/notifications/channels[...] | control_plane.shell ([manage] on writes) | Alert channels + deliveries + replay |
| POST/GET/POST/GET/POST | /v1/webhooks[...] | control_plane.shell ([manage] on writes) | Webhooks + DLQ + replay |
| GET | /v1/sre/dashboards | platform.sre_dashboards | SRE dashboard specs |
| GET | /v1/analytics/datasets; POST /v1/analytics/query | control_plane.shell | Analytics |
| POST | /v1/mcp | control_plane.shell | MCP endpoint |
| POST/GET/DELETE/POST/POST | /v1/itsm/connectors[...] | control_plane.shell | ITSM connectors + test/push |
Estate management
| Method | Path | Gate | What it does | |
|---|---|---|---|---|
| POST/GET | /v1/estate/targets | control_plane.estate_inventory | Register/list targets | |
| GET | /v1/estate/capabilities, /topology, /config-applied | control_plane.estate_inventory | Estate maps | |
| GET/POST | /v1/estate/host-keys[/reset] | control_plane.estate_inventory | Host-key pins | |
| GET/POST | /v1/estate/agents[/enroll] | control_plane.estate_inventory | Agents | |
| POST/GET/DELETE | /v1/estate/credentials[/{id}] | control_plane.estate_pam | PAM credentials | |
| GET | /v1/estate/credential-providers, /pam-connectors | control_plane.estate_pam | Providers/connectors | |
| POST/DELETE | /v1/estate/pam-connectors[/{id}] | control_plane.estate_pam | PAM connectors | |
| POST/POST/GET | /v1/estate/sessions[/{id}/exec,/recording] | control_plane.session_broker | Brokered recorded sessions | |
| POST | /v1/estate/breakglass/request | control_plane.session_broker | Estate break-glass request | |
| POST/GET | /v1/estate/breakglass/{id}/approve, /v1/estate/breakglass | control_plane.session_broker | Approve/list | |
| POST | /v1/estate/packs/plan\ | apply | control_plane.estate_policy | Policy-pack rollout |
| POST | /v1/estate/fanout | control_plane.estate_response | Fan-out action | |
| POST | /v1/estate/killswitch | control_plane.estate_response [step-up] | Trip estate kill-switch | |
| POST | /v1/estate/killswitch/reset | control_plane.estate_response [step-up][admin]→dual-control | Reset (opens 4-eyes) | |
| POST/GET/GET/GET/POST | /v1/ot/assets, /zones, /safety-posture, /simulate-seed | control_plane.ot_visibility / ot_readonly | OT/ICS (read-only) | |
| POST/GET/GET/POST/DELETE | /v1/fleets[...] | control_plane.fleet_mgmt | Fleets + members | |
| POST/GET/DELETE/POST | /v1/ztp/tokens[/{id}], /redeem | control_plane.ztp | Zero-touch provisioning | |
| GET/GET/PUT | /v1/sovereignty/regions, /topology, /residency | platform.sovereign_topology_api | Data residency |
Security operations
| Method | Path | Gate | What it does | |
|---|---|---|---|---|
| POST/POST/POST/GET/GET | /v1/soc/agents/investigate, /execute, /killswitch[/reset], /scorecards, /model | control_plane.agentic_soc | Agentic SOC + kill-switch | |
| GET/GET/GET/POST/POST/POST/POST/POST/GET | /v1/ctem/exposures, /scope, /vectors, /assess, /attack-path, /validate, /cve-feed[/fetch], /assessments | control_plane.exposure_mgmt / attack_path / validation_bas | CTEM | |
| POST/GET/GET | /v1/detections[/coverage] | control_plane.detection_hub / attack_coverage_map | Detections | |
| POST×6/GET×2/DELETE | /v1/detect/rules[...], /findings[/{id}/escalate] | control_plane.detection_hub | Detection rules + findings | |
| POST/POST | /v1/lake/ingest, /query | control_plane.security_data_lake / federated_search | Data lake | |
| POST/GET/POST/POST/GET | /v1/intel/indicators[/{id}/suppress\ | unsuppress], /collections/{id}/objects | control_plane.threat_intel | Threat intel (TIP) |
| GET/POST/GET/PATCH + notes/transition/assign/priority/escalate | /v1/soar/cases[...] | control_plane.case_management | Cases | |
| POST | /v1/soar/playbooks/{id}/run | control_plane.soar | Run playbook | |
| POST | /v1/soar/cacao/export\ | import | control_plane.soar | CACAO |
| GET/POST/GET/GET | /v1/ueba/entities[/{entity}], /scan, /anomalies | control_plane.ueba | UEBA | |
| GET/POST/GET | /v1/itdr/signals, /scan, /detections | control_plane.itdr | ITDR | |
| POST/GET/GET | /v1/correlation/run, /live, /incidents | control_plane.correlation | Correlation | |
| GET/POST/GET | /v1/posture/score, /snapshot, /history | control_plane.posture_score | Posture score | |
| POST/GET/DELETE/GET | /v1/risk/scenarios[/{id}], /portfolio | control_plane.risk_quant | FAIR risk | |
| GET/GET | /v1/siem/overview, /alerts | control_plane.siem_hub | SIEM hub | |
| GET/POST/GET | /v1/aiops/health, /scan, /anomalies | control_plane.aiops | AIOps | |
| POST/GET/DELETE/POST/GET | /v1/deception/honeytokens[/{id}], /trip, /trips | control_plane.deception | Deception | |
| POST/GET/GET | /v1/detonation/submit, /jobs, /provider | control_plane.detonation | Detonation sandbox | |
| POST/GET/POST/GET/POST | /v1/drp/assets, /ingest, /findings, /simulate | control_plane.drp | Digital risk | |
| POST/GET/POST/GET | /v1/config-drift/baselines, /check, /drifts | control_plane.config_drift | Config drift | |
| POST/GET/DELETE/POST | /v1/telemetry/routes[/{id}], /evaluate | control_plane.telemetry_fabric | Telemetry fabric | |
| GET/POST/POST/GET | /v1/collective/status, /optin, /iocs | control_plane.collective_defense | Collective defense | |
| POST/POST/GET/POST | /v1/policy-twin/simulate, /suites[/{id}/run] | control_plane.policy_twin | Policy twin | |
| POST/POST/GET/POST/POST | /v1/iga/sod/check, /certify, /campaigns, /leaver, /joiner | control_plane.sod / access_certification / iga | IGA |
Licensing, releases, suite/MSP & self-serve
| Method | Path | Gate | What it does |
|---|---|---|---|
| GET | /v1/licensing/airgap, /online, /mtls-profile | platform.airgap_licensing / online_licensing / mtls_license_profile | Licensing posture |
| POST | /v1/licensing/continuity/activate | platform.continuity_escrow [admin, dual-control] | Continuity activate (irreversible) |
| GET | /v1/licensing/continuity | public | Continuity posture |
| GET/POST/GET/POST | /v1/updates/check, /status, /apply | control_plane.shell | Update orchestration |
| GET/PUT/GET/PUT | /v1/release/channels[/{name}], /assignment | platform.release_channels ([admin] on PUT) | Release channels (stable/beta/edge) |
| GET | /v1/release/parity, /pack | platform.self_hosted_release_parity / signed_release_pack | Parity / supply-chain pack |
| POST | /v1/release/verify | platform.signed_release_verification | Verify a signed release |
| GET | /v1/contracts[/{name}], /v1/federation/openapi | platform.contract_registry / federation_openapi | Contract registry |
| POST/GET/GET | /v1/migrate/import, /jobs, /sources | control_plane.competitor_migration | Competitor migration |
| POST/GET/DELETE/GET | /v1/msp/managed[/{id}], /soc | control_plane.msp / comanaged_soc ([admin] on writes) | MSP / co-managed SOC |
| POST/GET/GET/POST/DELETE/GET/GET | /v1/federation/children[...], /health, /posture | suite.federation_core | Parent-suite federation |
| GET/PUT/DELETE/POST/GET | /v1/suite/routes, /airgap-bundles | suite.cross_suite_routing / airgap_bundle_distribution | Cross-suite routing / air-gap bundles |
| GET | /v1/suite/otel, /licensing-client, /audit-pipeline, /shell-config | suite.shared_* / admin_shell_ui | Shared suite services |
| POST/GET/POST | /v1/modules[/register,/{name}/reload] | platform.module_hot_reload | Module hot-reload |
| POST/POST/GET | /v1/data-ops/export-snapshot, /purge, /jobs | platform.self_service_data_ops | Self-service data ops |
| POST/GET/POST | /v1/ai/models[/{id}/promote,/retire] | control_plane.ai_spm ([step-up] on promote/retire) | AI model registry |
| GET/POST | /v1/sla/reports[/generate] | platform.sla_reporting | SLA reports |
| GET | /v1/trust/portal/private | platform.trust_portal_automation | Private trust portal |
| GET/GET/GET/GET/POST | /v1/trust/portal, /marketplace/catalog, /roadmap, /status[/incidents], /status/subscriptions | public | Self-serve discovery |
Appendix A — irreversible actions (no undo)
Read twice before running any of these:
DELETE /v1/tenants/{id}— crypto-erases the HYOK KEK; encrypted data incl. backups
unrecoverable (§3.5).
POST /v1/lockbox/hyok/revoke— revoked data stays undecryptable forever (§6.5).POST /v1/privacy/dsar/erase— user + credentials deleted (§6.6).POST /v1/licensing/continuity/activate— append-only, one-way (safe: preservation-only) (§10.4).POST /v1/ai/models/{id}/retire— no un-retire (§7.5, model lifecycle).
Appendix B — the safe alternatives to irreversible actions
- Instead of delete → suspend (§3.2) or export first (§3.4).
- Instead of HYOK revoke → deny future lockbox requests (§6.4) and rotate credentials.
- Before any policy change → simulate (§6.1) and policy-twin regression (§7.10).
- Before retention changes → confirm the floor (§9.4).
End of Administrator Guide. Runbooks referenced live in codes/runbooks/; the machine-readable route contract is codes/ci/entitlements.yaml; the capability matrix is codes/docs/capability-matrix.md.