Permissions
Roles, scoped API keys, service accounts, guardrails and Cedar policies decide what each member and key can do.
Rolling out
Permissions are enabled per organization while they roll out. Until your
organization has them, keys use the scopes in
Authentication, and the Roles, Service
accounts, Access and Audit events endpoints return 404. Contact support to
turn them on.
What a member or key can do is whatever its role grants, minus every restriction that applies: the key's own restrictions, the role's conditions, your organization's guardrails and policies, and the platform's rules. Personal and service keys never do more than their owner. An organization key has a fixed ceiling that cannot exceed the creator's access when created. Cedar policies can grant a key access directly.
| Term | Meaning |
|---|---|
| Action | One thing the API does, such as compute.create or inference.invoke. Every API reference page names the action it needs under Required permission. |
| Permission | An area and a level, such as Compute: Write. Each level includes the levels before it. |
| Role | A named set of permissions. Every member has exactly one role. |
| Personal key | A key owned by a member. It acts as that member, within its own restrictions, and stops working the moment the member leaves. |
| Service key | A key owned by a service account, for automation that should outlive the people who set it up. |
| Legacy key | A key created before your organization enabled permissions. Its scopes still decide what it can do. |
| Guardrail | An organization-wide rule: allowed networks, allowed models, or a maximum key lifetime. |
| Cedar policy | A rule written by an owner in Cedar, checked before it is saved. |
Permissions
| Area | Levels | Allows |
|---|---|---|
| Inference | Use | Run chat completions, messages, embeddings and decisions on models the organization offers. |
| Batch | Read, Write | Read: List and read batches and their files. Write: Upload files and create, cancel or tag batches. |
| Dedicated endpoints | Read | List dedicated endpoints and their events and series. |
| Compute | Read, Write | Read: List and inspect VMs and pods, metrics, logs, snapshots and capacity. Write: Create, fork, terminate, stop, restart, reboot, resize and snapshot VMs and pods, attach SSH keys, and reach workload HTTP endpoints. |
| Kubernetes clusters | Read, Write | Read: List clusters, node pools and nodes. Write: Create, scale and delete clusters and node pools. |
| Kubernetes access | View, Admin | View: Get a kubeconfig or token with view access inside the cluster. Admin: Get a kubeconfig or token with cluster-admin inside the cluster. |
| Integrations | Read, Write | Read: List SSH keys, registry credentials and webhooks. Write: Create, change and delete them, and rotate webhook secrets. |
| Runners | Read, Write | Read: List GitHub runner pools and jobs. Write: Create, resize and delete runner pools. |
| Provider | Read, Write | Read: Provider status, nodes, tokens, earnings and reservations. Write: Provisioning tokens, node changes, offboarding and capacity release. |
| API keys | Read, Write | Read: List the organization's API keys. Write: Create API keys within your own access. Signed-in sessions with apikey.update can edit a key's budget; apikey.revoke can revoke any key in the organization. |
| Service accounts | Read, Manage | Read: List service accounts. Manage: Create, change, disable and delete service accounts. |
| Members | Read, Write | Read: List members. Write: Invite people, manage pending invitations, change members' roles and remove members. |
| Access | Read, Write | Read: See roles, policies, the audit log and the access checker. Write: Create and change custom roles and guardrails. |
| Billing | Read, Write | Read: See the balance, usage, payment methods and auto-recharge. Write: Manage cards, deposits, auto-recharge and spend-limit requests. |
| Organization | Read, Write | Read: See organization details, banners and transfers. Write: Change organization settings and transfer VMs. |
An API key can always revoke itself, whatever its API keys level. It cannot
revoke another key.
Everyone can read the public catalog (models, GPU types, prices), call
whoami, and request access to a model. Creating keys, editing key budgets,
revoking other keys, managing service accounts, roles, guardrails and
policies, and managing members, billing and organization settings need a
signed-in session: an API key is refused them, so a leaked key cannot mint a
replacement or revoke another key. The catalog marks the levels that add
only such actions (sessionOnly), and the dashboard does not offer them for
a key.
GET /v1/authz/catalog returns this
table with every action each level contains.
Built-in roles
| Role | Grants | Cannot |
|---|---|---|
| Owner | Everything, including deleting the organization, writing Cedar policies and granting Owner. | |
| Admin | Inference: Use, Batch: Write, Dedicated endpoints: Read, Compute: Write, Kubernetes clusters: Write, Kubernetes access: Admin, Integrations: Write, Runners: Write, Provider: Write, API keys: Write, Service accounts: Manage, Members: Write, Access: Write, Billing: Write, Organization: Write. | Delete the organization, write Cedar policies, grant or remove Owner. |
| Member | Inference: Use, Batch: Write, Dedicated endpoints: Read, Compute: Write, Kubernetes clusters: Write, Kubernetes access: Admin, Integrations: Write, Runners: Write, Provider: Write, API keys: Write, Members: Read, Billing: Read, Organization: Read. | Manage billing, members, roles or policies; transfer VMs. |
| Billing | Billing: Write, Compute: Read, Members: Read, Organization: Read. | Run workloads, call inference, create keys. |
| Inference | Inference: Use, Batch: Write, Dedicated endpoints: Read, API keys: Write, Billing: Read, Organization: Read. | Launch GPUs, clusters or pods; see payment methods. |
| Viewer | Batch: Read, Dedicated endpoints: Read, Compute: Read, Kubernetes clusters: Read, Integrations: Read, Runners: Read, Provider: Read, API keys: Read, Members: Read, Billing: Read, Organization: Read. | Any change; inference; creating keys. |
Only an owner grants, changes or removes the Owner role. Nobody can give a
member, role, service account or key more than they hold themselves: the
request is refused with ESCALATION_REFUSED, naming the first permission you
lack.
API keys
In an organization with permissions enabled, every standard API key is organization-owned, has no member or service-account owner, and gets a fixed permission ceiling. Playground keys are a separate ephemeral flow and remain personal. Create a key from Organization → API keys in the dashboard, or with the API. Choose what it may do and any restrictions:
curl -X POST https://api.openrelay.inc/v1/orgs/{orgId}/api-keys/create \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "chatbot-prod",
"note": "chatbot service on the prod cluster",
"template": "inference",
"restrictions": {
"models": ["openrelay/laya"],
"ipRanges": ["203.0.113.0/24"]
},
"expiresInSeconds": 7776000
}'-
templatepicks a preset by what the key is for:Template For Permissions inferenceCalling models Inference: Use inference_batchCalling models and running batch jobs Inference: Use, Batch: Write compute_automationCI/CD deploys Compute: Write, Kubernetes clusters: Write, Kubernetes access: Admin, Integrations: Write read_onlyMonitoring and reports Batch: Read, Dedicated endpoints: Read, Compute: Read, Kubernetes clusters: Read, Integrations: Read, Runners: Read, Provider: Read, API keys: Read, Members: Read, Billing: Read, Organization: Read providerProvider nodes and provisioning tokens Provider: Write same_as_ownerEverything you can do No ceiling: the key follows its owner's role as it changes With permissions enabled,
same_as_owneris refused with400 BAD_REQUEST.Or send
permissions, a list of permission ids such as["compute.write", "kubernetes.read"]. A template or permission beyond your access is refused with403 ESCALATION_REFUSED; the response names the missing permission. -
restrictionsnarrows the key further:models(model ids),endpoints(dedicated endpoint ids), andipRanges(up to 20 IPv4 or IPv6 CIDRs the key may be used from).
A request that only sends scopes still works and creates an organization
key whose ceiling matches those scopes. The creator must hold every mapped
permission level; otherwise the request is refused with HTTP 403
(ESCALATION_REFUSED), and permission names the missing level:
| Scope | Ceiling |
|---|---|
| any | Inference Use, Batch Write, and reaching a workload's HTTP endpoint |
clusters:read | every Read level except Service accounts and Access |
clusters:write | Compute, Kubernetes, Integrations and Runners at their highest level, and Provider Write |
inference:endpoint:<id> | only those dedicated endpoints |
With permissions enabled, these create fields are refused with HTTP 400
(BAD_REQUEST): kind: "service", kind: "personal", any serviceAccountId,
and an empty permissions list.
A key can revoke only itself, regardless of its scopes.
With permissions enabled, edit only a key's budget with
PATCH /v1/orgs/{orgId}/api-keys/{id}.
Requests that set name, note or restrictions are refused with 400.
To change anything else, create a replacement key, then revoke the old one.
Keys created before permissions were enabled retain their legacy behavior.
When a member leaves, their personal keys stop working at once and are revoked; the removal response lists them. Service keys and legacy keys are unaffected.
Check what any key can do with
GET /v1/whoami, which lists its permissions, kind,
owner and restrictions, or with orl auth status.
Custom roles
A custom role is a set of permissions plus optional conditions that apply to every member and service account holding it, and to their keys:
curl -X POST https://api.openrelay.inc/v1/roles \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ML engineers",
"permissions": ["inference.use", "batch.write", "compute.read"],
"models": ["openrelay/laya"],
"ipRanges": ["10.8.0.0/16"]
}'Assign it with the member's role:
PATCH /v1/orgs/{orgId}/members/{userId}
with {"roleId": "role_…"} (built-in roles go in role, as before). Invite
someone straight into it with roleId on
POST /v1/orgs/{orgId}/members/add. A role still assigned cannot be deleted
(409 ROLE_IN_USE, listing who holds it). An organization has at most 50
custom roles.
Service accounts
A service account holds a role. Existing service-account keys continue to
work: disabling the account denies every request they make until you enable
it again, and deleting the account revokes them. With permissions enabled,
you cannot create new service-account keys.
Create an account with
POST /v1/service-accounts.
Guardrails
| Guardrail | Effect | Who sets it |
|---|---|---|
API key networks (key_networks) | API keys may only be used from these CIDRs. Sessions are unaffected. At a VM or pod HTTP endpoint, a legacy key with no restrictions is not held to it; restrict or replace such keys. | Access: Write |
Model allowlist (model_allowlist) | Inference only on these models and dedicated endpoints. | Access: Write |
Everyone networks (everyone_networks) | Sessions and keys may only be used from these CIDRs. | Owners |
| Maximum key lifetime | New keys must expire within this many days. | Access: Write |
Set the first three with POST /v1/authz/policies
and the lifetime with PUT /v1/authz/settings.
A rule that could block your own session is refused (409 WOULD_LOCK_OUT) if
saving it would take Members: Write or the right to edit policies from
you. A change that would let others call models you cannot, such as widening
or deleting the model allowlist, is refused with ESCALATION_REFUSED.
Cedar policies
Owners can write Cedar policies for rules the
guardrails do not cover. A policy is one permit or forbid statement, at
most 10 KB, and must name your organization as its resource:
resource in OpenRelay::Org::"<orgId>". Every entity it names must belong to
your organization: members are OpenRelay::User::"<userId>", built-in roles
OpenRelay::Role::"member", custom roles OpenRelay::Role::"<orgId>/role_…".
Validate before saving with
POST /v1/authz/policies/validate, which
returns line and column diagnostics and the lockout check.
A contractor's access ends on a date:
forbid (
principal in OpenRelay::User::"<userId>",
action,
resource in OpenRelay::Org::"<orgId>"
)
when { context.now > datetime("2026-12-31T00:00:00Z") };API keys only from the VPN:
forbid (
principal is OpenRelay::ApiKey,
action,
resource in OpenRelay::Org::"<orgId>"
)
unless { context.sourceIp.isInRange(ip("10.8.0.0/16")) };No terminating workloads for a role:
forbid (
principal in OpenRelay::Role::"<orgId>/role_<id>",
action == OpenRelay::Action::"compute.delete",
resource in OpenRelay::Org::"<orgId>"
);Members may call only one model:
forbid (
principal in OpenRelay::Role::"member",
action == OpenRelay::Action::"inference.invoke",
resource in OpenRelay::Org::"<orgId>"
)
unless { resource in OpenRelay::Model::"openrelay/laya" };A permit can grant a role more, but it never overrides the platform's
rules: a policy cannot reach another organization, and an API key never gains
a session-only action. A batch's records are checked against the model and
endpoint lists of the key, its role and your organization's model allowlist,
as they were when the batch was submitted; Cedar policies decide the submit
itself, not each record.
When a permission changes
Permission, action and role names are kept stable. When one is renamed, your
roles, keys and policies keep working: the old name means the new one. When
one is retired, a role or key that grants it keeps the rest of what it grants.
A policy that names something that no longer exists is held until you edit
it: a permit grants nothing. A forbid ignores actions that no longer
exist, since no request can carry them, and otherwise blocks at least what it
blocked before: a part of its scope that names a group or type that is gone
covers your whole organization, and conditions that name what is gone are
ignored, so it blocks everything its scope covers. Owners can still review
and repair access. A policy that tests whether a request has an attribute, with has,
must name one the schema declares: such a test is refused when you save, and
a stored policy that has one after a change is held the same way.
The Roles and Policies tabs mark any role or policy that names something
that no longer exists, with what to change. A role marked this way cannot be
given to a member or service account, or used for a new service key, until
you save it again.
Checking access
The access checker explains any decision: who, which action, which policies decided, and why.
curl -X POST https://api.openrelay.inc/v1/authz/check \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"principalType": "api_key", "principalId": "<keyId>", "action": "compute.create"}'{
"allow": false,
"permission": "compute.create",
"deniedBy": "key",
"message": "This API key is limited and does not include compute.create (Compute: Write).",
"policies": [{ "id": "key:<keyId>:permissions", "source": "key_restriction" }]
}GET /v1/authz/effective lists everything
a member, service account or key can do right now. Every change to keys
(other than the dashboard's own short-lived playground and batch keys),
members, roles, service accounts and policies is in the audit log,
GET /v1/audit-events, for 365 days.
When a request is denied
A control-plane denial is 403 with code: FORBIDDEN, plus the action it
needed, the layer that refused it, and the deciding policies. The
OpenRelay-Required-Permission header carries the action too:
{
"error": "This API key is limited and does not include compute.create (Compute: Write).",
"code": "FORBIDDEN",
"permission": "compute.create",
"deniedBy": "key",
"policies": ["key:k_9f2:permissions"]
}deniedBy is role, key, org_policy or platform. The inference API
answers 403 with type: permission_error and code permission_denied
(with permission and denied_by) or model_not_allowed (with model).
See Errors for every code.
Changes take effect on the next request in most cases. If a cache could not be cleared, an organization change can take up to 30 seconds and a key change up to 60 seconds.
Authentication
Authenticate every request with an OpenRelay API key (or_…) sent as a Bearer token. Create, rotate, and revoke keys from the dashboard or the API.
VM billing and burn rate
How VM billing works: one bundled hourly rate per VM, locked when it starts, CPU and GPU sizing, and how long your balance lasts before auto-stop.