Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
GuidesGet started

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.

TermMeaning
ActionOne thing the API does, such as compute.create or inference.invoke. Every API reference page names the action it needs under Required permission.
PermissionAn area and a level, such as Compute: Write. Each level includes the levels before it.
RoleA named set of permissions. Every member has exactly one role.
Personal keyA key owned by a member. It acts as that member, within its own restrictions, and stops working the moment the member leaves.
Service keyA key owned by a service account, for automation that should outlive the people who set it up.
Legacy keyA key created before your organization enabled permissions. Its scopes still decide what it can do.
GuardrailAn organization-wide rule: allowed networks, allowed models, or a maximum key lifetime.
Cedar policyA rule written by an owner in Cedar, checked before it is saved.

Permissions

AreaLevelsAllows
InferenceUseRun chat completions, messages, embeddings and decisions on models the organization offers.
BatchRead, WriteRead: List and read batches and their files. Write: Upload files and create, cancel or tag batches.
Dedicated endpointsReadList dedicated endpoints and their events and series.
ComputeRead, WriteRead: 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 clustersRead, WriteRead: List clusters, node pools and nodes. Write: Create, scale and delete clusters and node pools.
Kubernetes accessView, AdminView: Get a kubeconfig or token with view access inside the cluster. Admin: Get a kubeconfig or token with cluster-admin inside the cluster.
IntegrationsRead, WriteRead: List SSH keys, registry credentials and webhooks. Write: Create, change and delete them, and rotate webhook secrets.
RunnersRead, WriteRead: List GitHub runner pools and jobs. Write: Create, resize and delete runner pools.
ProviderRead, WriteRead: Provider status, nodes, tokens, earnings and reservations. Write: Provisioning tokens, node changes, offboarding and capacity release.
API keysRead, WriteRead: 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 accountsRead, ManageRead: List service accounts. Manage: Create, change, disable and delete service accounts.
MembersRead, WriteRead: List members. Write: Invite people, manage pending invitations, change members' roles and remove members.
AccessRead, WriteRead: See roles, policies, the audit log and the access checker. Write: Create and change custom roles and guardrails.
BillingRead, WriteRead: See the balance, usage, payment methods and auto-recharge. Write: Manage cards, deposits, auto-recharge and spend-limit requests.
OrganizationRead, WriteRead: 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

RoleGrantsCannot
OwnerEverything, including deleting the organization, writing Cedar policies and granting Owner.
AdminInference: 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.
MemberInference: 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.
BillingBilling: Write, Compute: Read, Members: Read, Organization: Read.Run workloads, call inference, create keys.
InferenceInference: Use, Batch: Write, Dedicated endpoints: Read, API keys: Write, Billing: Read, Organization: Read.Launch GPUs, clusters or pods; see payment methods.
ViewerBatch: 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
  }'
  • template picks a preset by what the key is for:

    TemplateForPermissions
    inferenceCalling modelsInference: Use
    inference_batchCalling models and running batch jobsInference: Use, Batch: Write
    compute_automationCI/CD deploysCompute: Write, Kubernetes clusters: Write, Kubernetes access: Admin, Integrations: Write
    read_onlyMonitoring and reportsBatch: 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 tokensProvider: Write
    same_as_ownerEverything you can doNo ceiling: the key follows its owner's role as it changes

    With permissions enabled, same_as_owner is refused with 400 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 with 403 ESCALATION_REFUSED; the response names the missing permission.

  • restrictions narrows the key further: models (model ids), endpoints (dedicated endpoint ids), and ipRanges (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:

ScopeCeiling
anyInference Use, Batch Write, and reaching a workload's HTTP endpoint
clusters:readevery Read level except Service accounts and Access
clusters:writeCompute, 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

GuardrailEffectWho 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 lifetimeNew 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.

On this page