Permissions & Access Control
How Ergon decides who can do what — an AWS IAM-inspired model built on principals, roles, permission grants, and cross-service resource grants.
The RBAC Model
Ergon uses a role-based access control model inspired by AWS IAM. Every authorization decision answers a single question: does this principal have permission to perform this action on this resource?
Each request passes through three gates before it is allowed:
- Service access — can this principal reach the target service at all? A company must have the service enabled, and the principal must hold service access to it (directly, through a role, or auto-provisioned for service-owned principals).
- Connection — is this principal connected to the specific zone (the workflow, bucket, agent, or other top-level resource) it is trying to act in? For people-like principals (members, agents, API keys, teams) a connection must exist before any grant in that zone counts. Revoke the connection and every grant on that zone goes inert instantly — no grants are deleted. See Zones & Connections below.
- Permission grant — is the specific action allowed on the specific resource? The platform evaluates every grant that applies to the principal (its own grants, its roles' grants, and grants inherited through team membership) and determines whether the action is explicitly allowed.
The connection gate is what keeps the model usable at scale: holding Workflows service access does not surface you in the access list of every workflow in the company — you appear in a zone only once you are connected to it. Connection comes first and grants live downstream: the grant drawer only lists already-connected principals, and the backend rejects a grant to an unconnected one. You connect through the request/offer flows (which can carry grants in the same step), or — for something you create — the create action connects you to it automatically.
The model follows a default-deny policy. If no grant explicitly allows an action, the request is rejected. When multiple grants apply, a deny effect always overrides an allow. Access is built up from specific, scoped grants — there is no “super” role or account-wide grant that bypasses these checks.
No grant means no access. You never need to worry about accidental exposure — principals start with zero permissions and must be explicitly granted every capability they need.
Principal Types
A principal is anything that can act on the platform. Ergon has one principal model, so roles, permission grants, connections, and resource patterns work identically across every kind: members (humans), API keys (programmatic actors), agents (AI actors), and teams (groups). A fifth kind — machine principals for automations, workflows, and worksheets — holds grants and service access through the same table; they are service-owned and provision their own connections automatically.
Company Members
Members are human users bound to an organization. Each member is assigned a role that determines their permissions across the platform. Members authenticate through the IAM service and receive access tokens scoped to their company and role.
API Keys
API keys are programmatic credentials created within a company. They follow the same permission model as members — each key is assigned a role and carries the same set of permission grants. Use API keys for server-to-server integrations, CI/CD pipelines, and any automation that needs to authenticate without a human in the loop.
Service Agents
Service agents are autonomous AI identities that act as first-class principals in the platform. Like members and API keys, agents are assigned roles and carry permission grants that control what they can access.
Agents have an additional access control layer: a two-way handshake. For a principal to invoke an agent, two conditions must be met simultaneously:
- The invoking principal must hold a permission that allows invoking the agent.
- The agent's own access control list (ACL) must include the invoking principal. The agent's owner explicitly decides who is allowed to interact with it.
This bidirectional consent prevents unauthorized invocations even if a principal has broad permissions — the agent itself must also agree to the interaction.
Teams
A team is a named group of members (and, optionally, nested teams). A team has no credentials of its own — it is a grouping, not a login — but it is a first-class principal: it holds roles, permission grants, service access, and connections directly, and its members inherit all of them through the subject graph.
Prefer modelling access as permission → role → team → members rather than granting each member individually. Onboarding someone then becomes a single team assignment, and removing them revokes everything the team conferred in one step. Manage teams, their members, roles, grants, and service access through the Teams API.
Roles & Permissions
Roles are one mechanism for organizing permissions. A principal carries a base role and can also hold permission grants attached directly to it (or inherited when it creates a resource). The effective permissions of a principal are the union of all grants that apply to it.
Member and role authority are separate
Creating, editing, or deleting role definitions requires iam:company:roles:manage. Assigning or removing a role on a member changes that member and instead requires iam:company:members:manage on the target member. Managing roles does not implicitly authorize changing member assignments.
System Roles
The platform provides three built-in roles that cover the most common access patterns:
| Role | Description |
|---|---|
| Owner | Governs IAM (members, roles, grants) and can create the top-level resources of each enabled service. Under the zero-trust model the owner does NOT automatically see or manage resources it didn't create — content access still requires explicit grants. |
| Admin | A selective set of IAM grants — manage members, roles, API keys, and permission grants. Admins configure access for others but are not a wildcard role and do not automatically have access to service resources. |
| Member | Baseline role. By default a member can only see the list of enabled services; access to any specific resource must be granted explicitly (directly, via a grant template, or by creating the resource). |
Custom Roles
When system roles are too broad or too narrow, create custom roles tailored to your organization. A custom role is a named container for a specific set of permission grants. For example, you might create a workflow-operator role that can manage workflows and view files, but cannot modify IAM settings or agent configurations.
curl -X POST https://platform.ergondata.ai/v1/auth/companies/{company_id}/roles \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "workflow-operator",
"description": "Can manage workflows and view files"
}'Permission Hierarchy
Permissions are named <service>:<namespace>:<action> and organized in a hierarchy of resource types. For example, the workflows service contains permissions like workflows:workflows:manage, workflows:workflows:view, and workflows:folders:create. A namespace wildcard such as workflows:workflows:* grants every action within that one namespace — but there is no service-wide (“workflows:*”) or global wildcard; those would be account-wide god access, which the platform does not have.
Permission Grants
A permission grant binds three things together: a permission (the action, referenced by permission_id), a resource (a hierarchical path identifying the target), and an effect (allow or deny). A grant can be attached to a role or directly to a principal (member, API key, or agent). Principals also receive grants automatically when they create a resource, and reusable bundles of permissions are modeled as roles.
Here is an example of granting the workflow-operator role permission to manage a specific workflow and view the files in a specific bucket. Resolve the permission_id for each permission name from the IAM permission catalog first.
# Grant "workflows:workflows:manage" on a specific workflow
curl -X POST https://platform.ergondata.ai/v1/auth/roles/{role_id}/permissions \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"permission_id": "{workflows_manage_permission_id}",
"resource": "org/{company_id}/folder/{folder_id}/workflow/{workflow_id}",
"effect": "allow"
}'
# Grant "buckets:files:view" on a specific bucket
curl -X POST https://platform.ergondata.ai/v1/auth/roles/{role_id}/permissions \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"permission_id": "{files_view_permission_id}",
"resource": "org/{company_id}/bucket/{bucket_id}",
"effect": "allow"
}'Batch Grants
Public access surfaces expose collection batch endpoints for creating several concrete grants in one operation. The same grouped contract, ordering, 200-grant expansion limit, and partial-success semantics apply across every route in this matrix.
Follow the resource's access links when the service supplies them, then read that concrete zone's GET …/access/resource-types catalog. Its permissions[] entries provide the UUIDs accepted by the corresponding collection route. Every expanded item is independently checked for tenant ownership, canonical zone containment, grantability, and the permission shown below on its concrete root.
| Service / zone | POST route | Required permission | ToolDef slug |
|---|---|---|---|
| Agents | /api/v1/agents/access/agents/grants/batch | agents:permissions:manage | agents.agent_access.create_grants_batch |
| Agents | /api/v1/agents/access/skills/grants/batch | agents:permissions:skills:manage | agents.skill_access.create_grants_batch |
| Agents | /api/v1/agents/access/llm-configs/grants/batch | agents:permissions:llm-configs:manage | agents.llm_config_access.create_grants_batch |
| Agents | /api/v1/agents/access/provider-keys/grants/batch | agents:permissions:provider-keys:manage | agents.provider_key_access.create_grants_batch |
| Automations | /api/v1/automations/access/projects/grants/batch | automations:permissions:projects:manage | automations.project_access.create_grants_batch |
| Automations | /api/v1/automations/access/automations/grants/batch | automations:permissions:automations:manage | automations.automation_access.create_grants_batch |
| Buckets | /api/v1/buckets/access/buckets/grants/batch | buckets:permissions:buckets:manage | buckets.access_grants.create_grants_batch |
| Buckets | /api/v1/buckets/access/folders/grants/batch | buckets:permissions:folders:manage | buckets.folder_access_grants.create_grants_batch |
| Channels | /api/v1/channels/configs/access/grants/batch | channels:permissions:manage | channels.channel_access.create_grants_batch |
| Channels | /api/v1/channels/addresses/access/grants/batch | channels:permissions:manage | channels.address_access.create_grants_batch |
| Compute | /api/v1/compute/collection-folders/access/grants/batch | compute:permissions:collection-folders:manage | compute.collection_folder_access.create_grants_batch |
| Compute | /api/v1/compute/collections/access/grants/batch | compute:permissions:collections:manage | compute.collection_access.create_grants_batch |
| Compute | /api/v1/compute/function-folders/access/grants/batch | compute:permissions:function-folders:manage | compute.function_folder_access.create_grants_batch |
| Compute | /api/v1/compute/functions/access/grants/batch | compute:permissions:functions:manage | compute.function_access.create_grants_batch |
| Conversations | /api/v1/conversations/access/conversation-projects/grants/batch | conversations:permissions:manage | conversations.access_grants.create_project_grants_batch |
| Conversations | /api/v1/conversations/access/conversations/grants/batch | conversations:permissions:manage | conversations.access_grants.create_conversation_grants_batch |
| IAM company | /v1/auth/companies/{company_id}/grants/batch | iam:company:grants:create | iam.company.grants.batch_create |
| IAM role zone | /v1/auth/companies/{company_id}/roles/access/grants/batch | iam:company:roles:manage | iam.company.roles.access.grants.batch_create |
| IAM team zone | /v1/auth/companies/{company_id}/teams/access/grants/batch | iam:company:teams:manage | iam.company.teams.access.grants.batch_create |
| IAM API key zone | /v1/auth/companies/{company_id}/api-keys/access/grants/batch | iam:company:api-keys:manage | iam.company.api_keys.access.grants.batch_create |
| IAM agent zone | /v1/auth/companies/{company_id}/agents/access/grants/batch | iam:company:agents:manage | iam.company.agents.access.grants.batch_create |
| IAM member zone | /v1/auth/companies/{company_id}/members/access/grants/batch | iam:company:members:manage | iam.company.members.access.grants.batch_create |
| Vault | /api/v1/vault/namespaces/access/grants/batch | vault:permissions:namespaces:manage | vault.namespace_access.create_grants_batch |
| Workflows | /api/v1/workflows/folders/access/grants/batch | workflows:permissions:folders:manage | workflows.folder_access_grants.create_grants_batch |
| Workflows | /api/v1/workflows/workflows/access/grants/batch | workflows:permissions:workflows:manage | workflows.access_grants.create_grants_batch |
| Workflows | /api/v1/workflows/workflow-views/access/grants/batch | workflows:permissions:workflows:manage | workflows.view_access_grants.create_grants_batch |
| Worksheets | /api/v1/worksheets/folders/access/grants/batch | worksheets:permissions:folders:manage | worksheets.access_grants.create_grants_batch |
| Worksheets | /api/v1/worksheets/worksheets/access/grants/batch | worksheets:permissions:worksheets:manage | worksheets.worksheet_access_grants.create_grants_batch |
| Worksheets | /api/v1/worksheets/views/access/grants/batch | worksheets:permissions:views:manage | worksheets.view_access_grants.create_grants_batch |
All routes use the same BatchCreateGrantsRequest schema: operations[] contains optional client_ref (at most 100 characters), required principal_type (member | api_key | agent | automation | workflow | worksheet | role | team; a service may accept a narrower set), required principal_id, non-empty resources[], non-empty UUID permission_ids[], and optional effect (allow | deny, default allow). Expansion is ordered by operation, resource, then permission ID.
curl -X POST https://platform.ergondata.ai/api/v1/agents/access/agents/grants/batch \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"operations": [{
"client_ref": "reviewers",
"principal_type": "member",
"principal_id": "{member_principal_id}",
"resources": [
"org/{company_id}/agent/{agent_id}",
"org/{company_id}/agent/{second_agent_id}"
],
"permission_ids": ["{agents_view_permission_id}", "{agents_use_permission_id}"],
"effect": "allow"
}]
}'A structurally valid request returns 200 with one ordered result per expanded grant: created, already_exists, or failed. Invalid or unauthorized items do not roll back independent successes. Retrying the whole request is safe: the natural grant tuple is idempotent, and existing grants return already_exists. A committed grant may also report side_effect_error_status and side_effect_error_detail; retry that result to rerun reconciliation.
Agents and API clients should prefer one batch operation over sequential single-grant calls whenever they need more than one grant.
Resource Patterns
A grant's resource is a hierarchical path that identifies the target instance (or container) the grant applies to. Paths always start at the organization and walk down the resource-type tree:
org/<company_id>/<type>/<id>/<type>/<id>/…
For example, org/{cid}/folder/{folder_id}/workflow/{workflow_id} identifies one specific workflow, while org/{cid}/bucket/{bucket_id} identifies a bucket. A grant on a container scopes to the resources beneath it.
Namespace Wildcards
Wildcards live on the permission name, not the resource path. A permission like workflows:workflows:* covers every action in that one namespace, granted on a concrete resource path. There is deliberately no service-wide (workflows:*) or global wildcard — a wildcard never reaches across namespaces or services.
Broad vs. Narrow Grants
Combining a permission (optionally a namespace wildcard) with a resource path determines how broad or narrow a grant is:
| Permission + Resource | Scope | Use Case |
|---|---|---|
workflows:workflows:* on a folder | All workflows in a folder | Every workflow action across the workflows contained in one folder. |
workflows:workflows:manage on a workflow | Single resource | Manage one specific workflow only. |
workflows:workflows:view on a workflow | Single resource, read-only | View one specific workflow without the ability to change it. |
When evaluating a request, the platform checks whether the requested action matches a permission held by the principal, and whether the target resource falls under the grant's resource path. A match is determined by comparing the hierarchical segments of the resource path — a grant on a container covers the resources beneath it.
Start with narrow grants and widen as needed. It is much safer to grant access to a specific resource and expand later than to start with a wildcard and try to restrict with deny rules.
Zones & Connections
Service access lets a principal reach a service. It does not, by itself, place the principal inside any particular resource in that service. A federated zone is the authorization boundary around a single concrete resource — a workflow, bucket, agent, skill, or LLM config. Its defining rule: access is scoped to that one instance and managed from its own access panel, never from the parent or the resource type as a whole. (A zone needn't contain anything — a skill or LLM config has no sub-resources but is still a zone, because access to it is delegated on its own.) A principal becomes eligible inside a zone only after an explicit connection ties it to that zone.
Without this gate, every principal with Workflows access would appear in the access list of every workflow in the company. The connection gate keeps each zone's access list intentional and small, and it doubles as a runtime kill switch: revoke a connection and every grant on that zone goes inert immediately — re-connect and they light back up, without ever touching the grants themselves.
Who needs a connection
The connection gate applies to people-like principals — members, agents, API keys, and teams (and roles, as grant carriers). Service-owned machine principals (automations, workflows, worksheets) provision their own connections and service access when they are wired together, so you do not manage those by hand.
Per-instance access
Each zone has its own Access panel, owned by whoever can manage that zone. From it you list the principals eligible to receive access (those with the right service access and a connection to this zone), see the grants on that instance, and add or revoke grants scoped to it. This panel only grants to already-connected principals — a grant never creates a connection, and the backend rejects one to an unconnected principal. To connect, use the request/offer flows below (they can carry the grants in the same step). Creating a resource is the exception that proves the rule: the create action connects its creator to the new zone and grants the zone bundle together.
Connecting a principal to a zone
A connection is always a two-party handshake; the zone owner is always the gatekeeper. There are three ways one is established:
- Direct (auto-materialize). When the actor holds manage on both ends — the target zone and the subject — a request or offer materializes immediately (connection minted + any requested grants issued, no handshake), because both sides are already consented by the same admin.
- Request. A principal (or someone wiring up a dependency they don't own) requests a connection to the target zone. An administrator of that zone approves it — and approval can connect and grant the requested permissions in one step.
- Offer. A zone owner invites a principal in from the zone's Connections view, optionally pre-selecting the permissions to grant on acceptance. The invited side accepts; the inviting side can withdraw but never self-accepts.
Neither side approves its own move
A request is approved by the zone, never the requester; an offer is accepted by the invited principal's controller, never the inviting zone. Both halves require two parties.
The example below connects a team directly into a zone, then mints a grant on that zone through its per-instance Access panel. (Connection endpoints currently authenticate with a user session token; per-instance grants accept user, API key, or agent tokens.)
# 1. Connect a team to a workflow zone (connects now, or opens a request
# if you don't manage the target zone)
curl -X POST https://platform.ergondata.ai/v1/auth/companies/{company_id}/connections \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"subject_type": "team",
"subject_id": "{team_id}",
"target_service": "workflows",
"target_resource": "org/{company_id}/folder/{folder_id}/workflow/{workflow_id}",
"permissions": ["workflows:workflows:view"]
}'
# 2. Mint a per-instance grant on a specific role (Access panel of that role)
curl -X POST https://platform.ergondata.ai/v1/auth/companies/{company_id}/roles/{role_id}/access/grants \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"principal_type": "member",
"principal_id": "{member_id}",
"permission_name": "iam:company:roles:view",
"effect": "allow"
}'Revoking and cleanup
Revoking a connection (DELETE …/connections/{connection_id}) is the instant kill switch described above. Lifecycle events clean up after themselves too: deleting a resource purges the connections and any pending connection requests that pointed at it, and retiring a principal — including a team — purges that principal's connections and requests. You never end up with grants or requests stranded behind a resource or identity that no longer exists.
API Reference
See IAM API — Connections, Connection Requests, and the per-instance Teams endpoints for the full contracts.
Service Permissions
Each platform service defines its own permission namespaces. The table below lists every service with a few representative permissions. Namespace wildcards (e.g. workflows:workflows:*) grant all actions within that one namespace; there is no service-wide wildcard.
| Service | Permission Scopes | Description |
|---|---|---|
| IAM | iam:company:teams:manage, iam:company:grants:view | Organization governance — members, teams, roles, API keys, agents, services, permission grants, and connections. |
| Workflows | workflows:workflows:manage, workflows:folders:create | Workflow lifecycle — folders, workflows, phases, fields, routing, and access grants. |
| Agent Hub | agents:agents:manage, agents:agents:use, agents:conversations:view, agents:privacy:manage | AI agent management — agent configs, invocation (use), derived view of an agent's public conversations (agents:conversations:view), and the per-agent always-public privacy lock (agents:privacy:manage). |
| Conversations | conversations:conversations:view, conversations:messages:create, conversations:notes:view, conversations:privacy:manage, conversations:settings:manage | Dialogue threads — conversations, messages, participants, attachments, granular notes read (notes:view), per-conversation privacy control, company note-taking/A2A settings, and access grants. |
| Automations | automations:automations:edit, automations:projects:create | Automation projects and automations — triggers, actions, connection-based grants, and access management. |
| Channels | channels:channels:manage, channels:addresses:send | Messaging — channel configs, addresses (send/receive), activity, and address ACL. |
| Buckets | buckets:buckets:create, buckets:files:view | File storage — buckets, nested folders, files, upload, search, and access grants. |
| Worksheets | worksheets:worksheets:edit, worksheets:folders:create | Spreadsheet data — folders, worksheets, columns, rows, views, and access grants. |
| Event Streams | event-streams:events:view, event-streams:metrics:view | Platform observability — event browsing, aggregated metrics, and correlated traces. |
Event Streams Permissions
The Event Streams service uses three distinct permission scopes to control access to different parts of the observability surface:
| Permission | Grants Access To |
|---|---|
event-streams:events:view | Browse and filter individual events, view event details, and connect to the real-time WebSocket stream. |
event-streams:metrics:view | View the metrics catalog, query aggregated metrics, summaries, time series, top event types, top actors, and source breakdowns. |
event-streams:traces:view | List and inspect correlated event traces that span multiple services. |
Memory Permissions
Memory follows the resource that owns it. Conversation notes and digests use conversations:notes:view and conversations:notes:manage on the conversation. Folder-scoped document search still requires the applicable Buckets folder and file permissions and cannot broaden the requested folder scope.
Semantic agent memories expose separate view and manage capabilities on the agent; deleting an entry requires the manage capability. A user can always inspect, correct, and delete their own preferences through the self-service preferences API. Reading another user's preferences is not implied by agent access or conversation access.
A grant in one memory layer never crosses into another. Note access does not grant user-preference access, and agent-memory access does not grant access to source conversation messages.
Cross-Service Grants
The permission model described above governs what a principal can do. Cross-service grants solve a different problem: controlling which resources in one service can access resources in another service. This is a resource-to-resource relationship, not a principal-to-resource one.
For example, a workflow might need to store files in a bucket, or send email through a channel address. Rather than relying on a particular user's personal permissions, the platform authorizes the integration at the resource level: the owning resource grants (or approves a connection to) the consuming resource, so the access holds no matter who triggers it.
This is the same connection machinery described in Zones & Connections, applied to service-owned machine principals. The difference is that those connections are provisioned automatically as you wire resources together, rather than requested or offered by a person.
The Double Handshake
Cross-service grants follow a deliberate three-step pattern that requires consent from both sides:
- Owner grants access. The administrator of the owning resource (e.g., a bucket) creates a grant specifying which principal may access it — for example, granting a specific workflow access to upload files to the bucket. Alternatively, the consumer can raise a connection request that the owner approves or rejects.
- Consumer discovers and selects. The consuming service queries for available connections and presents only the resources it has been authorized to use. When configuring a workflow, the bucket picker shows only buckets that have been granted to that workflow.
- Runtime enforcement. At the moment of action — when the workflow actually uploads a file or sends an email — the owning service verifies that a valid grant exists. If the grant has been revoked since configuration, the request is denied immediately.
Neither side can act alone. The owner must explicitly offer access, and the consumer must have an active grant verified at the point of use. This prevents unintended access even if someone knows a resource identifier.
Current Integrations
The platform currently supports the following cross-service grant integrations:
| Owner | Resource | Consumer | Use Case |
|---|---|---|---|
| Buckets | Bucket | Workflows | A workflow stores and retrieves file attachments in a shared bucket. |
| Channels | Address | Workflows | A workflow sends outbound messages through a channel address. |
| Agent Hub | LLM Config | Buckets | A Buckets folder uses an LLM configuration for document processing and embedding generation. |
How This Differs from IAM Permissions
It is important to understand the distinction between the two authorization layers. IAM permission grants answer the question “can this principal perform this action?” — they control what users, API keys, and agents can do. Cross-service grants answer a different question: “can this resource in service A access that resource in service B?” — they control how platform resources integrate with each other.
Both layers are enforced independently. A principal might have permission to configure a workflow (IAM grant), but the workflow can only send email through a channel address if the address owner has created a cross-service grant for that workflow (service grant). The two systems work together to provide defense in depth.
API Reference
For the full list of permissions, permission grant endpoints, and role management APIs, see the IAM API Reference — Permissions.