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
| Endpoint | Needs | |
|---|---|---|
GET /me | a subject | The caller's grants (Summary), for UIs. |
POST /check | a subject | Batched decisions for UIs: { checks: [{ permission, id? }] }, at most 100. |
POST /explain | a subject; access.decision:explain for another user | { permission, id?, userId? } to an Explanation. |
GET /permissions | access.role:read, access.assignment:manage or access.group_mapping:manage | The permission catalog and scope types. |
GET /roles, GET /roles/{id} | as above | Roles, with usage (active assignments and group mappings). |
POST /roles | access.role:manage | Create a custom role: { key, name, description?, permissions? }. |
PATCH /roles/{id} | access.role:manage | Rename or describe a custom role. |
PUT /roles/{id}/permissions | access.role:manage | Replace a custom role's permissions. |
DELETE /roles/{id} | access.role:manage | Delete a custom role nobody holds. |
GET /assignments | access.assignment:read | Newest first; userId, roleId, includeRevoked, q, limit, cursor. |
POST /assignments | access.assignment:manage | { userId, roleId, scopeType, scopeIds?, validFrom?, validUntil?, reason? }. |
POST /assignments/preview | access.assignment:manage | The same body; runs every rule without writing. |
POST /assignments/{id}/revoke | access.assignment:manage | { reason? }. |
GET /group-mappings | access.group_mapping:manage | |
POST /group-mappings | access.group_mapping:manage | { idp, groupName, roleId, scopeType, scopeIds? }. |
DELETE /group-mappings/{id} | access.group_mapping:manage | |
GET /changes | access.change_log:read | The 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 /assignmentspages withlimitandcursor: pass the previous page'snextCursor.GET /changespages withlimitandbefore: pass the previous page'snextBefore.kindtakes a kind (role.created) or a family (assignment);severityisnormalorhigh;targetis 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.