For the complete documentation index, see llms.txt. This page is also available as Markdown.

Access policies API

Create, read, update, and delete the access policies that route just-in-time access requests to approval paths — with the endpoints, schema, authentication, errors, and worked examples you need to imp

The Access Policies API gives you programmatic control over how just-in-time access requests are evaluated and approved. Each access policy is a rule that matches on the requestor (who is asking) and the resource (what they want), then routes matching requests down an approval path — manual approval, auto-approval for on-call engineers, standing access, or an outright deny.

Use this API to manage policies as code: read your current rules, add or change a single rule by name, or replace your whole configuration in one request. For the product concepts, evaluation order, and the full filter reference, see Configuring access policies.

Base URL and authentication

Every endpoint lives under your organization's base URL, which includes your organization slug (orgId):

https://api.p0.app/o/{orgId}

All requests require a bearer token. See Authenticating with the P0 API to get one — from a Google Cloud service-account token (recommended), the P0 CLI, or a legacy API key — and pass it in the Authorization header:

curl -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/{orgId}/policy

For the base URL, authentication, and permissions shared by all P0 APIs, see the P0 API overview.

Permissions

Action
Required permission
Roles that have it

Read policies (GET)

policies.version.read

Owner, Manager

Change policies (POST, PUT, DELETE)

policies.version.add

Owner

A token's privileges come from the P0 role of its identity, so grant each identity the least privilege it needs. A legacy API key carries the full Owner role and can call every endpoint on this page. A caller who is authenticated but lacks the permission — for example, a Manager calling a write endpoint — gets 403 Forbidden, not 401.

Two ways to manage policies

Your organization has one active policy configuration — a PolicyConfig, which is an object with a rules array of individual Policy rules. This API exposes that configuration two ways:

  • A single rule at a time, addressed by its name, under /policy/name/{name}. Use POST, PUT, and DELETE to add, change, or remove one rule without touching the rest. This is the most direct way to automate incremental changes.

  • The whole configuration at once, under /policy. POST replaces the entire rule set. Use this for config-as-code workflows that manage all rules together.

The two shapes carry different bodies: the /policy/name/{name} endpoints send and receive a single Policy, while POST /policy sends a PolicyConfig wrapped with a currentVersion for optimistic concurrency.

P0 keeps a version history of the configuration. Every write — whether through /policy/name/{name} or POST /policy — creates a new configuration version. A single-rule write still versions the whole configuration; it just leaves the other rules unchanged. To read the active configuration and the version you need for optimistic concurrency, get latest.

Rule names match case-insensitively. Creating Engineering-AWS when engineering-aws already exists returns 409 Conflict, and a GET by either casing finds the same rule.

Endpoints

List stored policy configurations

get

Returns every document in the organization's policy store, each with its rules and metadata. The result is unordered, and it includes more than version snapshots: the active-configuration pointer is returned with id: "latest" (and carries a version), and an options document is returned with id: "_default_" and no rules. To read the active configuration and its version, get latest instead (GET /policy/latest).

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Responses
200

The documents in the organization's policy store.

application/json
get/policy
GET /o/demo-org/policy HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "policies": [
    {
      "rules": [
        {
          "name": "engineering-aws-readonly",
          "disabled": true,
          "requestor": {
            "type": "any"
          },
          "resource": {
            "type": "any"
          },
          "approval": [
            {
              "type": "p0",
              "options": {
                "allowOneParty": true,
                "breakGlassApprover": true,
                "requireReason": true,
                "requireDuration": true,
                "requirePreapproval": true
              }
            }
          ]
        }
      ],
      "id": "text",
      "version": "text",
      "createdDate": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Replace the policy configuration

post

Replaces the entire active policy configuration with a new set of rules and creates a new version. Use currentVersion for optimistic concurrency: pass the version from GET /policy/latest (or from a prior write's response), and the request fails with 409 if the configuration changed in the meantime.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Body

A request to replace the active policy configuration.

currentVersionstringRequired

The version of the configuration you are editing. If it no longer matches the active configuration, the request fails with 409 so you can reload and reapply your edits.

Responses
200

The new policy configuration, including its version ID.

application/json
post/policy
POST /o/demo-org/policy HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 307

{
  "currentVersion": "text",
  "policy": {
    "rules": [
      {
        "name": "engineering-aws-readonly",
        "disabled": true,
        "requestor": {
          "type": "any"
        },
        "resource": {
          "type": "any"
        },
        "approval": [
          {
            "type": "p0",
            "options": {
              "allowOneParty": true,
              "breakGlassApprover": true,
              "requireReason": true,
              "requireDuration": true,
              "requirePreapproval": true
            }
          }
        ]
      }
    ]
  }
}
{
  "policy": {
    "rules": [
      {
        "name": "engineering-aws-readonly",
        "disabled": true,
        "requestor": {
          "type": "any"
        },
        "resource": {
          "type": "any"
        },
        "approval": [
          {
            "type": "p0",
            "options": {
              "allowOneParty": true,
              "breakGlassApprover": true,
              "requireReason": true,
              "requireDuration": true,
              "requirePreapproval": true
            }
          }
        ]
      }
    ],
    "version": "text"
  }
}

Get a policy configuration

get

Returns a stored policy configuration by its ID. Pass a version ID to read that snapshot, or the literal latest to read the active configuration together with its version.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
policyIdstringRequired

A policy configuration version ID, returned as version by the create endpoint. Pass the literal latest to read the active configuration together with its version — this is the only way to read the active configuration and the version you need for optimistic concurrency.

Example: latest
Responses
200

The requested policy configuration.

application/json
get/policy/{policyId}
GET /o/demo-org/policy/{policyId} HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "policy": {
    "rules": [
      {
        "name": "engineering-aws-readonly",
        "disabled": true,
        "requestor": {
          "type": "any"
        },
        "resource": {
          "type": "any"
        },
        "approval": [
          {
            "type": "p0",
            "options": {
              "allowOneParty": true,
              "breakGlassApprover": true,
              "requireReason": true,
              "requireDuration": true,
              "requirePreapproval": true
            }
          }
        ]
      }
    ],
    "id": "text",
    "version": "text",
    "createdDate": "2026-01-01T00:00:00.000Z"
  }
}

Get a policy rule

get

Returns a single policy rule from the active configuration by its name.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
namestringRequired

The policy rule's unique name. Cannot contain a forward slash (/) or backslash (\). Names match case-insensitively, so Engineering-AWS and engineering-aws refer to the same rule.

Example: engineering-aws-readonly
Responses
200

The requested policy rule.

application/json

A single access-control policy rule.

namestringOptional

A unique, human-readable name for the rule. Cannot contain / or \.

Example: engineering-aws-readonly
disabledbooleanOptional

If true, P0 skips this rule during evaluation. Defaults to false.

requestorone ofRequired

Matches who is making the request.

or
or
or
resourceone ofRequired

Matches which resource the request targets.

or
get/policy/name/{name}
GET /o/demo-org/policy/name/{name} HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
  "name": "engineering-aws-readonly",
  "disabled": true,
  "requestor": {
    "type": "any"
  },
  "resource": {
    "type": "any"
  },
  "approval": [
    {
      "type": "p0",
      "options": {
        "allowOneParty": true,
        "breakGlassApprover": true,
        "requireReason": true,
        "requireDuration": true,
        "requirePreapproval": true
      }
    }
  ]
}

Create a policy rule

post

Adds a single named rule to the active configuration and creates a new configuration version. The name in the body must match the name in the path. Fails with 409 if a rule with that name already exists — use PUT to change an existing rule.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
namestringRequired

The policy rule's unique name. Cannot contain a forward slash (/) or backslash (\). Names match case-insensitively, so Engineering-AWS and engineering-aws refer to the same rule.

Example: engineering-aws-readonly
Body

A single access-control policy rule.

namestringOptional

A unique, human-readable name for the rule. Cannot contain / or \.

Example: engineering-aws-readonly
disabledbooleanOptional

If true, P0 skips this rule during evaluation. Defaults to false.

requestorone ofRequired

Matches who is making the request.

or
or
or
resourceone ofRequired

Matches which resource the request targets.

or
Responses
201

The created policy rule.

application/json

A single access-control policy rule.

namestringOptional

A unique, human-readable name for the rule. Cannot contain / or \.

Example: engineering-aws-readonly
disabledbooleanOptional

If true, P0 skips this rule during evaluation. Defaults to false.

requestorone ofRequired

Matches who is making the request.

or
or
or
resourceone ofRequired

Matches which resource the request targets.

or
post/policy/name/{name}
POST /o/demo-org/policy/name/{name} HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 260

{
  "name": "engineering-aws-readonly",
  "disabled": true,
  "requestor": {
    "type": "any"
  },
  "resource": {
    "type": "any"
  },
  "approval": [
    {
      "type": "p0",
      "options": {
        "allowOneParty": true,
        "breakGlassApprover": true,
        "requireReason": true,
        "requireDuration": true,
        "requirePreapproval": true
      }
    }
  ]
}
{
  "name": "engineering-aws-readonly",
  "disabled": true,
  "requestor": {
    "type": "any"
  },
  "resource": {
    "type": "any"
  },
  "approval": [
    {
      "type": "p0",
      "options": {
        "allowOneParty": true,
        "breakGlassApprover": true,
        "requireReason": true,
        "requireDuration": true,
        "requirePreapproval": true
      }
    }
  ]
}

Update a policy rule

put

Replaces an existing named rule in the active configuration and creates a new configuration version. The name in the body must match the name in the path. Fails with 404 if no rule with that name exists — use POST to create one.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
namestringRequired

The policy rule's unique name. Cannot contain a forward slash (/) or backslash (\). Names match case-insensitively, so Engineering-AWS and engineering-aws refer to the same rule.

Example: engineering-aws-readonly
Body

A single access-control policy rule.

namestringOptional

A unique, human-readable name for the rule. Cannot contain / or \.

Example: engineering-aws-readonly
disabledbooleanOptional

If true, P0 skips this rule during evaluation. Defaults to false.

requestorone ofRequired

Matches who is making the request.

or
or
or
resourceone ofRequired

Matches which resource the request targets.

or
Responses
200

The updated policy rule.

application/json

A single access-control policy rule.

namestringOptional

A unique, human-readable name for the rule. Cannot contain / or \.

Example: engineering-aws-readonly
disabledbooleanOptional

If true, P0 skips this rule during evaluation. Defaults to false.

requestorone ofRequired

Matches who is making the request.

or
or
or
resourceone ofRequired

Matches which resource the request targets.

or
put/policy/name/{name}
PUT /o/demo-org/policy/name/{name} HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 260

{
  "name": "engineering-aws-readonly",
  "disabled": true,
  "requestor": {
    "type": "any"
  },
  "resource": {
    "type": "any"
  },
  "approval": [
    {
      "type": "p0",
      "options": {
        "allowOneParty": true,
        "breakGlassApprover": true,
        "requireReason": true,
        "requireDuration": true,
        "requirePreapproval": true
      }
    }
  ]
}
{
  "name": "engineering-aws-readonly",
  "disabled": true,
  "requestor": {
    "type": "any"
  },
  "resource": {
    "type": "any"
  },
  "approval": [
    {
      "type": "p0",
      "options": {
        "allowOneParty": true,
        "breakGlassApprover": true,
        "requireReason": true,
        "requireDuration": true,
        "requirePreapproval": true
      }
    }
  ]
}

Delete a policy rule

delete

Removes a single named rule from the active configuration and creates a new configuration version.

Authorizations
AuthorizationstringRequired
Bearer authentication header of the form Bearer <token>.
Path parameters
namestringRequired

The policy rule's unique name. Cannot contain a forward slash (/) or backslash (\). Names match case-insensitively, so Engineering-AWS and engineering-aws refer to the same rule.

Example: engineering-aws-readonly
Responses
204

The rule was deleted.

No content

delete/policy/name/{name}
DELETE /o/demo-org/policy/name/{name} HTTP/1.1
Host: api.p0.app
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*

No content

Policy schema

A Policy has three required parts and two optional fields:

Field
Type
Required
Description

name

string

No

A unique, human-readable name. Cannot contain / or \. Required to address the rule under /policy/name/{name}.

disabled

boolean

No

If true, P0 skips the rule during evaluation. Defaults to false.

requestor

RequestorRule

Yes

Matches who is making the request: any, user, group, or agentic.

resource

ResourceRule

Yes

Matches the target: any or a specific integration.

approval

ApprovalRule[]

Yes

The approval path. At least one non-break-glass approver is required. A single deny rule denies all matching requests.

The JSON here is the API's wire format. When you match a directory group, requestor and group approvers use a groups array with an effect, for example { "type": "group", "effect": "keep", "groups": [ ... ] }. Policy objects reject unknown fields, so a mistyped or extra property returns 400 Bad Request. For the complete rule reference — every requestor, resource, filter, and approval type, plus evaluation order — see Configuring access policies.

Examples

The following examples assume your bearer token is in the P0_API_TOKEN environment variable and your organization slug is your-org. See Authenticating with the P0 API to get a token.

Read a single rule

The response is the matching Policy:

Create a rule

Add a rule by POSTing a Policy to /policy/name/{name}. The name in the body must match the name in the URL. This example routes the engineering group's non-admin AWS permission-set requests to your Security Reviewers, and requires a reason:

A successful create returns 201 Created and echoes the stored rule. If a rule with that name already exists, the request fails with 409 Conflict — use PUT to change it instead.

Update a rule

PUT replaces an existing rule. This example changes the approver to the SRE directory group and lets on-call engineers self-approve:

A successful update returns 200 OK with the stored rule. If no rule with that name exists, the request fails with 404 Not Found — use POST to create it.

Delete a rule

A successful delete returns 204 No Content with an empty body.

Read the active configuration

To read the active configuration with the version you need for a concurrent-safe write, get the special latest configuration:

The response wraps the active configuration, its version, and metadata:

GET /policy (without latest) lists every stored document, not just versions, and in no particular order. It also returns the latest pointer (id: "latest") and an internal options document (id: "_default_", with no rules). Use GET /policy/latest to read the current configuration, and a specific version ID to read a past snapshot. Do not use a version ID from the list as currentVersion — the concurrency check compares against the version stored on latest.

Replace the whole configuration

POST /policy replaces every rule at once and creates a new configuration version. To avoid overwriting a concurrent change, pass the version from GET /policy/latest as currentVersion. Submit the new configuration:

The response contains the stored configuration and the new version:

If the active configuration changed since you read currentVersion, the request fails with 409 Conflict and the message "Policies have changed since your edits. Please reload rules and apply these edits again." Reload with GET /policy/latest, reapply your edits, and retry.

Errors

Status
Meaning

400 Bad Request

The body failed schema validation — for example, the name contains a slash, the name in the body does not match the URL, service is not a known integration, a filter's key is invalid, or the body includes an unknown field. The response describes the problem.

401 Unauthorized

The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization (it holds no P0 role). Add the identity — for example, a service account's email — as a member in Role-Based Access Control. See Authenticating with the P0 API.

403 Forbidden

The token is valid and the identity is a member, but it lacks the required permission — for example, a Manager calling a write endpoint.

404 Not Found

No rule matches the name, or no configuration matches the policyId.

409 Conflict

A rule with that name already exists (on create), more than one rule matches the name (on get, update, or delete), or the supplied currentVersion no longer matches the active configuration.

422 Unprocessable Entity

A filter pattern (or an agentic subjectPattern) is not a valid regular expression, or is unsafe because it is subject to catastrophic backtracking.

Last updated