zynactradocs

HTTP API

The decision and admin API both SDKs serve, usually under /access, for the React client, the admin screens and scripts.

Both SDKs serve the same API: accesshttp.Handler in Go, the controllers AccessModule registers in Nest. Mount it under a prefix, usually /access. The contract is spec/openapi.yaml; this page summarises it.

Authentication is the product's: the API reads the subject your auth middleware put on the request, and answers 401 without one. Every admin operation checks the caller's own access.* permissions and the admin rules.

Endpoints

EndpointNeeds
GET /mea subjectThe caller's grants (Summary), for UIs.
POST /checka subjectBatched decisions for UIs: { checks: [{ permission, id? }] }, at most 100.
POST /explaina subject; access.decision:explain for another user{ permission, id?, userId? } to an Explanation.
GET /permissionsaccess.role:read, access.assignment:manage or access.group_mapping:manageThe permission catalog and scope types.
GET /roles, GET /roles/{id}as aboveRoles, with usage (active assignments and group mappings).
POST /rolesaccess.role:manageCreate a custom role: { key, name, description?, permissions? }.
PATCH /roles/{id}access.role:manageRename or describe a custom role.
PUT /roles/{id}/permissionsaccess.role:manageReplace a custom role's permissions.
DELETE /roles/{id}access.role:manageDelete a custom role nobody holds.
GET /assignmentsaccess.assignment:readNewest first; userId, roleId, includeRevoked, q, limit, cursor.
POST /assignmentsaccess.assignment:manage{ userId, roleId, scopeType, scopeIds?, validFrom?, validUntil?, reason? }.
POST /assignments/previewaccess.assignment:manageThe same body; runs every rule without writing.
POST /assignments/{id}/revokeaccess.assignment:manage{ reason? }.
GET /group-mappingsaccess.group_mapping:manage
POST /group-mappingsaccess.group_mapping:manage{ idp, groupName, roleId, scopeType, scopeIds? }.
DELETE /group-mappings/{id}access.group_mapping:manage
GET /changesaccess.change_log:readThe change log: kind, actor, target, severity, limit, before.

/check and /explain take IDs

An item with id is decided against that record, which the server loads with the loader registered for the permission's kind. The UI is never trusted with attributes. IDs that don't exist come back allowed: false; a kind without a loader is refused with 400. An item without id asks whether the caller holds the permission at any scope.

POST /access/check
{ "checks": [{ "permission": "leave.request:approve", "id": "lr_1" }, { "permission": "leave.request:create" }] }

200
{ "results": [{ "permission": "leave.request:approve", "id": "lr_1", "allowed": true }, { "permission": "leave.request:create", "allowed": true }] }

Previewing an assignment

POST /assignments/preview answers with what the assignment would grant, or the problem it would fail with:

{
  "roleKey": "line_manager",
  "severity": "normal",
  "reasonRequired": false,
  "validUntilRequired": false,
  "grants": [{ "permission": "leave.request:approve", "scope": "team" }]
}

maxDurationSeconds is set for break-glass roles. Call it as an assignment form changes, so escalation, self-assignment and missing reasons show at the field before anyone submits.

Paging

  • GET /assignments pages with limit and cursor: pass the previous page's nextCursor.
  • GET /changes pages with limit and before: pass the previous page's nextBefore. kind takes a kind (role.created) or a family (assignment); severity is normal or high; target is a role, assignment or group mapping ID.

Problems

Errors are RFC 9457 problem documents (application/problem+json) with a stable code:

{
  "type": "https://zynactra.dev/problems/access/escalation",
  "title": "Not allowed to grant this",
  "status": 403,
  "code": "escalation",
  "detail": "you cannot grant leave.request:approve with scope global: you can only grant what you hold yourself, at the same or a wider reach",
  "permission": "leave.request:approve",
  "field": "roleId"
}

reason is set on denials, field on validation problems (the request field they're about), callId when Cerbos was involved. Codes and statuses are in Errors.

Edit on GitHub

On this page