Managing policies through the REST API#
Permission Service exposes a REST API under /v1beta/policies/ for managing Cedar policies at runtime. Use it to list, fetch, create, and delete the policies the service evaluates. This page describes each endpoint, its parameters, request and response shapes, and the status codes callers should expect.
For the Cedar syntax used in the policy field, see Writing Cedar policies for Permission Service. For how policies are loaded from Helm or YAML at startup, see Deployment and Database configuration.
Overview#
The policy management API is a CRUD surface over Cedar policy records:
Method |
Path |
Purpose |
|---|---|---|
GET |
|
List policies with offset pagination and filters. |
GET |
|
Fetch a single policy by numeric ID. |
PUT |
|
Add a single policy. |
PUT |
|
Add many policies in one request. |
DELETE |
|
Remove a policy by numeric ID. |
Key behaviors that apply to all endpoints:
Storage backend. Write operations require Postgres mode. In config-file mode the storage is read-only and write endpoints return
501 Not Implemented. See Deployment for how to select a backend.Scopes are inferred from the Cedar text. When you add a policy, the service parses the policy head and derives the optional principal, action, and resource scopes. You do not send these fields; any extra JSON fields in the payload are ignored. Scope inference rules are described in Policy scopes.
Optional policy validation. When the service is started with
--policy-validation(envPOLICY_VALIDATION=true), each submitted policy is validated against registered service metadata before it is stored. Validation failures return400 Bad Request.Resource ID normalization. When a policy pins a resource (for example
resource == ResourceAddress::"https://example.com/file name.usd"), the service URL-encodes the resource id before storage. The same encoding is applied to theresourcequery parameter onGET /v1beta/policies/, so clients should pass the id in the same textual form the API returns.Cache invalidation and events. Successful writes invalidate the in-memory evaluation cache for the affected
(principal, action, resource)tuple and, when notifications are enabled, publish a permission-change event.
Policy record#
All endpoints that return policies use the PolicyResponse shape:
{
"id": 1,
"order": 0,
"policy": "permit(principal == Principal::\"test-user\", action == Action::\"tags:get\", resource == ResourceAddress::\"Astronaut.usd\");",
"resource": {
"id": "Astronaut.usd",
"type": "ResourceAddress",
"data": null
},
"action": {
"name": "get",
"service": "tags"
},
"principal": {
"sub": "test-user",
"info": null
},
"created_at": "2026-04-16T12:34:56Z",
"created_by": ""
}
Field reference:
Field |
Type |
Description |
|---|---|---|
|
integer ( |
Unique policy identifier, assigned by the service. |
|
integer ( |
Evaluation order; lower values are evaluated first. |
|
string |
The Cedar policy text (exactly one statement). |
|
object | |
Principal scope inferred from the policy head, or |
|
object | |
Action scope inferred from the policy head, or |
|
object | |
Resource scope inferred from the policy head, or |
|
string (RFC 3339 timestamp) |
When the policy was created. |
|
string |
Principal id of the caller that created the policy; empty string if the service was running with authentication disabled. |
GET /v1beta/policies/ — list policies#
Returns policies from the database using offset pagination. Optional query parameters narrow the result set by scope.
Query parameters#
Name |
Type |
Default |
Description |
|---|---|---|---|
|
integer |
|
1-based page number. Must be |
|
integer |
|
Page size. Must be in |
|
string |
— |
Filter by principal id. Use the literal string |
|
string |
— |
Filter by action in Cedar form, for example |
|
string |
— |
Filter by resource in Cedar form, for example |
The principal, action, and resource filters are parsed from their string form and must be syntactically valid; invalid values return 400 Bad Request. When a filter is omitted, policies with any scope on that dimension (including unset) match.
Request#
GET /v1beta/policies/?page=1&limit=10 HTTP/1.1
Host: permission-service.storage-apis.svc.cluster.local:3000
Authorization: Bearer <token>
Responses#
Status |
Body |
Meaning |
|---|---|---|
|
|
Page of policies returned. |
|
error message (string) |
Invalid |
|
— |
Missing or invalid bearer token (auth enabled). |
|
error message (string) |
Caller lacks |
|
error message (string) |
Query parameter failed deserialization (for example |
|
error message (string) |
Storage failure while reading policies. |
Successful response shape:
{
"items": [ /* array of PolicyResponse */ ],
"page": 1,
"page_size": 10,
"page_count": 3
}
page_size reflects the number of items returned on this page. page_count is the total number of pages when the backend can compute it; it may be null for backends that do not report a total.
GET /v1beta/policies/{id} — fetch a policy#
Returns the PolicyResponse for a single policy.
Path parameters#
Name |
Type |
Description |
|---|---|---|
|
integer ( |
Policy identifier, as returned by previous calls. |
Request#
GET /v1beta/policies/42 HTTP/1.1
Host: permission-service.storage-apis.svc.cluster.local:3000
Authorization: Bearer <token>
Responses#
Status |
Body |
Meaning |
|---|---|---|
|
|
Policy found. |
|
— |
Missing or invalid bearer token (auth enabled). |
|
error message (string) |
Caller lacks |
|
error message (string) |
No policy with that id exists. |
|
error message (string) |
|
PUT /v1beta/policies/ — add a policy#
Adds a single Cedar policy. The principal, action, and resource scopes are inferred from the Cedar text; there is no need (and no way) to send them explicitly.
Request body#
Field |
Type |
Required |
Description |
|---|---|---|---|
|
string |
yes |
Cedar policy text. Must contain exactly one |
|
integer |
no |
Evaluation order to assign to the stored policy. Defaults to the service’s |
Any additional JSON fields in the body are silently ignored.
Request#
PUT /v1beta/policies/ HTTP/1.1
Host: permission-service.storage-apis.svc.cluster.local:3000
Authorization: Bearer <token>
Content-Type: application/json
{
"policy": "permit(principal == Principal::\"test-user\", action == Action::\"tags:get\", resource == ResourceAddress::\"Astronaut.usd\");",
"order": 10
}
Responses#
Status |
Body |
Meaning |
|---|---|---|
|
|
Policy stored; the response includes the assigned |
|
error message (string) |
The Cedar text failed to parse, policy validation (when enabled) rejected it, or the storage backend rejected the record (for example, a duplicate). |
|
— |
Missing or invalid bearer token (auth enabled). |
|
error message (string) |
Caller lacks |
|
error message (string) |
JSON body failed to deserialize (for example missing |
|
error message (string) |
Storage failure while writing the policy. |
|
error message (string) |
The configured storage backend does not support writes (for example config-file mode). |
PUT /v1beta/policies/batch/ — add many policies#
Batched version of the single-policy endpoint. The request body is a JSON array; each element is the same object accepted by PUT /v1beta/policies/. Scopes are inferred per item from the Cedar text.
The service validates every item up front. If any item fails validation or scope inference, the whole request is rejected before any policy is written.
A single request can carry at most 100 items. Larger arrays are rejected with 422 Unprocessable Entity; split the work across multiple requests on the caller side.
Request#
PUT /v1beta/policies/batch/ HTTP/1.1
Host: permission-service.storage-apis.svc.cluster.local:3000
Authorization: Bearer <token>
Content-Type: application/json
[
{
"policy": "permit(principal == Principal::\"test-user-1\", action == Action::\"tags:get\", resource == ResourceAddress::\"Astronaut.usd\");"
},
{
"policy": "permit(principal == Principal::\"test-user-2\", action == Action::\"tags:get\", resource == ResourceAddress::\"Astronaut.usd\");",
"order": 5
}
]
An empty array is accepted and produces an empty result without contacting the storage backend.
Responses#
Status |
Body |
Meaning |
|---|---|---|
|
|
All policies stored. |
|
error message (string) |
Cedar parsing, validation, or storage rejected an item. Error messages are prefixed with |
|
— |
Missing or invalid bearer token (auth enabled). |
|
error message (string) |
Caller lacks |
|
error message (string) |
An item failed per-field validation (for example, |
|
error message (string) |
Storage failure while writing the batch. |
|
error message (string) |
Storage backend does not support writes. |
Successful response shape:
{
"results": [ /* array of PolicyResponse, in the same order as the request */ ]
}
DELETE /v1beta/policies/{id} — delete a policy#
Removes a Cedar policy from the database. The endpoint is idempotent: deleting a policy that does not exist still returns 204 No Content.
Path parameters#
Name |
Type |
Description |
|---|---|---|
|
integer ( |
Policy identifier. |
Request#
DELETE /v1beta/policies/42 HTTP/1.1
Host: permission-service.storage-apis.svc.cluster.local:3000
Authorization: Bearer <token>
Responses#
Status |
Body |
Meaning |
|---|---|---|
|
— |
Policy removed, or no policy with that id existed. |
|
— |
Missing or invalid bearer token (auth enabled). |
|
error message (string) |
Caller lacks |
|
error message (string) |
|
|
error message (string) |
Storage failure while deleting the policy. |
|
error message (string) |
Storage backend does not support writes. |
Change notifications#
When notifications are enabled, every successful write (PUT /v1beta/policies/, PUT /v1beta/policies/batch/, and DELETE /v1beta/policies/{id}) causes the service to publish a permission-change event to the configured Event Aggregation Service. Each event identifies the (principal, action, resource) tuple whose effective policies may have changed, so downstream services can invalidate their own caches or otherwise react to the update. Failed or rejected writes do not emit events.
Notifications are best-effort and do not block the API response: a caller receives its success status as soon as the policy is persisted, and the event is dispatched asynchronously.
For the event schema, delivery guarantees, and how to configure the Event Aggregation Service endpoint, see Service notifications.
Interactive API reference#
When the service runs with the REST API enabled, the full OpenAPI definition is served at /openapi.json and an interactive Swagger UI is available at /swagger-ui on the REST port (default 3000). Those surfaces stay in sync with the service implementation and cover every field and schema referenced on this page.
See also#
Writing Cedar policies for Permission Service — syntax, scopes, and conditions.
How authorization requests are evaluated — how
orderand resource evaluation priority drive the decision for stored policies.Deployment — selecting Postgres vs config-file mode and seeding initial policies.
Database configuration — on-disk policy format and scope inference details.