Roles and Permissions
Operator roles, tenant scoping, authentication methods, the audit trail, and authorization errors in AgentOwl.
AgentOwl uses a role-based access control (RBAC) model to govern who can perform operations across the platform. Every authenticated API request carries an operator identity, a role, and a tenant scope that together determine what the caller is allowed to do.
This guide covers the two operator roles, how tenant scoping works, the supported authentication methods, and the audit trail that records every administrative action. In the dashboard, tenants are called workspaces.
Roles overview
AgentOwl defines two human-operator roles. There are no end-user or read-only roles in v1 — the system is designed for operators and automation, not end-user inboxes.
| Role | Scope | Intended for |
|---|---|---|
platform-admin | Global — all tenants | Infrastructure operators who manage the entire AgentOwl deployment |
tenant-admin | Scoped — explicitly listed tenants only | Team leads or service owners who manage their own tenants |
Platform admin
A platform admin has unrestricted access to every resource and operation in the system. The role is designed for the small number of operators who provision and maintain the AgentOwl deployment itself.
Platform admins can:
- Create, list, read, and delete any tenant.
- Create and manage bindings, endpoints, routes, and policies for any tenant without being listed in a tenant scope.
- Trigger worker-role deployments (
POST /v1/deployments). - Query the audit log across all tenants or for a specific tenant.
- Publish, validate, diff, and roll back runtime configuration.
- Trigger provider reconciliation and view drift reports.
- View dead-letter entries and replay events.
When a platform admin authenticates, tenant_scope is null, which the
authorization layer interprets as "access all tenants."
Tenant admin
A tenant admin has full administrative access, but only within the
tenants explicitly listed in their tenant_scope array. Any request that
touches a tenant outside that list receives a 403 Forbidden response.
Tenant admins can:
- Create tenants, up to the limit of their account's plan (see Plan limits). New tenants are added to their scope.
- List and read tenants they are scoped to.
- Create and manage bindings, endpoints, routes, and policies within their authorized tenants.
- Query the audit log for their own tenants (the
tenant_idquery parameter is required). - View events, dead letters, and replay events within their tenants.
Tenant admins cannot:
- Exceed their plan's tenant limit.
- Trigger worker deployments.
- Query the audit log without specifying a
tenant_id. - Access any resource belonging to a tenant outside their scope.
Tenant scoping
Multi-tenancy is the core isolation boundary in AgentOwl. Every resource — bindings, endpoints, routes, policies, events — belongs to exactly one tenant. The authorization layer enforces this on every request.
How it works
Each authenticated request carries an AuthContext with a
tenant_scope field:
platform-admin → tenant_scope: null (global access)
tenant-admin → tenant_scope: ["t_abc123"] (scoped access)Before any tenant-scoped operation executes, the system calls
requireTenantAccess(auth, tenantId):
- If the caller is a
platform-admin, access is granted immediately. - If the caller is a
tenant-adminand the targettenantIdis in theirtenant_scopearray, access is granted. - Otherwise, the request is rejected with:
{
"error": {
"code": "forbidden.tenant_scope",
"type": "forbidden",
"message": "Operator does not have access to tenant 't_xyz'"
}
}Scoping in list operations
When a tenant-admin lists resources, the query is automatically
filtered to only return results from their authorized tenants. For
example, GET /v1/tenants returns only the tenants in the caller's
scope — not the full list.
Permission matrix
The table below shows which operations each role can perform.
| Operation | Platform admin | Tenant admin |
|---|---|---|
| Tenants | ||
| Create tenant | Yes | Yes, within plan limits |
| List tenants | All | Scoped only |
| Get tenant | Any | Scoped only |
| Delete tenant | Any | Scoped only |
| Bindings | ||
| Create binding | Any tenant | Scoped only |
| List bindings | Any tenant | Scoped only |
| Get binding | Any tenant | Scoped only |
| Delete binding | Any tenant | Scoped only |
| Endpoints | ||
| Create endpoint | Any tenant | Scoped only |
| List endpoints | Any tenant | Scoped only |
| Get / update endpoint | Any tenant | Scoped only |
| Delete endpoint | Any tenant | Scoped only |
| Routes | ||
| Create route | Any tenant | Scoped only |
| List routes | Any tenant | Scoped only |
| Get / update route | Any tenant | Scoped only |
| Delete route | Any tenant | Scoped only |
| Policies | ||
| Create policy | Any tenant | Scoped only |
| List policies | Any tenant | Scoped only |
| Get / update policy | Any tenant | Scoped only |
| Delete policy | Any tenant | Scoped only |
| Configuration | ||
| Validate config | Yes | Yes (scoped) |
| Diff config | Yes | Yes (scoped) |
| Publish config | Yes | Yes (scoped) |
| Rollback config | Yes | Yes (scoped) |
| Deployments | ||
| Trigger deployment | Yes | No |
| List deployments | Yes | No |
| Audit log | ||
| Query all tenants | Yes | No |
| Query own tenant | Yes | Yes |
| Events | ||
| List / get events | Any tenant | Scoped only |
| Replay event | Any tenant | Scoped only |
| Operations | ||
| Trigger reconciliation | Yes | Yes (scoped) |
| View drift | Yes | Yes (scoped) |
| View dead letters | Any tenant | Scoped only |
| Ingress (webhooks) | ||
| Receive email / SMS / voice | N/A — provider-verified, no operator auth |
Plan limits
Tenant admins are limited by their account's plan. New accounts start on the Free plan.
| Plan | Workspaces (tenants) | Messages per day | Bindings |
|---|---|---|---|
| Free | 1 | 100 | 3 |
| Pro | 10 | 10,000 | 50 |
| Enterprise | Custom | Custom | Custom |
These plan limits describe the allowances for each account plan. Check purchase and upgrade availability in Admin and phone-number availability for your account. Phone provisioning requires an active paid entitlement. If a purchase, upgrade or number setup action is unavailable, contact support@agentowl.dev. Sandbox checkout does not grant production privileges.
Free email sending requires reviewed pilot enablement and an exact address that is ready. Enabled accounts have a separate account-wide limit of 10 recipient submissions per UTC day and a burst limit of five new recipients per rolling minute. The Messages per day column above describes inbound platform messages, not outbound recipient allowance.
Phone numbers (SMS and voice bindings) require an active paid entitlement; a stored plan label alone does not establish provisioning eligibility.
Authentication methods
AgentOwl supports three authentication methods. API keys and OAuth access
tokens use the Authorization: Bearer <token> header; the dashboard uses a
session cookie.
API key
A pre-shared API key stored as a SHA-256 hash in the D1 api_keys
table. The raw token is never persisted.
Authorization: Bearer aok_<48-hex>- Format:
aok_<48-hex> - Storage: SHA-256 hash in D1
api_keystable - Created via: dashboard (Settings → API Keys → Create Key) or the API. The key is shown once; copy it immediately.
- AuthContext:
auth_method: "api-key" - Scope: API keys receive wildcard scope
["*"]and act on behalf of the account that created them. - Revocation: Revoke in Settings → API Keys.
When to use: CLI scripts, CI/CD pipelines, programmatic access.
OAuth 2.1 access token
An opaque access token obtained via the OAuth 2.1 authorization code
flow with PKCE or the device code flow. Stored as a SHA-256 hash in
the D1 oauth_tokens table.
Authorization: Bearer oat_<48-hex>- Format:
oat_<48-hex>(opaque, not JWT) - Storage: SHA-256 hash in D1
oauth_tokenstable - Obtained via: authorization code flow with PKCE or device code flow
- AuthContext:
auth_method: "oauth" - Scoped: tokens carry specific scopes (e.g.,
tenants:read,endpoints:write) - Revocation:
POST /oauth/revoke(RFC 7009)
agentowl login without --token uses the device code flow and stores an
oat_ token.
When to use: MCP clients, third-party integrations, scoped automation.
Dashboard session
When you sign in to the dashboard with GitHub or Google, the browser holds a
signed session cookie. Requests without an Authorization header fall back
to that cookie.
- AuthContext:
auth_method: "session" - Used by: the admin dashboard only. Use an API key or OAuth token for anything else.
Internal worker roles
Worker deployment environments are separate from human operator roles. The main production Worker combines the API, HTTP ingress, queue consumer, and maintenance tasks. The recorded agent-work release also deployed a separate Cloudflare email Worker with no HTTP routes. Other role environments are templates that need their own bindings and secrets before use.
| Environment | Purpose | Configuration scope |
|---|---|---|
| Default main Worker | API, SMTP/SMS/voice ingress, dispatch, reconciliation and maintenance | D1, runtime and secret KV, Queue producer/consumer, private attachment R2, outbound fetch |
email-ingress | Cloudflare Email Routing handler | D1, runtime KV, Queue producer, private attachment R2; no HTTP routes |
sms-ingress, voice-ingress | Optional separate HTTP ingress deployments | Operator must provision the required bindings and secrets |
queue-consumer, reconciler | Optional separate processing deployments | Operator must provision the required bindings and secrets |
Ingress persists events to D1, and the API can make outbound provider calls. The current deployment does not enforce the earlier proposed separation in which ingress had no database access and the API had no provider access. Tenant authorization is enforced by application checks; deployment environment names alone do not restrict handlers or outbound network access. See Architecture.
Only platform-admin operators can trigger deployments via
POST /v1/deployments.
Audit trail
Every authenticated API request is logged to the audit_log table with
full actor attribution.
What is recorded
| Field | Description |
|---|---|
audit_id | Unique log entry ID |
timestamp | ISO 8601 timestamp |
operator_id | Who performed the action |
role | platform-admin or tenant-admin |
auth_method | api-key, oauth, or session |
tenant_id | Target tenant (if applicable) |
action | HTTP method + path (e.g., POST /v1/bindings) |
resource_type | tenant, binding, endpoint, route, policy, etc. |
resource_id | ID of the affected resource |
request_id | Correlation ID for the request |
status_code | HTTP response status |
ip_address | Caller IP address |
user_agent | Caller user-agent string |
Querying the audit log
GET /v1/audit-log?tenant_id=t_abc123&since=2026-03-01T00:00:00Z&limit=100Access rules:
- Platform admin: Can query with or without
tenant_id. Omitting it returns entries across all tenants. - Tenant admin: Must include
tenant_id, and it must be in their scope. Omitting it returns a validation error.
Error responses
All authorization failures return structured error responses with machine-readable codes.
| HTTP status | Error code | Meaning |
|---|---|---|
401 | auth.missing_credentials | No Authorization header |
401 | auth.invalid_scheme | Header does not use Bearer scheme |
401 | auth.missing_token | Bearer token is empty |
401 | auth.invalid_token | Token is invalid or expired |
403 | forbidden.tenant_scope | Operator lacks access to the target tenant |
403 | forbidden.platform_admin_required | Operation requires platform-admin role |
403 | forbidden.no_account | The API key is not linked to an account |
403 | forbidden.account_inactive | The account is not active |
429 | rate_limit.tenant_limit | Creating a tenant would exceed the plan's tenant limit |
All error responses include a request_id for correlation with the
audit log and operational logs.
Best practices
For platform admins
- Use scoped OAuth tokens for automation. API keys carry wildcard scope; prefer OAuth access tokens with only the scopes a pipeline needs.
- Rotate keys regularly. Create a new API key, switch automation to it, then revoke the old one.
- Monitor the audit log. Regularly review
GET /v1/audit-logfor unexpected actions or unfamiliar operator IDs.
For tenant admins
- Name your API keys. Give each key a name that identifies where it is
used (for example
ci-deploy) so you can revoke the right one. - Use OAuth 2.1 tokens for agents. Use scoped OAuth access tokens
for MCP clients and automation so you can revoke them independently
via
POST /oauth/revoke. - Use dry-run for destructive operations. Tenant deletion supports
?plan=trueto preview what will be removed before executing.
For automation and MCP clients
- Prefer scoped OAuth tokens over API keys. OAuth 2.1 access tokens carry specific scopes and can be revoked individually, providing better security than wildcard API keys.
- Handle token revocation gracefully. If your token is revoked, surface the error to the operator and re-authenticate.
- Include a descriptive
User-Agent. The audit log records it, making it easier to identify which automation performed an action.
Quick reference
Auth context shape
interface AuthContext {
operator_id: string;
role: "platform-admin" | "tenant-admin";
tenant_scope: string[] | null; // null = global access
auth_method: "api-key" | "oauth" | "session";
scope?: string[];
}Role decision tree
Is the operation platform-level? (deployments, unscoped audit log)
├── Yes → Requires platform-admin
└── No → Is a tenant_id involved?
├── Yes → requireTenantAccess() checks:
│ ├── platform-admin → allowed
│ └── tenant-admin → allowed if tenant_id in scope
└── No → Authenticated access sufficient