Skip to content

Authorization Model

Pick exactly one credential pattern per call path.

Use caseCredentialWhere to store itNotes
User-driven Core admin UIBearer access token from /api/v1/auth/loginEncrypted server-side session or secure backend token store.The User must have an active AdminGrant.
Backend authorization checks for application trafficScoped API keySecret manager, environment injection, or workload identity secret.Must include authz:check; API key calls must provide actor.
Backend management automationScoped API keySecret manager.Grant only the required permission keys and scope.
One-off bootstrap or local demoPassword loginLocal dev only.SDKs retain password login, but it should not be the routine backend pattern.
Browser/mobile direct useNone for API keysNever store API keys client-side.Browser clients should talk to your backend, not directly to management APIs.

The canonical HTTP request is:

{
"actor": {
"user_id": "user_alice",
"member_id": "member_finance_reviewer",
"user_member_id": "um_alice_finance_reviewer",
"space_id": "space_acme"
},
"resource_type": "invoice",
"resource_id": "invoice_001",
"action": "approve"
}

Required fields:

FieldRequired whenMeaning
actor.user_idAlways for API key calls. Optional for Bearer session calls.Real login/audit user.
actor.member_idAlways for API key calls. Optional for Bearer session calls.Business actor in the Space.
actor.user_member_idAlways for API key calls. Optional for Bearer session calls.Active binding proving the User may act as the Member.
actor.space_idAlways for API key calls. Optional for Bearer session calls.Tenant boundary.
resource_typeAlways, unless you use resource.type.Registered resource type key.
resource_idAlways, unless you use resource.id.Core resource id.
actionAlways.Action key registered under the resource type.

Bearer session calls may omit actor; Core uses the session’s active actor chosen during login or by POST /api/v1/actor/switch-member.

API key calls must include actor; Core cannot infer a human actor from a machine key.

HTTP authz requests must not include body request_id, ip, or user_agent. Core derives the canonical request id, IP, and user agent from middleware and the HTTP request.

Core allows a request only when all of these are true:

  1. The User exists and is active.
  2. The Space exists and is active.
  3. The Member exists, is active, and belongs to the same Space.
  4. The UserMember exists, is active, belongs to the same User, Member, and Space, and is not expired or revoked.
  5. The resource type and action are registered and active.
  6. The target resource exists and is active.
  7. The target resource belongs to the same Space as the actor.
  8. At least one active role grant gives the Member the requested permission.
  9. The permission scope covers the target resource.

The important deny codes for application developers are:

CodeTypical causeWhat to fix
ACTOR_NOT_FOUNDActor ids do not resolve.Check User, Member, UserMember, and Space ids.
USER_INACTIVEUser is disabled or deleted.Restore or use another User.
USER_MEMBER_REVOKEDBinding was revoked, disabled, or expired.Create or restore an active UserMember binding.
SPACE_MISMATCHActor and target resource are in different Spaces.Do not cross tenant boundaries.
RESOURCE_NOT_FOUNDResource row is missing or inactive.Register or restore the resource.
NO_MATCHING_PERMISSIONNo active Role/Permission candidate matches resource and action.Grant a Role with the required Permission.
SCOPE_OUT_OF_BOUNDSRole exists but its scope anchor does not cover the target.Move the resource, adjust group anchor, or add a narrower grant.
GLOBAL_SCOPE_DISABLEDglobal scope is disabled in v1.0.Use space, group_tree, or self.
Permission scopeCoversRequires
spaceAny target resource in the actor Space.Actor and resource must share space_id.
group_treeTarget group equals anchor group or is a descendant.MemberRole.scope_anchor_group_id and target group_id.
selfTarget resource owner equals actor Member.Resource mapping must expose owner_member_id.
globalDisabled in v1.0.Do not use for production permissions.

For group_tree, the rule is exact path match or safe dot-prefix:

target_path = anchor_path OR target_path starts with anchor_path + "."

That means finance covers finance.apac, but does not cover financeops.