Authorization
RBAC with a single-call /check, group-level roles, explainable authz (the grant-path trace), and ABAC policy.
Authorization in Qeet ID is RBAC-first with a single-call check, group-level roles, an explainable mode that returns the grant-path trace, and an ABAC policy layer for rules RBAC can't express.
Permissions & roles
A permission is a string like billing:write. A role bundles permissions and
is assigned to a user or a group within a tenant. A user's effective
permissions are direct ∪ group-derived.
/v1/permissionsList permissions/v1/tenants/{tenantID}/rolesCreate a role/v1/roles/{roleID}/permissions/{permID}Add a permission to a roleAssigning roles
To a user:
/v1/users/{userID}/tenants/{tenantID}/roles/{roleID}Assign a role to a userTo a group (every member inherits the role's permissions):
/v1/tenants/{tenantID}/groups/{groupID}/roles/{roleID}Grant a role to a group/v1/tenants/{tenantID}/groups/{groupID}/rolesList a group's rolesInspect a user's combined effective permissions in a tenant:
/v1/users/{userID}/tenants/{tenantID}/permissionsEffective permissionsThe check — single call
The hot-path question "may this principal do X?" is one request:
/v1/checkAuthorize an actioncurl "https://api.qeetid.com/v1/check?user_id=$U&tenant_id=$T&permission=billing:write" \
-H "Authorization: ApiKey $QEETID_API_KEY"
# → { "allowed": true }const ok = await qeetid.can({ user: userId, tenant: tenantId, permission: "billing:write" });
// canAll(user, tenant, ["a", "b"]) → true only if all passok, _ := qeetid.Can(ctx, qeetidsdk.PermissionCheck{User: userID, Tenant: tenantID, Permission: "billing:write"})ok = qeetid.can(user=user_id, tenant=tenant_id, permission="billing:write")Group-level roles
Assigning a role to a group grants it to every member transitively — the basis for directory-driven access. SCIM keeps group membership in sync (see Enterprise → SCIM), so adding someone to a group in Okta/Entra grants their roles in Qeet ID automatically.
Explainable authz
Add ?explain=true to /check and instead of the bare { allowed }, Qeet ID returns
the full grant-path trace: every distinct grant (direct and group-derived) that
confers the permission, or — on a denial — the reason. Most CIAM platforms can't tell
you why a decision was made; Qeet ID can.
/v1/check?explain=trueAuthorize + explaincurl "https://api.qeetid.com/v1/check?user_id=$U&tenant_id=$T&permission=billing:write&explain=true" \
-H "Authorization: ApiKey $QEETID_API_KEY"{
"allowed": true,
"paths": [
{
"permission": "billing:write",
"granted_by": "role:billing-admin",
"via": "group:finance-team",
"group_id": "…",
"role_id": "…"
}
]
}{
"allowed": false,
"paths": [],
"reason": "no role grants billing:write for this user in this tenant"
}It's in the dashboard too
The admin dashboard's Access Tester renders this grant-path trace visually — paste a user, tenant, and permission and see exactly which role/group allows it.
ABAC policy
For rules RBAC can't express (e.g. attribute matching), each tenant has an ABAC policy evaluated alongside RBAC. A request must satisfy both the required permission and any matching policy.
/v1/tenants/{tenantID}/policyRead the tenant ABAC policy/v1/tenants/{tenantID}/policyUpdate the ABAC policyThe tenant auth policy (password rules, allowed login methods, breached-password rejection) is separate from this ABAC policy — see Authentication → auth policy. Fine-grained / ReBAC (Zanzibar-style) authorization is on the roadmap.