Skip to content
AXOWLDevelopers
Quickstart

Custom actions — how an org composes its own

Custom actions are how an organization extends the Action Library with scopes for its own systems — approving a shipment, releasing a payment in an ERP, anything Axowl does not run itself.

A custom scope is written exactly like a managed one. The only difference is the namespace: the first segment is the org’s own word instead of org, view, platform or user.

shipment.approve:region=KR,value<=50000
└─ scope ──────┘ └──── constraints ────┘
PartWhat it isExample (a logistics org)
Namespacethe org’s system or domain — anything except org, view, platform, usershipment
Actionwhat is doneapprove
Constraintsthe limits the permission carriesregion=KR · value<=50000

There is no separate “define” step. A custom scope is registered the first time it is added to a role:

POST /api/org/{slug}/roles/{roleId}/permissions
Content-Type: application/json
{ "templateExpression": "shipment.approve:region=KR,value<=50000" }
  • The caller needs org.role.update (Owners and Admins have it implicitly). A caller who is not an Owner or Admin can add only a scope they hold themselves, constraints included; otherwise the call returns 403 with error: "PERMISSION_DENIED".
  • The response is { "templateId": "…" }. Remove the entry again with DELETE /api/org/{slug}/roles/{roleId}/permissions/{templateId}.
  • The first time a scope is used, it is added to the catalog and an auth.perm.created event is recorded. From then on it appears in the organization’s Action Library with origin: "custom".
  • Shape. At least two dot-separated segments: namespace.action or namespace.resource.action.
  • Characters. Lowercase letters, digits, - and _ in each segment.
  • Wildcard. * is allowed only as the last segment (shipment.*).
  • Constraints. After the colon, comma-separated entries of a key, an operator (=, !=, <, <=, >, >=) and a value. Keys and values cannot be empty. A value can be a set — region={KR,JP} — and <, <=, >, >= take a number.
  • No lifecycle keys on a role. uses, ttl and exp are refused with 400 and errorCode: "LIFECYCLE_NOT_SUPPORTED". Set an expiry on the role assignment, or grant the scope through an approval form.
  • Reserved namespaces. A scope that starts with org, view, platform or user must be an existing Library scope. Anything else in those namespaces is refused with 400 and errorCode: "SCOPE_NOT_IN_LIBRARY" — a made-up org.* scope would be saved but never open anything.

On a role. Members who hold the role hold the scope, with its constraints.

On a form. A custom scope can be the target of an approval form, like a managed one. The request is then approved along the approval line, under the same ceiling: an approver can only approve what they themselves hold. The form’s risk floor follows the scope’s last segment, as it does for managed scopes: shipment.delete starts at 3, shipment.approve at 2. A grant made this way can carry the lifecycle controls ttl / exp; a finite uses is not available on a custom scope and such a request is refused at submission (see Lifecycle control variables).

From the org’s system. Before performing the action, the org’s system asks Axowl whether it is within the rules:

POST /api/org/{slug}/permissions/check
Content-Type: application/json
{
"scope": "shipment.approve",
"userId": "u-1042",
"context": { "region": "KR", "value": 15000 }
}
{
"allow": true,
"reason": "matched",
"evaluationId": "0b0f…",
"permissionId": "5c1e…",
"ruleExpression": "region=kr,value<=50000"
}

How the answer is decided:

  1. The scope must be registered in this organization; otherwise allow is false with reason no_matching_permission.
  2. Every rule the organization’s roles attach to that scope is checked against context. One violated rule denies the request, and reason names it — for example value_violates(60000<=50000), mismatch_region(expected=kr,got=us), or context_missing_value when a compared key is absent. A set rule (region={kr,jp}) is satisfied by any one of its values, and with != by a value outside the set. uses, ttl and exp on a rule are not compared with context.
  3. If the scope is registered and no rule is violated, allow is true.

The call must be authenticated, and {slug} must be the organization the caller is acting in; otherwise the answer is 401 or 404. The check is evaluated for the organization as a whole: userId identifies who the action is for in the audit record and does not change the result. ruleExpression is returned to machine callers and to members who hold org.permission.read; for other callers it is null. Every check, allowed or denied, is recorded as an event whose type is the scope itself (shipment.approve), with the context and the outcome.

ManagedCustom
Defined byAxowlthe org, by adding it to a role
Namespaceorg · viewthe org’s own
Operation runs inAxowlthe org’s own system
Enforced byAxowl’s API gatesthe org’s system, by calling the check
Grammar · constraints · approval ceiling—identical

Related: Action Library · Event & system control · Capability grants · SDK overview