Skip to content

Governance templates API

A governance template is a per-tenant, versioned rule template: a stable template_key + template_version, a display name, an opaque obligation_refs payload (which obligations it references), and a target_controls payload that maps each obligation onto an existing gateway configuration control. This API lets an authorized admin catalogue templates and preview / apply a template's controls against a gateway.

The apply engine always respects the tenant residency floor: a template may tighten a residency/PII control but can never downgrade it below the tenant's armed floor. A control the floor refuses is reported as blocked_by_floor and is never persisted.

Base URL: https://<your-gateway-host>/admin/v1

Required role for every endpoint: admin, tenant_admin, or ki_manager. A caller with any lower role (or none) is rejected 403/401. The client is never the authorization boundary — every call is re-gated and tenant-scoped server-side.

Tenant scope

Tenant scope is decided server-side; the client value never widens it.

Caller Scope
admin Must target one tenant with ?tenant_id=<id> on the per-tenant routes.
tenant_admin / ki_manager Always their own tenant. A ?tenant_id naming another tenant is ignored (pinned to the caller's own).

The template routes are inherently per-tenant: an admin who omits ?tenant_id is rejected 400 { "error": "tenant_id required" }. A non-admin whose account carries no tenant is refused 403.

The diff / apply routes take a gateway_id in the body instead of ?tenant_id: the caller must have access to that gateway (its tenant must match the caller's, unless admin), and the template is looked up scoped to the gateway's tenant — so a cross-tenant template reads as 404.

Authoritative flag

is_authoritative is not client-settable. Every template created through this API is stored non-authoritative (0); authoritative (DPO-signed) content is provisioned out of band. An existing authoritative template is read-only to this API: PATCH and DELETE against it return 409. The management UI marks every non-authoritative template with a watermark so it is never presented as a compliance guarantee.


GET /governance/leaves

The catalogue of governable configuration leaves a template's target_controls may reference. Returns a JSON array (sorted by leaf) of { "leaf", "scope", "kind" }; scope is gateway (overlaid on the gateway, subject to the floor) or tenant (derived from the tenant — a gateway apply cannot change it). This is the single source of truth for the leaf catalogue; clients must not hardcode it.

[
  { "leaf": "eu_region_routing",           "scope": "gateway", "kind": "boolean" },
  { "leaf": "pii_masking_enforced",        "scope": "tenant",  "kind": "boolean" },
  { "leaf": "provider_allowlist_enforced", "scope": "gateway", "kind": "boolean" }
]

GET /governance/templates

List the tenant's live templates. Returns a JSON array; each row's obligation_refs and target_controls are decoded from storage (a null column yields null / [] respectively — never an object that would break a client .map).

POST /governance/templates

Create a template. Body (JSON object):

Field Accepted Rejected → 400
template_key string, 1–190 chars (required) missing / empty / non-string / > 190
name string, 1–255 chars (required) missing / empty / non-string / > 255
template_version integer 1 … 2147483647 (default 1) present-but-non-numeric / < 1 / > 2147483647
target_controls JSON array of { "leaf": <governable leaf>, "value": <boolean>, "obligation"?: <string> }; absent → stored [] unknown leaf / non-boolean value / duplicate leaf / not an array
obligation_refs JSON array or object (opaque; never interpreted) a scalar/string
is_authoritative ignored — always stored 0 —

A duplicate (tenant, template_key, template_version) returns 409 { "error": "a template with this key and version already exists" }. On success returns 201 with the created row.

PATCH /governance/templates/{id}

Update a live template's mutable fields (name, obligation_refs, target_controls), tenant-scoped. Only fields present in the body are changed — an absent field is preserved (a partial PATCH never clears stored controls). Present fields use the same validation as create. is_authoritative is ignored. An unknown / cross-tenant / soft-deleted id → 404; an authoritative template → 409 (read-only).

DELETE /governance/templates/{id}

Soft-delete a live template, tenant-scoped. Unknown / cross-tenant / already-deleted → 404; an authoritative template → 409 (read-only). On success returns 200 { "deleted": true }.

POST /governance/templates/{id}/diff

Preview what applying the template would change on a gateway. No write. Body: { "gateway_id": "<id>" } (required, string — else 400). The caller must have access to the gateway (403/404/503 otherwise). Returns:

{
  "gateway_id": "…",
  "tenant_id": "…",
  "changes": [
    { "leaf": "eu_region_routing", "scope": "gateway",
      "current": true, "requested": false, "resulting": true, "status": "blocked_by_floor" }
  ],
  "warnings": []
}

Each changes[] entry also carries an obligation field — the governance obligation the leaf maps to — omitted from the example above.

Per-control status:

Status Meaning
apply A gateway leaf whose effective value will change (tightening).
unchanged Already at the requested value / a no-op.
blocked_by_floor The tenant residency floor refuses this downgrade — not persisted.
tenant_scoped A PII leaf a gateway apply cannot change (set it on the tenant) — not persisted.

warnings carries a human-readable note when the apply would introduce a deny-all condition (enforcing a provider allowlist with no usable providers).

POST /governance/templates/{id}/apply

Persist the template's controls onto the gateway, floor-safe. Same body + gateway-access rules as diff. Only apply-status controls are written; blocked_by_floor / tenant_scoped / unchanged controls are skipped. Returns the same shape as diff (the applied result).

The apply refuses to introduce a deny-all condition: 409 { "error": "applying this template would deny ALL inference on the gateway (enable a usable provider allowlist first)" }, with no write. A stored template whose target_controls is malformed (e.g. a legacy row) surfaces as 422.

Error codes

Status Cause
400 malformed body / failed field validation / missing gateway_id / admin omitted ?tenant_id.
401 unauthenticated.
403 role below ki_manager, or a non-admin without a tenant, or a gateway in another tenant.
404 unknown / cross-tenant / soft-deleted template or gateway.
409 duplicate (tenant, key, version); PATCH/DELETE of an authoritative template; an apply that would deny all inference.
422 a stored template with an invalid target_controls payload.
503 transient gateway lookup failure (retryable).