Custom roles
Out of the box every workspace ships five built-in roles —
owner, admin, member, read-only, and api-key. They cover
80% of the cases. The remaining 20% — agencies that want
client-isolated reporting, finance teams that need billing without
links, security teams that need audit-only — get Cedar
policies on Business+.
Cedar is the open-source policy language AWS published; we picked it because it’s text-based, version-controllable, and has a formally verified evaluator. Policies are read at request time from Postgres; the evaluator runs in-process in api-core, so there is no extra round-trip on protected endpoints.
1. Anatomy of a custom role
A custom role is a name plus an ordered list of Cedar statements.
Each statement is permit or forbid, scoped to a principal,
action, and resource set.
// "Client report viewer" — read clicks for one client only
permit (
principal,
action in [Action::"analytics.read", Action::"links.list"],
resource
)
when {
resource.workspace == "ws_acme_marketing"
};
forbid (
principal,
action == Action::"links.create",
resource
);Save the role from the dashboard or via API:
curl -X POST \
https://api.elido.app/v1/workspaces/1/roles \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Client report viewer",
"policy": "permit ( principal, action in …"
}'The API validates the policy server-side (parse + type check
against the Elido entity schema) before storing it; invalid
policies return 422 with the offending span.
2. Available actions
Action namespaces match the REST resource families:
| Namespace | Actions |
|---|---|
links | create, read, list, update, delete, bulk-import |
domains | claim, verify, delete, read |
qrs | create, update, read, delete |
analytics | read, export, query-clickhouse |
members | invite, remove, change-role |
billing | read, update-plan, download-invoice |
audit | read, export |
api-keys | issue, rotate, revoke, list |
Forbidding an action overrides any permit — Cedar’s deny-takes-
precedence rule is what makes audit-only roles safe.
3. Conditions
Cedar when and unless blocks read entity attributes. The Elido
entity schema exposes these:
{
"User": ["id", "email", "workspaces", "default_workspace"],
"Workspace": ["id", "tier", "owner", "created_at"],
"Link": [
"id", "workspace", "creator", "tags", "campaign", "created_at"
],
"Domain": ["id", "workspace", "verified", "primary"]
}Common patterns:
// Grant only on links the principal created
permit (principal, action == Action::"links.update", resource)
when { resource.creator == principal };
// Time-window restrict — read-only outside business hours
permit (principal, action == Action::"links.read", resource)
when {
context.hour >= 9 && context.hour < 18
};
// Tag-scoped — agency clients tag their links with `client:acme`
permit (principal, action in [Action::"links.read", Action::"analytics.read"], resource)
when { "client:acme" in resource.tags };context carries request metadata: hour (UTC), day_of_week,
source_ip, user_agent. Useful for guardrails on automation
tokens.
4. Assigning a custom role
Roles assign 1:1 to workspace members or API keys:
curl -X PUT \
https://api.elido.app/v1/workspaces/1/members/usr_8a2f \
-H "Authorization: Bearer $ELIDO_TOKEN" \
-d '{ "role": "Client report viewer" }'In the dashboard, the role dropdown on a member row lists built-ins plus every custom role on the workspace.
5. Cedar imports
Two utilities to manage policy at scale:
- Workspace-scoped: each workspace owns its roles. Import /
export via
GET /v1/workspaces/{id}/roles?format=cedar. - Org-scoped templates: on Enterprise, the org admin can publish template roles every workspace inherits — useful for SOC 2 controls that you want consistent across the portfolio.
# Pull every workspace role as a Cedar bundle
curl -H "Authorization: Bearer $ELIDO_TOKEN" \
"https://api.elido.app/v1/workspaces/1/roles?format=cedar" \
> roles.cedarThe bundle is plain text and lives well in git — diff before release.
6. Edge cases
- Empty allow set — a role with no
permitstatements grants nothing. Built-inread-onlyis implemented this way (a singlepermitfor*.readactions on the workspace). - Conflicting roles via SCIM groups — when SCIM maps multiple
groups onto the same user, roles compose. Effective policy is
the union of
permits minus the union offorbids. Audit log records which group each statement came from. - Policy size — Cedar evaluator caps at 500 statements per policy; if you need more, split into multiple roles and assign the same user to several.
See also
- SCIM & SSO — IdP groups → role mapping
- SOC 2 / HIPAA evidence — audit log of role assignments and policy edits
- API reference —
rolesresource schema