Skip to Content
Elido is in closed beta — APIs are stable but rate-limits and quotas may change before GA. Request access →
GuidesCustom roles (RBAC)

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:

NamespaceActions
linkscreate, read, list, update, delete, bulk-import
domainsclaim, verify, delete, read
qrscreate, update, read, delete
analyticsread, export, query-clickhouse
membersinvite, remove, change-role
billingread, update-plan, download-invoice
auditread, export
api-keysissue, 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.cedar

The bundle is plain text and lives well in git — diff before release.

6. Edge cases

  • Empty allow set — a role with no permit statements grants nothing. Built-in read-only is implemented this way (a single permit for *.read actions 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 of forbids. 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