# What is P0?

P0 Security is a cloud-native Privileged Access Management platform. Enforce just-in-time access and least-privilege governance across multi-cloud environments.

Welcome to the P0 documentation. We're glad you're here.

P0 Security is a next-generation Privileged Access Management (PAM) platform purpose-built for today's hybrid, multi-cloud, and developer-driven environments. It unifies governance, just-in-time (JIT) access, and access orchestration across all identity types, human, non-human, and AI agents, while eliminating the complexity and fragmentation of legacy tooling.

At the core of P0 is a continuously updated Identity Graph that connects identity data from your directories, identity providers, cloud identity services, and key applications. It gives teams a clear, accurate view of every identity, what it can access, and how those permissions are granted. P0 leverages this identity graph to enable fine-grained, ephemeral access, policy-based automation, and continuous risk-aware governance, whether you're managing cloud workloads, on-prem infrastructure, or developer tools like GitHub and Jenkins.

With agentless JIT, integrated workflows (Slack, CLI, GitHub), and support for modern app architectures, P0 delivers powerful access control without slowing teams down, redefining PAM for the cloud-native, AI-augmented era.

## Get started

New to P0? Start here based on your role:

| Your goal                           | Start here                                                                                                                                                   |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Set up P0 for your organization** | [P0 Onboarding guide](/getting-started/p0-security-onboarding): account setup, integrations, and first scan                                                  |
| **Enable just-in-time access**      | [Getting started with just-in-time access](/getting-started/getting-started-with-just-in-time-access): install, configure approvals, make your first request |
| **Discover your access posture**    | [Getting started with Posture](/getting-started/getting-started-with-posture): connect your cloud, run your first scan, and triage findings                  |
| **Explore your access inventory**   | [Getting started with Access Inventory](/getting-started/getting-started-with-access-inventory): connect your cloud and query your access graph              |
| **Request access to a resource**    | [Requesting access](/access-management/just-in-time-access/requesting-access): request access via Slack, the web app, or the CLI                             |
| **Use the P0 CLI**                  | [Installing the P0 CLI](/p0-cli/installing-p0-cli): install and authenticate the command-line tool                                                           |

**P0 Capabilities:**

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><a data-mention href="/pages/fQpbbGMQOmIuAkMUWvaH">/pages/fQpbbGMQOmIuAkMUWvaH</a></td><td>The P0 Dashboard offers a unified view of your onboarded infrastructure, summarizing discovered identities, key posture findings, JIT access metrics, and high-frequency requests in a single pane of glass. It helps teams continuously improve access posture and reduce time-to-access, while also delivering clear, leadership-friendly reporting to show progress and risk reduction.</td><td></td><td></td></tr><tr><td><a data-mention href="/pages/Gtv7FF0r3wGs1iPQlkiM">/pages/Gtv7FF0r3wGs1iPQlkiM</a></td><td>Lists identities, entitlements, and resources in your cloud.</td><td>Analyze relationships between these assets to answer questions like who has overprivileged access, access to sensitive resources, or discover complex lateral movement paths.</td><td><a href="/files/YZ8d0Xt93rPI8TvMbrYq">/files/YZ8d0Xt93rPI8TvMbrYq</a></td></tr><tr><td><a data-mention href="/pages/e2hny5Ivp27MvmR6YIiR">/pages/e2hny5Ivp27MvmR6YIiR</a></td><td>Discover access vulnerabilities in your cloud.</td><td>P0 analyzes access across systems, including IdPs, clouds, and managed services (like Kubernetes) to discover access security vulnerabilities.</td><td><a href="/files/KImQQHa8C2mzHConKJmq">/files/KImQQHa8C2mzHConKJmq</a></td></tr><tr><td><a data-mention href="/pages/WsKD3kAwkLlLemZ66gzA">/pages/WsKD3kAwkLlLemZ66gzA</a></td><td>Give members of your organization access to perform specific tasks for just as long as they need it.</td><td>On average, members gain access within 5 minutes of request, including approval time.</td><td><a href="/files/1iNb1xN2YZYoyK8lV6w0">/files/1iNb1xN2YZYoyK8lV6w0</a></td></tr><tr><td><a data-mention href="/pages/6HPbVZFQzDkPANdeZLvV">/pages/6HPbVZFQzDkPANdeZLvV</a></td><td>Runtime authorization for what your AI agents can do across sensitive systems.</td><td>The P0 AuthZ Control Plane for Agents provides runtime authorization across the full agent action chain. P0 verifies the originator and the agent behind every MCP tool call, evaluates policy before each action through the self-hosted P0 AI Gateway, and ties every action to a named person, a specific agent, and the policy that governed it.</td><td></td></tr><tr><td><a data-mention href="/pages/KpIZuocJSZsC39c6tb36">/pages/KpIZuocJSZsC39c6tb36</a></td><td>Manage rotation of static service-account credentials.</td><td>P0 discovers service-account owners, creates fresh credentials and manages your credential vault. P0 integrates with your ticketing system to ensure that owners update credentials in 3rd-party systems before rotation.</td><td><a href="/files/FXbdyR31dvkLdnmSpoNd">/files/FXbdyR31dvkLdnmSpoNd</a></td></tr></tbody></table>


# P0 dashboard

Track access security with automated reporting. View identities, posture findings, JIT access metrics, and request trends from the P0 dashboard.

P0 provides automated reporting so you can track your progress toward improving access security.

### Features

* Track access-vulnerability findings burndown over time
* View top access vulnerabilities at a glance
* View the breakdown of IAM assets in your environment
* Track your organization's use of just-in-time access, and ensure rapid time-to-access

<figure><img src="/files/fFIhzxqmlrTyizWNCm43" alt="P0 dashboard showing posture findings chart, inventory identity breakdown, and just-in-time access metrics including approval time and request frequency" width="563"><figcaption></figcaption></figure>

### Explore dashboard data sources

The dashboard surfaces data from across P0's capabilities. Dive deeper into each area:

* [Access inventory](/inventory/access-inventory): Search and analyze identities, entitlements, and resources
* [Posture overview](/posture/posture-overview): Review access-vulnerability findings and track remediation
* [Just-in-time access](/access-management/just-in-time-access): Monitor access requests, approvals, and usage metrics

### Get started

New to P0? Follow the [Getting started with Access Inventory](/getting-started/getting-started-with-access-inventory) or [Getting started with Posture](/getting-started/getting-started-with-posture) guide to connect your cloud and populate the dashboard.


# Access inventory

Discover production assets, identities, and access risks across AWS, Google Cloud, Kubernetes, Okta, and Google Workspace with P0's access inventory.

Use P0's access inventory to view your production assets, all the identities that can access them, and associated access risks.

*IAM assessment is currently available for AWS, Google Cloud, Kubernetes, Okta, and Google Workspace.*

### Features

* Combines IAM data from disparate sources, including your identity provider, your IAM policies, and your cloud access logs
* Evaluates issues based on risks of individual privileges, using P0's comprehensive open-source [IAM Privilege Catalog](https://catalog.p0.dev)
* Detects lateral movement across identities and clouds

### How it works in P0

P0 connects to your IAM systems, reading your configuration.

<div data-full-width="false"><figure><img src="/files/CUPaxJvJkGcUtKNn5vOn" alt="P0 data collection progress indicator showing Collecting Data at 3% while fetching IAM policy data" width="375"><figcaption></figcaption></figure></div>

P0 builds a complete end-to-end access graph for your environment:

<figure><img src="/files/YZ8d0Xt93rPI8TvMbrYq" alt="Access inventory graph visualization showing identities, entitlements, and resources connected by lateral movement paths" width="563"><figcaption></figcaption></figure>

You can query your access graph to find sensitive access to specific resources, specific risks, or more:

<figure><img src="/files/Sq4QCPWWtLJ14mnLW61K" alt="Inventory query results table filtered by storage exfiltration risk showing principals, roles, and affected resources" width="563"><figcaption></figcaption></figure>

### Related documentation

* [Access inventory details](/inventory/access-inventory): Search your inventory, view asset lists, and explore the access graph
* [Query search](/inventory/query-search): Write queries to find specific identities, entitlements, and resources
* [Posture overview](/posture/posture-overview): See access-vulnerability findings detected from your inventory data

### Get started with P0's access inventory

Getting set up and collecting your organization's inventory takes about 15 minutes.

1. Sign up for a P0 account at [p0.app/create-account](https://p0.app/create-account).
2. Follow the instructions in [Creating an environment](/environments/creating-an-environment).
3. For a complete walkthrough, see the [Getting started with Access Inventory](/getting-started/getting-started-with-access-inventory) guide.


# Posture

Automatically scan your cloud environment for access risks. P0 Security detects overprivileged accounts, stale credentials, and security vulnerabilities.

With Posture, P0 automatically scans your environment for access risks.

### Features

* Detects access vulnerabilities from authentication, authorization, and lateral movement
* Automatically determines finding assignees based on fine-grained resource ownership
* Provides automated remediation suggestions and lets users document acceptable risks

### How it works

P0 automatically scans your access graph for issues after collecting your [Access inventory](/readme/access-inventory).

<figure><img src="/files/KImQQHa8C2mzHConKJmq" alt="Posture findings list showing monitors ranked by severity with finding counts for unused privileges, IAM roles, and service accounts" width="563"><figcaption></figcaption></figure>

For each finding, you can view the attack path, assign findings, and review auto-generated fixes.

<figure><img src="/files/KXPFDuxRg6azC1eIJCgz" alt="Split view showing a posture monitor with findings list on the left and the selected finding&#x27;s attack path and details on the right" width="563"><figcaption></figcaption></figure>

### Related documentation

* [Posture overview](/posture/posture-overview): Filter, export, and manage posture findings in detail
* [Monitor results](/posture/monitor-results): View individual monitor results and take bulk actions
* [Finding details](/posture/finding-details): Inspect attack paths, assign findings, and review suggested fixes

### Getting started

P0 automatically collects posture findings after you follow [Creating an environment](/environments/creating-an-environment). For a Posture-focused walkthrough that covers running your first scan and triaging findings, see [Getting started with Posture](/getting-started/getting-started-with-posture). The same scan also powers [Access Inventory](/inventory/access-inventory). To explore your access graph, see [Getting started with Access Inventory](/getting-started/getting-started-with-access-inventory).


# Just-in-time access

Grant tailored, short-lived privileges to specific cloud resources on demand. Reduce standing access and enforce least privilege with P0 Security's JIT access.

Use just-in-time access to give tailored, short-lived privileges to specific resources on demand.

### Benefits of just-in-time access

* Reduce your IAM attack surface by only holding privileges when they are in use.
* Grant access more quickly; the mean time between making an access request and gaining access using P0, including approval, is 5 minutes.
* Request and grant access based on operations (like reading a bucket), eliminating the need to craft custom roles and policies, and preventing incorrect access grants.

### How it works in P0

Once you connect P0 to your organization's Slack and an IAM resource, any member of your organization can request access by typing `/p0 request` in Slack. No further set-up is required.

Here's what it looks like:

<figure><img src="/files/hWevj6Ab2ol9xFuNjwon" alt="Animated demo of a just-in-time access request flow using the /p0 request Slack command, showing request, approval, and access grant" width="543"><figcaption></figcaption></figure>

Requests can be approved by managers, resource owners, or automatically by integrations such as PagerDuty. You can discuss justification for each request in Slack. Access requests, together with their justifications, approval, grant, and revocation histories are recorded by P0, and can be exported to your SIEM.

### Related documentation

* [Just-in-time access overview](/access-management/just-in-time-access): Configure and manage JIT access workflows
* [Requesting access](/access-management/just-in-time-access/requesting-access): Learn how to request access via Slack or the web app
* [Approving access](/access-management/just-in-time-access/approving-access): Set up approval workflows and pre-approvals
* [Access policies](/access-management/just-in-time-access/access-policies): Route, auto-approve, or deny requests based on policies
* [P0 CLI commands](/p0-cli/p0-commands-and-usage): Request and manage access from the command line

### Get started with just-in-time access

Getting started with just-in-time access takes about 15 minutes. Follow the [quick-start guide](/getting-started/getting-started-with-just-in-time-access).


# P0 connectors

Understand P0 connectors: stateless, versioned runtimes you deploy in your own cloud account to broker just-in-time access, so no standing credentials sit on laptops or in P0's SaaS.

A **connector** is a small piece of P0 software that runs inside your own cloud account and brokers just-in-time access to your infrastructure. It's the part that actually reaches your resources (signing in to a host, provisioning a database grant, and so on), so that neither your engineers' laptops nor P0's SaaS ever hold standing credentials to your fleet.

P0's default access paths are agentless and driven entirely by your cloud provider's native IAM. Connectors exist for the cases where that isn't enough: where a cloud-native path is fragile, where provider IAM limits what an external service can do, or where you want an access path that keeps working even when a managed agent, or P0 itself, is unavailable.

***

## What a connector is

A connector is a minimal runtime you deploy in your own cloud account, a container (for example on Cloud Run or ECS) or a serverless function. It has a deliberately small surface:

* It's **the only component that holds credentials to your target fleet**, and those credentials never leave your environment. Signing keys and per-host secrets live in your own KMS and secrets manager.
* It's **stateless**, runs from a **published, versioned image**, and can **scale to zero** between sessions when deployed as a function.
* It exposes **no inbound interface** other than a listener that mutually authenticates (mTLS) against P0's certificate authority.
* It installs through **P0's infrastructure-as-code** as a one-line operation, and updates by bumping an image or module version.

Conceptually, the connector *is* your security perimeter running P0-authored logic. It sits inside your trust boundary rather than reaching in from outside it.

### Where it sits

<figure><img src="/files/Egfnc6F4DrbAGsm7W0cC" alt="Diagram showing the P0 connector inside the customer&#x27;s cloud trust boundary, alongside KMS and secrets manager and existing cloud-native paths, between the P0 control plane and the target fleet"><figcaption></figcaption></figure>

The connector lives *next to* your cloud-native paths, not in front of them. Adopting a connector doesn't remove SSM, Bastion, or OS Login. They keep working, and the connector is an extra route to the same hosts.

***

## How a request flows

Using SSH as the worked example, a request for `p0 ssh prod-web-01` looks like this. The same shape (request, approve, mint short-lived credential, hand it to the connector) generalizes to other resource types.

<figure><img src="/files/xS8ZleJCl0RRvh96HSu0" alt="Sequence diagram of an SSH request: generate ephemeral keypair, request access and justification, evaluate policy and approval, sign public key, verify cert and fetch host credential, then log in and start the session"><figcaption></figcaption></figure>

The important properties: the engineer's private key never leaves their machine; the certificate is short-lived; the signing material and per-host credentials stay in your cloud; and the connector, not the laptop and not P0, is the only thing that ever authenticates to the host.

***

## Why we built connectors

### 1. A second, independent path in

This is the core motivation. A connector path needs only that `sshd` is running and the host is reachable on the network. It doesn't depend on an in-host agent or a managed access service, so it stays available across the failure modes that most often take the cloud-native path down.

<figure><img src="/files/Be1z67bl9qIBP4FmRyFK" alt="Diagram comparing the cloud-native path, which needs an in-host agent and breaks if it dies, with the connector path, which needs sshd and network and survives agent failure"><figcaption></figcaption></figure>

| Failure mode                              | Cloud-native path                                           | Connector path                                                           |
| ----------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------ |
| In-host agent dies or won't start at boot | Blocked, often only fixable by re-provisioning the instance | Unaffected, if `sshd` is up                                              |
| Managed access service degraded           | Blocked                                                     | Unaffected                                                               |
| P0 control plane unreachable              | Blocked                                                     | Can continue within policy, signing material already lives in your cloud |
| No network route to the host              | Blocked                                                     | Blocked, a connector doesn't solve reachability                          |

Because the connector and your signing material both live in your cloud, it's possible to define a break-glass path that keeps functioning even if P0's control plane is temporarily unreachable. Whether and how that's enabled is a policy decision made with your team.

### 2. Credentials never touch laptops, and never leave your cloud

The connector is the single mediator that authenticates to your fleet, and it reads its credentials from your own vault. Your engineers never own a credential to the target hosts, and neither does P0's SaaS. Per-host credentials are short-lived and rotate on a schedule you control.

### 3. It works within your IAM instead of around it

Running P0's logic inside your environment sidesteps the provider-IAM limits that would otherwise constrain what an external service can do, without granting P0 broad standing permissions. The connector operates with least-privilege, scoped credentials that stay in your account.

### 4. A minimal, consistent, up-to-date footprint

Every connector ships as the same kind of artifact: a small, stateless, published image installed through IaC and versioned centrally. That keeps the deployment story identical across integrations and makes staying current a version bump rather than a migration.

***

## What a connector isn't *not*

* **Not a bastion or jump host.** No human ever logs into the connector. It provisions access; it is not a machine you land an interactive session on.
* **Not a proxy in front of your cloud-native paths.** It runs beside them as an independent route, and doesn't intercept or replace SSM, Bastion, or OS Login.
* **Not a fix for network reachability.** You bring your own network path to the target host; the connector must be able to reach `sshd`, but it doesn't create connectivity where none exists.
* **Not a standing root account.** The on-host principal it uses is tightly constrained (see the [Security model](#security-model)).

***

## Security model

There are two trust boundaries: between your engineer's machine and P0, and between P0 and your connector. Everything past the connector, its conversation with the fleet and with your vault, stays inside your cloud account.

The connector exposes no inbound surface except a listener that mutually authenticates against P0's certificate authority. It has no persistent state, and per-host credentials are short-lived and rotated on a configurable schedule.

**The on-host principal.** To bridge an external identity into a Linux session, the connector uses a dedicated system account on the target host (commonly seen as `p0-user`). Every product in this category has an equivalent privileged on-host principal (it is intrinsic to the problem), and P0 bounds it deliberately:

* It is **not a general-purpose root account.** Its `sudoers` policy enumerates only the specific commands it may run (ephemeral user provisioning and file operations on a fixed set of paths), or it is locked to a single **signed bootstrap script** that is integrity-checked at every invocation.
* It **has no interactive shell.** A try to land a shell as this account fails; only the permitted commands or bootstrap script run.
* **Every invocation is logged**, both in the host's audit log and in the connector's stream to your SIEM.

Handled honestly: if that on-host principal were fully compromised on a host, an attacker could create or remove local users and change file ownership within the configured paths, but couldn't modify the integrity-checked bootstrap script, and couldn't pivot to other hosts, because credentials are per-host and short-lived. That's a materially smaller blast radius than a generic root SSH key. It's not zero, and we don't claim otherwise.

***

## One integration, several connectors

A connector is a general primitive, not a single product. A given integration is usually a small **constellation of cooperating connectors**, each doing one job:

* an **IAM-layer security perimeter** that manages cloud-native access,
* an **SSH connector** for host access,
* a **database-engine connector** that provisions grants over SQL,
* a **cloud-API connector** that creates managed identities through a provider's admin API.

Keeping each piece separate and single-purpose is deliberate: every connector carries only the permissions it needs, and each can be validated independently. It also means a connector for a new system is a small, well-scoped addition rather than a change to everything around it.

You can see this pattern in the integrations that ship today:

* The [GCP IAM management security perimeter](/integrations/resource-integrations/google-cloud/security-perimeter) and the [Microsoft Entra ID security perimeter](/integrations/directory-integrations/microsoft-entra-id/viewing-security-perimeter-logs) are the IAM-layer connectors for those clouds.
* The [Azure jump host management connector](/integrations/resource-integrations/microsoft-azure/jump-host-management) dispatches privileged session commands to your jump host VMs.
* The PostgreSQL and MySQL integrations deploy a `p0-connector` to broker database grants. See [Installing an RDS database](/integrations/resource-integrations/postgresql-new/installing-an-rds-database).

***

## FAQ

**Do my engineers connect&#x20;*****to*****&#x20;the connector?** No. No human logs into the connector. It brokers access; it's not something you open a session against.

**Does P0 hold credentials to my hosts?** No. Signing keys and per-host credentials live in your own KMS and secrets manager. The connector reads them there, inside your account.

**Do I still need SSM, Bastion, or OS Login?** You can keep them. The connector runs alongside your cloud-native paths as an additional, independent route. It doesn't replace them.

**Does a connector solve private-network reachability?** No. You bring your own network path to the target; the connector must be able to reach the host, but it doesn't create connectivity.

**What happens if a connector is compromised?** The blast radius stays small: per-host credentials that are short-lived, no lateral movement to other hosts, no interactive shell on the on-host account, and full audit logging of everything it does.

**Does access keep working if P0 is temporarily unreachable?** It can, within policy. Because the signing material lives in your cloud, you can define a break-glass path through the connector that does not depend on P0's control plane being reachable. This is configured with your team.

**How is a connector installed and kept up to date?** Through P0's infrastructure-as-code, as a one-line install. Updates are an image or module version bump.

***

## Related documentation

* [Google Cloud security perimeter](/integrations/resource-integrations/google-cloud/security-perimeter): Install the IAM-layer connector for GCP.
* [Viewing Security perimeter logs](/integrations/directory-integrations/microsoft-entra-id/viewing-security-perimeter-logs): Read the Entra security perimeter connector's logs.
* [Azure jump host management](/integrations/resource-integrations/microsoft-azure/jump-host-management): Configure the jump host connector.
* [Just-in-time access](/readme/just-in-time-access): The access model connectors broker.


# P0 AuthZ Control Plane for Agents

Verify the human and the agent, enforce least-privilege policy on every MCP tool call, and keep a complete audit trail with P0 AuthZ Control Plane™ for Agents and self-hosted AI Gateway.

Organizations have always needed least-privileged controls over what identities can do in sensitive systems. AI agents do not change that requirement, they dramatically intensify it. Agents act autonomously across more systems, around the clock, at machine speed. And they do so using the same broad permissions and static credentials that enterprises already struggle to govern for human users and\
non-human identities.

This creates three core risks to the business:

* **Loose delegation:** The agent inherits delegated authority without a defined scope, ownership or boundaries. The agent gets whatever access the weakest link allows.
* **Standing privilege:** The agent gets broad, standing access that long outlives any given task they are invoked to complete. Leaving that access path wide open to malicious activity.
* **Broken attribution:** When something goes wrong, nobody can explain who asked, what happened or why it was allowed.

#### How agents break traditional access management models

Agents introduce two structural changes that traditional identity access models do not handle:

* **Multiple identities at play.** Every agent action involves at least two identities: the originator and the agent itself, and they should rarely hold the same authority. Treating them as one (agent-as-user or agent-as-service-account) collapses the distinction and grants either end with too much access and obscures human accountability.
* **Complex action chain.** Where user access workflows are fairly straightforward, agents require at least four distinct control points, with multi-agent workflows extending this even further. Policy applied at any single layer leaves gaps across the others.

#### Benefits of the AuthZ Control Plane for Agents

Whether agents are triggered by humans, service accounts, workloads or other agents, every action needs identity and context that carries through the full access chain. With P0, every agentic access workflow is evaluated, scoped and governed at runtime.

* **Discover**. Inventory all agents and MCP servers, managed or\
  unmanaged.
  * Discover all intermediate agents, identities, tools and resources
  * Understand permissions, access paths and delegated authority
  * Identify shadow agents, standing access and emerging risk
* **Control.** Authorize what they can access, when and with whose authority.
  * Enforce runtime authorization before actions occur
  * Apply Zero Standing Privilege and Just-in-time access
  * Control agent behavior through policy, context and approvals
* **Prove.** Show what happened, ensure accountability and monitor for policy drift.
  * Capture the full action chain across requester, agent, tool and resource
  * Explain why access was granted, denied or revoked
  * Deliver audit-ready evidence, accountability and compliance

### How P0 Security controls agentic access at runtime

The P0 AuthZ Control Plane for Agents enforces policy at runtime in two critical ways that most tools do not: by understanding the full identity context behind every action and carrying that through each control point to enforces policy across the entire action chain at runtime.

* **Blended identity context** - Ties the agent to the human, service account, workload or agent that initiated the action for comprehensive context
* **End-to-end policy enforcement** - Applies policy to the blended identity across each control point in action chain before access occurs

P0’s agentic security offering is composed of two distinct but complementary pieces. Understanding where each sits in your stack prevents confusion when evaluating or deploying them.

**Layer 1: P0 OAuth Server and P0 AI Gateway**\
Sits in the data path between your agents and your MCP servers. Intercepts every tool call, verifies identity, enforces policy and writes to the audit log. Self-hosted in your environment so your sensitive data never leaves your perimeter.

**Layer 2: P0 AuthZ Control PlaneTM for Agents**\
The policy engine and authorization layer that tells the gateway what to allow, flag or block at runtime. Defines roles, manages JIT approvals, surfaces governance exceptions and provides the audit and identity inventory. Hosted as SaaS.

P0 Security covers these four control points in every agent action chain:

| Control point                 | Question                             |
| ----------------------------- | ------------------------------------ |
| **1. User authentication**    | Who is the accountable human?        |
| **2. Agent authentication**   | Which agent is acting?               |
| **3. Tool authorization**     | Can this agent use this tool?        |
| **4. Resource authorization** | What can it do in the target system? |

**Blended identity.** The gateway verifies the human by signing them in through Google Workspace, the identity provider supported for user sign-in today, and issues each agent client its own credentials. Agents can also authenticate with tokens minted by your own IdP, by enrolling that issuer as an [identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider) on the gateway. On each session it produces a signed token that cryptographically binds the user and the agent together. That token travels with every MCP tool call, and the agent never receives the credentials to the upstream system.

**Enforcement layers.**

* At layer 1, the gateway enforces at the MCP protocol layer, evaluating conditions on each MCP tool call. This applies to any MCP server, but may be too coarse for complex services.
* At layer 2, when P0 has an IAM integration with the upstream service (AWS and GCP today), enforcement happens *inside* that service via a session-bound temporary user, role, or policy, so the service itself controls and enforces the agent's effective entitlements. This unlocks the full extent of the service's native access control for any agent.

### Related documentation

* [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway): Architecture and installation of the self-hosted enforcement components
* [Agentic Gateway integration](/integrations/resource-integrations/agentic-gateway): Register the gateway and configure the MCP servers it fronts
* [P0 CLI commands](/p0-cli/p0-commands-and-usage): Connect agents and manage access from the command line

### Get started with agentic authorization

P0 delivers agentic authorization as a Helm chart you deploy into your own Kubernetes environment. Start with [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway), then configure your upstream servers with the [Agentic Gateway integration](/integrations/resource-integrations/agentic-gateway). To scope a rollout for your organization, [contact P0](https://www.p0.dev/).


# Service-account key rotation

Manage rotation of static service account credentials for third-party systems. P0 discovers owners, creates fresh credentials, and coordinates updates.

For most production cases, P0 recommends configuring service-account authentication using [workload identity federation](https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-63c.pdf). However, certain third-party systems (such as business intelligence tools) may require access to your production cloud, and only support access via static credentials. For these identities, P0 will manage rotation of these credentials, avoiding use of stale credentials.

### How it works

1. You set a credential rotation policy within P0. For example, you may require that credentials are rotated every 30 days, and no credentials are ever more than 40 days old.
2. P0 uses your [Access inventory](/readme/access-inventory) to automatically detect credentials that have upcoming rotation due dates.
3. P0 determines account owners within your organization based on associated resources. For example, P0 might use the technical contact configured in the credential's managing cloud account.
4. P0 stages updated credentials for each account that needs rotation within a vault you connect. For example, AWS KMS, GCP GSM, or HashiCorp Vault.
5. P0 assigns tickets in your tracking system for owners to update credentials in third-party systems.
6. When each rotation ticket is closed, P0 revokes the previous credential.

<figure><img src="/files/FXbdyR31dvkLdnmSpoNd" alt="Credential rotation dashboard showing assignees with overdue and upcoming rotation tickets, with options to view details or send reminders" width="563"><figcaption></figcaption></figure>

### Related documentation

* [Access inventory](/readme/access-inventory): P0 uses your inventory data to detect credentials approaching rotation deadlines
* [Tracker integrations](/integrations/tracker-integrations): Connect your ticketing system for automated rotation ticket assignment
* [Resource integrations](/integrations/resource-integrations): Connect the cloud environments whose credentials you want to rotate

### Getting started with service-account key rotation

Key rotation requires an enterprise P0 license. Contact [P0 sales](mailto:sales@p0.dev) to get started.


# P0 onboarding

Deploy P0 Security, integrate your cloud environments, and establish just-in-time access for human and non-human identities. For security and DevOps teams.

Welcome to **P0 Security**.

This guide provides a structured pathway for deploying P0, integrating your cloud environments, and establishing secure, just-in-time (JIT) access for both human and non-human identities. It is designed for Security Engineers, DevOps practitioners, and engineering leaders seeking a precise and unified approach to modern access governance.

## Table of Contents

1. [Prerequisites](#prerequisites)
2. [Account Setup](#account-setup)
3. [Core Capabilities](#core-capabilities)
   * [Getting Started with Inventory & Posture](#️-getting-started-with-inventory--posture)
   * [Getting Started with Just-in-Time Access](#️-getting-started-with-just-in-time-access)
4. [Next Steps and Resources](#next-steps-and-resources)

## Prerequisites

Before onboarding, confirm the following:

* A corporate email address for registration.
* Permissions to establish cloud integrations (AWS, GCP, Azure, etc.).
* The ability to assign or manage custom IAM roles within your provider.
* *(Optional)* Slack workspace access if leveraging Slack-based requests.
* *(Optional)* Microsoft Teams access if leveraging Teams-based requests.

## Account Setup

1. **Choose an Identity Provider**
   * Select the authentication method for your organization.
   * See [Supported Identity Providers](/getting-started/p0-security-onboarding/supported-identity-providers) for available options.
   * For Okta sign-in, contact P0 Security before proceeding.
2. **Create Your P0 Account**
   * Register with your organizational email at <https://p0.app/create-account>.
   * Sign in using your chosen identity provider.
3. **Initial Organization Configuration**
   * Complete the onboarding wizard.
   * Define your organization name.
   * Invite foundational team members.

## Core Capabilities

P0 offers two primary capabilities: gaining visibility into your cloud environments and managing just-in-time access. You can set them up in any order.

### ⬇️ Getting Started with Inventory & Posture

To begin discovering, analyzing, and securing your cloud identity and access posture, you will need to connect your cloud environments to P0. This involves creating an integration and running an initial scan to populate your inventory.

For a complete walkthrough, see the detailed guides:

* [**Getting Started with Access Inventory**](/getting-started/getting-started-with-access-inventory)
* [**Getting Started with Posture**](/getting-started/getting-started-with-posture)

### ⬇️ Getting Started with Just-in-Time Access

To enable secure, temporary access to your cloud resources, you will need to configure integrations, set up approval workflows, and make your first request. This process includes installing P0 on specific IAM resources and assigning security reviewers.

For a step-by-step tutorial, refer to the detailed guide:

* [**Getting Started with Just-in-Time Access Guide**](/getting-started/getting-started-with-just-in-time-access)

## Next Steps and Resources

You are now positioned to:

* Extend integrations (IAM, databases, developer tools).
* Connect P0 to your corporate directory.
* Automate workflows and configure advanced access policies.
* Enable continuous risk analysis and remediation.

## Support

📧 <support@p0.dev>

## Recommended Reading

* [Creating an Environment](/environments/creating-an-environment)
* [Access Inventory & Posture Guides](/inventory/access-inventory)

**P0 Security:** Delivering secure cloud access at engineering velocity.\
Welcome to unified access governance.


# Supported identity providers

P0 Security supports many identity providers for authenticating to the P0 web app and CLI. Select the identity provider that matches your organization's authentication strategy.

## Available identity providers

| Identity Provider      | Setup Required | Description                                                   |
| ---------------------- | -------------- | ------------------------------------------------------------- |
| **Google**             | None           | Sign in with your Google Workspace or personal Google account |
| **Microsoft Entra ID** | None           | Sign in with your Microsoft work or school account            |
| **Okta**               | Yes            | Sign in with your organization's Okta instance                |
| **Email/Password**     | None           | Create a P0-specific account with email and password          |

## Google

Google authentication works automatically with any Google account.

**To sign in with Google:**

1. Navigate to <https://p0.app/create-account>.
2. Click **Google**.
3. Select your Google account and authorize P0.

## Microsoft Entra ID

Microsoft Entra ID authentication works automatically with any Microsoft work or school account.

**To sign in with Microsoft Entra ID:**

1. Navigate to <https://p0.app/create-account>.
2. Click **Entra ID**.
3. Enter your Microsoft credentials and authorize P0.

## Okta

Okta authentication requires your organization's Okta administrator to configure an OIDC application for P0.

{% hint style="info" %}
Contact P0 Security to get started with Okta sign-in. Our team will guide you through the setup process.
{% endhint %}

Once configured, your users can sign in using their Okta credentials on both the P0 web app and CLI.

**To set up Okta sign-in:**

See [Okta sign-in setup](/getting-started/p0-security-onboarding/supported-identity-providers/okta-sign-in-setup) for detailed configuration instructions.

## Email/Password

Create a standalone P0 account using any email address.

**To create an account with email and password:**

1. Navigate to <https://p0.app/create-account>.
2. Click **Sign up with email**.
3. Enter your email address and create a password.
4. Verify your email address via the confirmation link.

{% hint style="warning" %}
Email/password accounts don't integrate with your corporate identity provider. Consider using Google, Microsoft, or Okta authentication for centralized identity management.
{% endhint %}

## Choosing an identity provider

When selecting an identity provider, consider:

* **Centralized identity management**: Google, Microsoft, and Okta integrate with your existing directory, enabling automatic user provisioning and deprovisioning.
* **Security policies**: Corporate identity providers enforce your organization's security policies, including multi-factor authentication and conditional access.
* **User experience**: Users can sign in with credentials they already know.

{% hint style="info" %}
This page describes how users authenticate **to P0 Security**. For integrating your identity provider's directory with P0 for access management and inventory, see [Directory Integrations](/integrations/directory-integrations).
{% endhint %}


# Okta sign-in setup

This guide describes how to configure Okta as an identity provider for signing in to P0 Security. After completing this setup, your users can authenticate to the P0 web app at <https://p0.app> and the P0 CLI using their Okta credentials.

{% hint style="info" %}
This guide covers **signing in to P0 with Okta**. This is different from the [Okta Directory Integration](/integrations/directory-integrations/okta), which enables P0 to manage access and inventory within your Okta instance.
{% endhint %}

**Approximate setup time:** 15 minutes

## Prerequisites

* An existing P0 Security account
* Administrative access to your Okta instance with one of the following roles:
  * Super Administrator
  * Application Administrator

## Overview

Setting up Okta sign-in for P0 involves:

1. [Contact P0 Security](#step-1-contact-p0-security)
2. [Create an application integration in Okta](#step-2-create-an-application-integration)
3. [Configure OIDC parameters](#step-3-configure-oidc-parameters)
4. [Verify client credentials](#step-4-verify-client-credentials)
5. [Share configuration with P0](#step-5-share-configuration-with-p0)

## Step 1: Contact P0 Security

Before configuring Okta, contact P0 Security to start the setup process:

* **Email:** <support@p0.dev>

P0 confirms your organization is ready for Okta sign-in configuration.

## Step 2: Create an application integration

1. Log in to the Okta Admin Portal.

{% hint style="info" %}
The admin URL is your subdomain plus `-admin` (for example, `companyname-admin.okta.com`). If you have customized your domain, access the admin console using your un-customized domain.
{% endhint %}

2. Select **Applications** > **Applications** from the menu.
3. Click **Create App Integration**.

   <figure><img src="/files/ykenTr2aA4Hd2dvnPXa6" alt="Okta Admin Applications page with the Create App Integration button highlighted" width="375"><figcaption></figcaption></figure>
4. In the "Create a new app integration" modal:

   * Select **OIDC - OpenID Connect** as the Sign-in method.
   * Select **Native Application** as the Application type.

   <figure><img src="/files/BYgWuhFJ3ZW8HQxqmBAk" alt="Okta Create a new app integration modal with OIDC - OpenID Connect and Native Application selected" width="375"><figcaption></figcaption></figure>
5. Click **Next**.

## Step 3: Configure OIDC parameters

On the "New Native App Integration" page, configure the following settings:

1. **App integration name:** Enter `P0 Security`.
2. **Logo** *(optional)*: Upload the P0 logo if desired.

<div align="left"><figure><img src="/files/9TYMcM4j9d9HbwDjqgx7" alt="P0 Security application logo" width="150"><figcaption></figcaption></figure></div>

3. **Grant type:** Enable the following grant types:
   * Authorization Code
   * Device Authorization
   * Token Exchange

<figure><img src="/files/vnHNKXrVojHgIqM9WwJ3" alt="Okta New Native App Integration settings showing grant type options with Authorization Code, Device Authorization, and Token Exchange enabled" width="375"><figcaption></figcaption></figure>

4. **Sign-in redirect URIs:** Add the following URI:

   ```
   https://p0.app/oidc/auth/_redirect
   ```
5. **Assignments:** Configure access for your organization:
   * To enable P0 for everyone, select **Allow everyone in your organization to access**.
   * To restrict access to specific groups, select **Limit access to selected groups** and choose the appropriate Okta groups.

     <figure><img src="/files/ErdFA4Mglaoj7UKIhVxQ" alt="Okta app integration settings showing sign-in redirect URIs, sign-out redirect URIs, and Assignments section" width="375"><figcaption></figcaption></figure>
6. Click **Save**.

## Step 4: Verify client credentials

After creating the application, verify the client credentials settings:

1. Navigate to the **General** tab of your new P0 Security application.
2. In the **Client Credentials** section, confirm:
   * **Client authentication** is set to **None**
   * **Proof Key for Code Exchange (PKCE)** is enabled

<figure><img src="/files/BAGJrHWmbdLtWqHveawK" alt="Okta P0 Security app General tab showing Client Credentials with Client authentication set to None and PKCE enabled" width="333"><figcaption></figcaption></figure>

## Step 5: Share configuration with P0

Share the following information with P0 Security to complete the setup:

1. **Okta organization URL:** Your Okta domain (for example, `mycompany.okta.com`)
2. **Client ID:** Found in the **Client Credentials** section of the **General** tab

{% hint style="info" %}
These values aren't secrets and are safe to share over email or Slack.
{% endhint %}

Send these values to <support@p0.dev> or your P0 account executive. P0 configures your organization and confirms when Okta sign-in is ready.

## Signing in with Okta

Once P0 confirms the configuration is complete, users can sign in:

**Web app:**

1. Navigate to <https://p0.app>.
2. Click **Sign in with Okta**.
3. Authenticate with your Okta credentials.

**CLI:**

1. Run `p0 login`.
2. Complete authentication in the browser window that opens. The CLI automatically uses Okta once your organization is configured.

{% hint style="success" %}
Your organization is now configured to sign in to P0 Security using Okta.
{% endhint %}

## Related topics

* [Supported identity providers](/getting-started/p0-security-onboarding/supported-identity-providers)
* [Okta Directory Integration](/integrations/directory-integrations/okta): For managing access and inventory within Okta
* [Installing P0 CLI](/p0-cli/installing-p0-cli)


# Request access quickstart

Request your first just-in-time access to a cloud resource through Slack, Microsoft Teams, the P0 web app, or the CLI. A quickstart for developers and engineers.

This quickstart walks you through requesting your first just-in-time access to a cloud resource. By the end, you will have submitted an access request using Slack, Microsoft Teams, the P0 web app, or the P0 CLI.

{% hint style="info" %}
This guide is for developers and engineers who need to request access to resources. If you are an administrator setting up P0, start with the [Getting started with just-in-time access](/getting-started/getting-started-with-just-in-time-access) guide instead.
{% endhint %}

## Before you start

Confirm the following:

* Your organization has a P0 account and at least one [resource integration](/integrations/resource-integrations) installed
* You can sign in to [p0.app](https://p0.app) with your organizational email
* *(For Slack requests)* The P0 Slack bot is installed in your workspace
* *(For Microsoft Teams requests)* The [P0 Microsoft Teams integration](/integrations/notifier-integrations/microsoft-teams) is installed in your organization

## Step 1: Choose your request method

P0 supports four ways to request access. Select the one that fits your workflow:

| Method              | Best for                                                      | What you need                                                                                   |
| ------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **Slack**           | Quick requests without leaving your chat workflow             | P0 Slack bot in your workspace                                                                  |
| **Microsoft Teams** | Quick requests without leaving your chat workflow             | [P0 Microsoft Teams integration](/integrations/notifier-integrations/microsoft-teams) installed |
| **Web app**         | Browsing available resources before requesting                | Access to [p0.app](https://p0.app)                                                              |
| **CLI**             | Scripted workflows or requesting and using access in one step | [P0 CLI installed](/p0-cli/installing-p0-cli)                                                   |

## Step 2: Request access

{% tabs %}
{% tab title="Slack" %}

1. Open Slack and navigate to any channel.
2. Type `/p0 request` and press Enter. The P0 request modal opens.
3. Select the **resource type** (such as AWS, Google Cloud, or Kubernetes).
4. Select the specific **resource** and **access level** you need.
5. Enter a **reason** for your request. This helps approvers understand the context.
6. Select a **duration** for how long you need access.
7. Click **Request**.

P0 sends you a direct message with your request details and routes the request to the appropriate approver.

{% hint style="info" %}
If you know exactly what you need, skip the modal with a slash command:

```
/p0 request gcloud role my-project viewer --reason "Investigating production issue"
```

See [requesting access via Slack](/access-management/just-in-time-access/requesting-access#using-slack-slash-commands) for the full command syntax.
{% endhint %}
{% endtab %}

{% tab title="Microsoft Teams" %}

1. Open Microsoft Teams and navigate to any channel or chat.
2. Type `@P0 Security request` and send the message. The P0 request card opens.
3. Select the **resource type** (such as AWS, Google Cloud, or Kubernetes).
4. Select the specific **resource** and **access level** you need.
5. Enter a **reason** for your request. This helps approvers understand the context.
6. Select a **duration** for how long you need access.
7. Click **Request**.

P0 sends you a message with your request details and routes the request to the appropriate approver.

{% hint style="info" %}
Your P0 administrator must install the [Microsoft Teams integration](/integrations/notifier-integrations/microsoft-teams) before you can use this method.
{% endhint %}
{% endtab %}

{% tab title="Web app" %}

1. Sign in to [p0.app](https://p0.app).
2. Navigate to **Access Management** in the left sidebar.
3. Click the **Request Access** button in the top right corner.
4. Select the **resource type**, **resource**, and **access level** you need.
5. Enter a **reason** and select a **duration**.
6. Click **Request**.

Your request appears in the **Pending** section of the Access Management Activity page.

See [web request modal](/access-management/just-in-time-access/requesting-access/web-request-modal) for more details.
{% endtab %}

{% tab title="CLI" %}

1. Install the P0 CLI if you haven't already. See [installing the P0 CLI](/p0-cli/installing-p0-cli).
2. Authenticate, replacing `<your-org>` with your P0 organization ID:

   ```bash
   p0 login <your-org>
   ```
3. Request access. For example, to request an AWS IAM role:

   ```bash
   p0 request aws role MyReadOnlyRole \
     --account 123456789012 \
     --reason "Investigating S3 access issues"
   ```
4. To request access and wait until it's provisioned before running a command, add the `--wait` flag:

   ```bash
   p0 request aws role MyReadOnlyRole \
     --account 123456789012 \
     --reason "Investigating S3 access issues" \
     --wait
   ```

See [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request) for the full command reference and all supported providers.
{% endtab %}
{% endtabs %}

## Step 3: Get approved and use your access

After you submit your request:

1. **Wait for approval.** P0 routes your request to the appropriate approver based on your organization's [access policies](/access-management/just-in-time-access/access-policies). You receive a notification when access is approved or denied.
2. **Use your access.** Once approved, P0 provisions access automatically. Depending on the resource, expect a short propagation delay (10 seconds to one minute).
3. **Access expires automatically.** When the approved duration ends, P0 revokes access. You can also click **Relinquish** in your P0 Slack or Microsoft Teams message to give up access early.

{% hint style="warning" %}
Most IAM systems have a propagation delay of 10 to 30 seconds after access is provisioned before you can use it.
{% endhint %}

## Troubleshooting

| Problem                                             | Solution                                                                                                                                                                                                                   |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/p0 request` does not respond in Slack             | Confirm that the P0 Slack bot is installed. Ask your P0 administrator to check the [Slack integration](/integrations/notifier-integrations/slack).                                                                         |
| P0 Security bot does not respond in Microsoft Teams | Confirm that the P0 Microsoft Teams integration is installed and the app is available to users. Ask your P0 administrator to check the [Microsoft Teams integration](/integrations/notifier-integrations/microsoft-teams). |
| "This resource doesn't exist" error                 | Your organization's access policy may not cover this resource. Contact your P0 administrator. See [access policies](/access-management/just-in-time-access/access-policies).                                               |
| CLI returns an authentication error                 | Run `p0 login` to re-authenticate. See [p0 login troubleshooting](/p0-cli/troubleshooting/p0-login).                                                                                                                       |
| Access approved but not working                     | Wait 30 seconds for IAM propagation. If access still does not work, check the [requesting access](/access-management/just-in-time-access/requesting-access) guide for resource-specific details.                           |

## What's next

* [Request access on behalf of another user](/access-management/just-in-time-access/requesting-access/for-another-party)
* [Use the P0 CLI for AWS role assumption](/p0-cli/p0-commands-and-usage/p0-aws-role-assume)
* [Use the P0 CLI for SSH access](/p0-cli/p0-commands-and-usage/p0-ssh)
* [Browse the full requesting access guide](/access-management/just-in-time-access/requesting-access)


# Getting started with just-in-time access

Install P0 Security, configure approval workflows, and make your first just-in-time access request. A step-by-step guide for security and DevOps teams.

## Visual overview

The following diagram illustrates an example Just-in-Time Access workflow at a high level.

<figure><img src="/files/mzJ7umR8GlSddWzNV5Qk" alt="JIT access workflow diagram showing P0 CLI and P0 Service connecting to Okta AWS Account Federation App and AWS IAM to grant access to resources like S3, EKS, RDS, EC2, and KMS"><figcaption></figcaption></figure>

## Steps to get started

This guide provides the following sections to help you get up and running with P0's [Just-in-time access](/access-management/just-in-time-access):

1. [Set up an account and a security reviewer](#set-up-an-account-and-a-security-reviewer)
2. [Install P0 on an IAM resource](#install-p0-on-an-iam-resource)
3. [Make your first access request](#make-your-first-access-request)

{% hint style="info" %}
This process takes about 15 minutes.
{% endhint %}

{% hint style="info" %}
This document uses the following terms:

* **Requestor:** Person who requests access to a resource via P0's Slack bot.
* **Approver:** Person who approves these access requests via P0's Slack bot.
  {% endhint %}

## Set up an account and a security reviewer

To create your P0 account and set up an approver to approve access requests:

1. Create a free P0 account at[ https://p0.app/create-account](https://p0.app/create-account). All you need is an email address.
2. Set up a cloud integration through the guided onboarding flow (select **Access Orchestration**). You may skip this step and instead follow the instructions to install a resource [in the next step.](#install-p0-on-an-iam-resource)
3. Once you complete the onboarding, under **P0 Management**, add one or more "Security Reviewers". Security Reviewers can approve access requests.

<figure><img src="/files/5WKRruuzLbFVrtheq0sA" alt="P0 Management Access control panel with Security Reviewers section highlighted, showing fields for adding reviewer email addresses"><figcaption></figcaption></figure>

{% hint style="info" %}
To further configure access request requestors, approvers, or change settings such as the ability to approve your own access requests, navigate to **Policy Studio** (or go to `https://p0.app/o/<your organization>/policies`). You can edit the existing default rule or create new rules.
{% endhint %}

## Install P0 on an IAM resource

If you already configured a resource as part of the guided onboarding, you may skip this step. Otherwise, you will need to install an IAM resource to which users can request Just-in-time access.

To do this, navigate to **Integrations** and select the integration you wish to install from the list of "Resource" integrations.

<figure><img src="/files/fuHXaw3vcMnGRfGQQwnP" alt="P0 Integrations page with the Resources section highlighted, listing AWS, Azure, Google Cloud, GitHub, Custom, Kubernetes, Snowflake, PostgreSQL, and SSH"><figcaption></figcaption></figure>

Once you have selected a resource, follow the instructions in the app to provide P0 with permissions to grant and revoke access on that resource via the **IAM Management** installation. For more information, follow one of the resource-specific installation guides below.

{% content-ref url="/pages/oOcOnLUMfBQoti6jD1po" %}
[Google Cloud](/integrations/resource-integrations/google-cloud)
{% endcontent-ref %}

{% content-ref url="/pages/baaaYyZ5nS00f0uGiG18" %}
[AWS](/integrations/resource-integrations/aws)
{% endcontent-ref %}

{% content-ref url="/pages/L3Cnca6oWCOCnTjRmkKb" %}
[Snowflake](/integrations/resource-integrations/snowflake)
{% endcontent-ref %}

{% content-ref url="/pages/PmNajuau9q2r5PTTC5b3" %}
[Kubernetes](/integrations/resource-integrations/kubernetes)
{% endcontent-ref %}

{% content-ref url="<https://github.com/p0-security/p0-docs/blob/main/integrations/resource-integrations/postgresql/README.md>" %}
<https://github.com/p0-security/p0-docs/blob/main/integrations/resource-integrations/postgresql/README.md>
{% endcontent-ref %}

## Make your first access request

Once you've set up P0, you can make your first access request. You can try this out entirely on your own, if you enabled one-party approvals in [Set up an account and a security reviewer](#set-up-an-account-and-a-security-reviewer). Otherwise, grab a colleague to help you, and designate one person as the requestor and the other as the approver:

{% hint style="info" %}

* You can use the [P0 Security Command-line Interface](https://www.npmjs.com/package/@p0security/cli) (CLI) as an alternate method to request permissions, and then approve using the P0 website app.
* P0 is in the process of adding additional IAM request methods, including a Microsoft Teams bot.
  {% endhint %}

{% tabs %}
{% tab title="Via the p0.app" %}

1. Navigate to any page under Access Management `https://p0.app/o/<your organization>/access-management/activity` and click the Request Access button in the top right.

<figure><img src="/files/KQZIsDbH6r70LQPdQ5Ou" alt="Access Management Activity page showing Active and Pending sections with the Request Access button in the top right"><figcaption></figcaption></figure>

2. Populate the request details. For example:

<figure><img src="/files/SBcobOoTFmtyVTgCkjTj" alt="Request Access dialog with fields for Resource, Access Type, Account, Resource ARN, Policies, Reason, and duration filled in for an AWS S3 request" width="375"><figcaption></figcaption></figure>

3. The approver can see the request on the Access Management Activity page in the Pending section.

   <figure><img src="/files/OG01KaKEvTlS1n8ciShp" alt="Access Management Activity page showing a pending request for AmazonS3ReadOnlyAccess with Approve and Deny buttons"><figcaption></figcaption></figure>

   After a few moments, the access requestor receives a notification in the **p0-requests** channel that access was granted.

{% hint style="warning" %}
Most IAM systems have a delay of around 10 to 30 seconds after access is configured before access propagates to the resource and usage is enabled.
{% endhint %}

4. Once the access propagates to the resource, the request progresses to the Active section.

<figure><img src="/files/gsiAJEtdHmhJTGoZLyoz" alt="Access Management Activity page showing an active granted request for AmazonS3ReadOnlyAccess with expiration time and Revoke button"><figcaption></figcaption></figure>

{% hint style="info" %}
Access automatically ends after the expiration period is over, or when the requestor clicks the **Relinquish** button in their P0 DM.
{% endhint %}
{% endtab %}

{% tab title="Via Slack" %}

1. Have the requestor open Slack, navigate to the **p0-requests** channel, and enter `/p0 request` in the Slack channel:

<figure><img src="/files/XtpKX2s78kuSbbc9WlhE" alt="Slack sidebar with the p0-requests channel highlighted for entering access request commands" width="376"><figcaption></figcaption></figure>

2. Populate the request details. For example:

   <figure><img src="/files/NhHbetPl3twqgkS6D8R5" alt="Slack P0 request dialog with Google Cloud resource, Role access type, storage.objectViewer role selected, and a reason for access" width="375"><figcaption></figcaption></figure>
3. The approver receives a Slack notification via a DM from **P0 Security**. Navigate to that DM chat, choose an expiration, and click **Approve**.

   <figure><img src="/files/A3eajWgPoWRMT3QRRPuv" alt="Slack approval DM from P0 Security with expiry dropdown showing options from 5 minutes to 30 days, and Approve and Deny buttons" width="563"><figcaption></figcaption></figure>

   After a few moments, the access requestor receives a notification in the **p0-requests** channel that access was granted.

{% hint style="warning" %}
Most IAM systems have a delay of around 10 to 30 seconds after access is configured before access propagates to the resource and usage is enabled.
{% endhint %}

4. Once the access propagates to the resource, the requestor can validate the access.

{% hint style="info" %}
Access automatically ends after the expiration period is over, or when the requestor clicks the **Relinquish** button in their P0 DM.
{% endhint %}

<figure><img src="/files/UBv1bygiiaVVFR19k0uz" alt="Slack DM from P0 Security showing granted access notification with a Relinquish button to revoke access early" width="563"><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

## What's next

If you run into any issues, reach out to <support@p0.dev> for assistance.

Now that you can make access requests, you can:

* Add [additional IAM integrations](/integrations/resource-integrations)
* Connect P0 to [your directory](/integrations/directory-integrations)
* Add [auto approvals](/integrations/approval-integrations)
* Start exploring P0's [Access Inventory](/getting-started/getting-started-with-access-inventory) and [Posture](/getting-started/getting-started-with-posture) capabilities
* [Configure access policies](/access-management/just-in-time-access/access-policies) to route, automatically approve, and automatically deny requests based on the requestor and resource


# Configure your first access policy

Create an access policy in P0 Security's Policy Studio to control who can request access, what they can access, and who approves. A step-by-step tutorial for security administrators.

By default, P0 routes all access requests to your Security Reviewers. As your team grows, you need more control: route requests to the right approver, restrict access to sensitive resources, or allow automatic approvals for low-risk environments.

*Access policies* let you define these rules. In this tutorial, you create your first access policy that routes requests from your engineering team to a specific approver group.

## What you'll learn

After completing this tutorial, you are able to:

* Navigate to Policy Studio and edit your policy configuration
* Write a policy rule with requestor, resource, and approval sections
* Test your policy with a real access request
* Understand how P0 evaluates multiple policy rules

{% hint style="info" %}
This tutorial takes about 10 minutes.
{% endhint %}

## Before you start

Ensure you have the following:

* A P0 account with Owner permissions
* At least one [resource integration](/integrations/resource-integrations) installed (such as AWS, Google Cloud, or Kubernetes)
* A [Slack notifier](/integrations/notifier-integrations/slack) connected to receive approval notifications
* A [directory integration](/integrations/directory-integrations) installed (Google Workspace, Okta, or Microsoft Entra ID) with at least one group that contains your team members

{% hint style="info" %}
If you haven't set up P0 yet, complete the [Getting started with just-in-time access](/getting-started/getting-started-with-just-in-time-access) tutorial first.
{% endhint %}

## Step 1: Open Policy Studio

1. Sign in to [p0.app](https://p0.app).
2. Navigate to **Policy Studio** in the left sidebar, or go to `https://p0.app/o/<your-organization>/policies`.

Policy Studio opens on the **Control** table view, which lists your existing policy rules. To edit the raw configuration, select the **View options** (⋯) button in the page header, then select **YAML view**. If this is your first time here, the list may be empty.

{% hint style="info" %}
A *policy configuration* is a collection of one or more policy rules. You have one active policy configuration at any time, but it can contain as many individual rules as you need.
{% endhint %}

## Step 2: Understand the policy structure

Every policy rule has three parts:

| Part          | Purpose                            | Example              |
| ------------- | ---------------------------------- | -------------------- |
| **Requestor** | Who is making the request          | The engineering team |
| **Resource**  | What they are requesting access to | An AWS integration   |
| **Approval**  | Who can approve the request        | The SRE team         |

Here is a minimal policy rule:

```yaml
- requestor:
    type: group
    id: engineering@yourcompany.com
    label: Engineering
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: sre@yourcompany.com
      label: SRE Team
      directory: workspace
```

This rule means: when anyone in the **Engineering** group requests access to any resource, route the approval to the **SRE Team** group.

## Step 3: Create your first policy

In the **YAML view**, replace the editor content with the following configuration, substituting your own values:

```yaml
- requestor:
    type: group
    id: <your-team-group-id>
    label: <Your Team Name>
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: <your-approver-group-id>
      label: <Approver Team Name>
      directory: workspace
      options:
        allowOneParty: false
```

Replace the placeholder values:

| Placeholder                | What to enter                                                                                                                                                                                                         |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<your-team-group-id>`     | The group identifier for the requestors. For Google Workspace, use the group email (such as `engineering@yourcompany.com`). For Okta, use the group ID from the Okta admin console. For Entra ID, use the group UUID. |
| `<Your Team Name>`         | A human-readable name for the requestor group (such as `Engineering`)                                                                                                                                                 |
| `<your-approver-group-id>` | The group identifier for the approvers, in the same format as above                                                                                                                                                   |
| `<Approver Team Name>`     | A human-readable name for the approver group (such as `SRE Team`)                                                                                                                                                     |

{% hint style="warning" %}
Set the `directory` value based on your identity provider: `workspace` for Google Workspace, `okta` for Okta, or `azure-ad` for Microsoft Entra ID.
{% endhint %}

The `allowOneParty: false` option prevents requestors from approving their own requests. Set this to `true` to allow self-approval.

Click **Submit** to activate your policy configuration.

## Step 4: Test your policy

Verify that your policy works by submitting a test access request.

{% tabs %}
{% tab title="Via Slack" %}

1. Open Slack and navigate to the **p0-requests** channel.
2. Enter `/p0 request` and fill in the request details for a resource covered by your policy.
3. Confirm that the approval notification goes to a member of your approver group, not the default Security Reviewers.
   {% endtab %}

{% tab title="Via p0.app" %}

1. Navigate to **Access Management** and click the **Request Access** button.
2. Fill in the request details for a resource covered by your policy.
3. Confirm that the approval notification goes to a member of your approver group, not the default Security Reviewers.
   {% endtab %}
   {% endtabs %}

{% hint style="info" %}
If no policy rule matches a request, P0 blocks the request with the message: *"This resource doesn't exist, or your organization doesn't allow this principal to access this resource."* Ensure your policy configuration covers all requestors who need access.
{% endhint %}

## Step 5: Add a rule for a specific service

Now extend your configuration by adding a second rule that restricts a specific service to a dedicated approver. Add the following rule after your first one:

```yaml
- requestor:
    type: group
    id: <your-team-group-id>
    label: <Your Team Name>
    directory: workspace
  resource:
    type: integration
    service: aws
  approval:
    - type: group
      id: <aws-approver-group-id>
      label: <AWS Approver Team>
      directory: workspace
      options:
        allowOneParty: false
        requireReason: true
```

This rule routes AWS-specific requests to a dedicated approver group and requires the requestor to provide a reason. Your first rule still handles requests for all other services.

Click **Submit** to activate the updated configuration.

{% hint style="info" %}
When multiple rules match a request, P0 combines the approvers. Approval from any one matched approver is sufficient to grant access. See [how P0 evaluates access policies](/access-management/just-in-time-access/access-policies#evaluation-of-access-policies) for the full evaluation logic.
{% endhint %}

## Summary

You created an access policy configuration in Policy Studio that:

* Routes your team's access requests to a specific approver group
* Adds a service-specific rule for AWS with a required reason
* Replaces the default Security Reviewer approval flow with targeted approver assignment

## What's next

Now that you have a working policy, you can:

* [Deny access](/access-management/just-in-time-access/access-policies#deny) to sensitive resources by adding `type: deny` approval rules
* [Filter by resource attributes](/access-management/just-in-time-access/access-policies#integration) to restrict which specific roles, policies, or permission sets users can request
* [Set up auto-approval](/access-management/just-in-time-access/access-policies#auto) with PagerDuty or Incident.io for on-call engineers
* [Allow standing access](/access-management/just-in-time-access/access-policies#always-allowed) for low-risk resources with `type: persistent` rules
* Review the complete [access policies reference](/access-management/just-in-time-access/access-policies) for all configuration options


# Getting started with posture

Connect your cloud environment, run your first posture scan, and triage access risk findings with P0 Security's posture management product.

With [Posture](/readme/posture), P0 automatically scans your cloud environment for access risks, such as overprivileged accounts, unused privileged access, and risky lateral-movement paths. This guide walks you through your first scan and shows you how to review, triage, and remediate the findings P0 surfaces.

## Steps to get started

This guide provides the following sections to help you get up and running with Posture:

1. [Set up an account](#set-up-an-account)
2. [Connect your cloud and run your first scan](#connect-your-cloud-and-run-your-first-scan)
3. [Review your posture findings](#review-your-posture-findings)
4. [Investigate and triage a finding](#investigate-and-triage-a-finding)
5. [Create a custom monitor](#create-a-custom-monitor)

{% hint style="info" %}
Setup takes about 15 minutes. Your first scan then runs in the background and may take additional time to complete, depending on the size of your environment.
{% endhint %}

{% hint style="info" %}
This guide uses the following terms:

* **Monitor:** A rule that P0 evaluates against your access graph to detect a class of access risk. P0 provides built-in monitors, and you can define your own.
* **Finding:** A single issue that a monitor detects in your environment.
  {% endhint %}

## Set up an account

To create your P0 account:

1. Create a free P0 account at <https://p0.app/create-account>. All you need is an email address.
2. Set up a cloud integration through the guided onboarding flow (select **Privilege Governance**). You can skip this step and instead connect your cloud in the [next step](#connect-your-cloud-and-run-your-first-scan).

## Connect your cloud and run your first scan

Posture findings come from a scan of your cloud environment. To connect a cloud provider, create an environment, and run your first scan, follow the steps in [Creating an Environment](/environments/creating-an-environment).

{% hint style="info" %}
If you already use P0 for just-in-time access, you still need to complete this step. Data collection for Posture requires different permissions in your cloud provider than access orchestration does.
{% endhint %}

When the scan completes, P0 evaluates its built-in monitors against your access graph and populates your findings. Select **Dashboard** to confirm that your first scan has finished.

{% hint style="info" %}
A single environment and scan power both products. The same data also feeds [Access Inventory](/inventory/access-inventory), a queryable view of every identity, entitlement, and resource P0 collected. To explore it, see [Getting started with Access Inventory](/getting-started/getting-started-with-access-inventory).
{% endhint %}

## Review your posture findings

Select **Posture** in the P0 app sidebar to see the results of each monitor.

<figure><img src="/files/En5gA1PKFJUuR8KQeqoO" alt="Posture overview page showing findings summary with urgent, new, and average age metrics, filter controls, and a list of monitors with severity and count"><figcaption><p>The Posture overview lists each monitor with its severity and finding count.</p></figcaption></figure>

By default, this page shows all open findings, ranked by severity. To narrow the list:

* Select the filter icon to reveal the filter controls.
* Use the **Status** dropdown to filter by finding status: `Open`, `Ignored`, or `Resolved`.
* Enable the **Unassigned** checkbox to show only findings that aren't assigned.
* Narrow results to a specific target scope, such as an AWS account, Azure subscription, or GCP project.

To work through a single monitor, select it to open its [Monitor Results](/posture/monitor-results) page. This page shows the monitor's description, its history of new and resolved findings, and the full list of findings.

{% hint style="info" %}
Start with your highest-severity monitors. P0 resolves findings automatically once they no longer appear in a later scan, so focus your first session on the issues that matter most.
{% endhint %}

## Investigate and triage a finding

Select **view** next to a finding to open its [details](/posture/finding-details). From here, you can understand the risk and decide how to act on it.

<figure><img src="/files/2zdfyiAEzfqElwIxNG8P" alt="Finding details page showing attack path visualization, actions for Assign, Ignore, and Review fix, and risk details for an AWS IAM policy" width="563"><figcaption><p>A finding's details page shows its attack path and the actions you can take.</p></figcaption></figure>

For each finding, you can:

* **Review the attack path.** P0 shows how an actor holding the identity can gain risky access to your system.
* **Review fix.** For P0-provided monitors, P0 generates cloud shell commands that resolve the finding, such as replacing an overly permissive policy with a least-privilege one.
* **Assign.** If you've connected P0 to [Jira](/integrations/tracker-integrations/jira), assign the finding for resolution. P0 creates a ticket containing the finding description, its context, and any resolution commands.
* **Ignore.** Document an acceptable risk. The finding no longer appears in your results unless you filter by `Ignored` status.
* **Add notes.** Record a business justification or other context directly on the finding.

{% hint style="info" %}
You can assign, ignore, or review fixes for several findings at once from a monitor's results page using bulk actions.
{% endhint %}

## Create a custom monitor

Beyond P0's built-in monitors, you can define custom monitors to enforce your organization's own access policies. A custom monitor starts from a query in the [Access Inventory](/inventory/access-inventory):

1. In the Inventory, select a **show** option and enter a **where** query.
2. When the results match what you expect, select **Save Search**.
3. Enable the **Create a monitor for this search?** toggle, then add a title, description, and severity.

P0 evaluates your custom monitor on every scan, and its results appear alongside the built-in monitors in [Monitor Results](/posture/monitor-results). For full details, see [Creating custom monitors](/inventory/access-inventory#creating-custom-monitors).

## What's next

If you run into any issues, contact <support@p0.dev> for help.

Now that you can review and triage posture findings, you can:

* Run [custom queries to explore your environment](/inventory/query-search)
* Connect P0 to a [ticketing system](/integrations/tracker-integrations) to assign findings automatically
* Connect P0 to [your directory](/integrations/directory-integrations) to enrich identity data
* Add more cloud accounts and resources by [creating additional environments](/environments/creating-an-environment)
* Start using [P0's just-in-time access](/getting-started/getting-started-with-just-in-time-access)


# Getting started with access inventory

Connect your cloud environment, navigate the Access Inventory page, run your first query, investigate access risks, and save a monitor with P0 Security.

[Access Inventory](/readme/access-inventory) gives you a single, queryable view of your entire IAM configuration. P0 combines data from your identity provider, your IAM policies, and your access logs into one access graph, then scores every privilege against P0's open-source [IAM Privilege Catalog](https://catalog.p0.dev).

This guide walks you through your first session with Access Inventory:

1. [Connect a resource and run your first scan](#step-1-connect-a-resource-and-run-your-first-scan)
2. [Open the Inventory page and confirm your data](#step-2-open-the-inventory-page-and-confirm-your-data)
3. [Run your first query](#step-3-run-your-first-query)
4. [Investigate a result](#step-4-investigate-a-result)
5. [Save a search as a monitor](#step-5-save-a-search-as-a-monitor)

{% hint style="info" %}
Initial setup takes about 15 minutes. IAM assessment is currently available for AWS, Google Cloud, Kubernetes, Okta, and Google Workspace.
{% endhint %}

## Who this guide is for

This guide is for administrators and security engineers who are setting up P0 for the first time and want to explore their cloud access. You need an understanding of cloud IAM concepts (identities, roles, and policies) and administrative access to at least one cloud provider.

## Prerequisites

Before you start, make sure you have:

* A P0 account. Create a free account at [p0.app/create-account](https://p0.app/create-account).
* Administrative access to a supported cloud provider (AWS, Google Cloud, Kubernetes, Okta, or Google Workspace).
* Permission to create an environment in your P0 organization.

## Step 1: Connect a resource and run your first scan

Access Inventory builds its graph from the resources you connect. To collect your first set of data, create an environment and run an inventory scan.

1. Sign in to [p0.app](https://p0.app).
2. Follow the steps in [Creating an Environment](/environments/creating-an-environment) to install P0 on a supported resource and start a scan.

P0 reads your IAM configuration and displays a collection progress indicator while it works. A single environment and scan power both products: the same data also feeds [Posture](/readme/posture), which flags access risks such as overprivileged accounts and risky lateral-movement paths. To review and triage those findings, see [Getting started with Posture](/getting-started/getting-started-with-posture).

{% hint style="info" %}
A scan can take several minutes to complete, depending on the size of your environment. You can continue to the next step once collection finishes.
{% endhint %}

## Step 2: Open the Inventory page and confirm your data

After your scan completes, confirm that P0 collected your data.

1. In the P0 dashboard, select the **Inventory** page.
2. Review the asset table. An empty search matches everything in your IAM configuration, so you see every collected item listed by default.
3. Use the **Show** dropdown to switch between **credentials**, **entitlements**, **identities**, and **resources**. Each option displays a different slice of your access graph.

If the table shows identities and resources from the provider you connected, your inventory is ready to query. If the table is empty, return to [Step 1](#step-1-connect-a-resource-and-run-your-first-scan) and confirm that your scan finished.

## Step 3: Run your first query

A query has two controls:

* **Show**: selects the kind of data to display (credentials, entitlements, identities, or resources).
* **Where**: a free-form search box that filters results to the term you enter.

Try a search to find access of interest:

1. Set **Show** to **identities**.
2. In the **Where** field, enter a term that appears in your data, such as a username, a resource name, or a permission. P0 returns the identities connected to that term.
3. To target a specific type of data, add a type prefix. For example, enter `risk:CRITICAL` to show only items that expose a critical IAM risk.

You can also refine a query without typing. Hover over any item in the results, then select **show** or **hide** to add that item to your query.

To learn the query language from scratch, see [Query Language Basics](/inventory/query-search/query-language-basics). For the full query syntax (including exact matches, attribute matches, and path expressions), see the [Search Reference](/inventory/query-search/search-reference).

## Step 4: Investigate a result

Each result links to a detailed view that explains why it matches your query.

1. In the results table, select **view** on any item to open its details. For more on what each field means, see [Result Details](/inventory/result-details).
2. Review the **Risks** section to see the access risks reachable from the item and the privileges that expose them.
3. Switch to the graph visualization to see the access paths that connect identities, entitlements, and resources. Select any node to view its properties.

The graph reveals lateral movement, the chains of access that let one identity reach a resource through federation, group membership, or cross-account roles.

## Step 5: Save a search as a monitor

When a query surfaces a risk you want to track over time, save it as a custom monitor. P0 re-runs the query on every scan and reports the results in [Monitor Results](/posture/monitor-results).

1. Build a query that returns the results you want to track, and confirm the displayed results match your expectations.
2. Select **Save Search**.
3. Enable the **Create a monitor for this search?** toggle.
4. Enter a title, description, and severity for the monitor, then save.

For more detail on building and managing inventory queries, see [Access Inventory](/inventory/access-inventory).

## What's next

You now know how to connect data, query your inventory, investigate access, and save a monitor. From here, you can:

* Connect P0 to [your directory](/integrations/directory-integrations) to enrich identity data.
* [Analyze your security findings](/posture/monitor-results) from built-in and custom monitors.
* Connect P0 to a [ticketing system](/integrations/tracker-integrations) to track remediation.
* [Get started with Just-in-Time Access](/getting-started/getting-started-with-just-in-time-access) to grant time-limited access to the resources you discovered.

If you run into any issues, contact <support@p0.dev>. We're here to help.


# Getting started with the P0 CLI

Install the P0 CLI, authenticate, send your first permission request, and open your first SSH session with just-in-time access, all from the command line.

This tutorial walks you through the P0 command-line interface (CLI) from installation to your first permission request and SSH session. By the end, you have installed the CLI, authenticated with your organization, sent a Google Cloud role request, and connected to a machine over SSH with just-in-time access.

## Steps to get started

1. [Install the P0 CLI](#install-the-p0-cli)
2. [Authenticate with your organization](#authenticate-with-your-organization)
3. [Send your first permission request](#send-your-first-permission-request)
4. [Discover SSH targets](#discover-ssh-targets)
5. [Open your first SSH session](#open-your-first-ssh-session)
6. [Copy files with SCP](#copy-files-with-scp)

{% hint style="info" %}
This process takes about 10 minutes, assuming your organization has already configured an SSH integration.
{% endhint %}

## Prerequisites

Before you begin, confirm the following:

* **P0 account**: You have an account at [p0.app](https://p0.app) and belong to an organization.
* **SSH integration**: Your administrator has installed the SSH access control integration for at least one cloud provider (AWS, Google Cloud, or Azure). See the [SSH integration guide](/integrations/resource-integrations/ssh) for setup instructions.
* **Node.js v22+**: Required for npm installation. Check with `node --version`.

{% hint style="info" %}
If you prefer a standalone binary that bundles Node.js, see the platform-specific installation guides for [macOS](/p0-cli/installing-p0-cli/macos) and [Windows](/p0-cli/installing-p0-cli/windows).
{% endhint %}

**Provider-specific prerequisites:**

| Provider     | Required tools                                                                                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AWS          | [AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) + [Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) |
| Google Cloud | [gcloud CLI](https://cloud.google.com/sdk/docs/install) (the CLI runs `gcloud auth login` automatically when needed)                                                                                                                  |
| Azure        | [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (authenticated with `az login`)                                                                                                                            |

## Install the P0 CLI

Install the CLI globally with npm:

```bash
npm install -g @p0security/cli
```

Verify the installation:

```bash
p0 --version
```

You should see the installed version number. Run `p0 --help` to view all available commands.

{% hint style="info" %}
For alternative installation methods, including standalone macOS and Windows binaries, see [Installing p0 CLI](/p0-cli/installing-p0-cli).
{% endhint %}

## Authenticate with your organization

Log in to your P0 organization. Replace `<your-org>` with your organization ID (visible in your P0 URL at `p0.app/o/<your-org>`):

```bash
p0 login <your-org>
```

Your browser opens to your organization's SSO provider (Google, Okta, Microsoft, or another configured provider). After you authenticate, the CLI confirms:

```
You are now logged in to the <your-org> organization, and can use the p0 CLI.
```

{% hint style="info" %}
The CLI stores your session in `~/.p0/identity.json`. If your session expires, the CLI automatically re-launches the browser login flow the next time you run a command.
{% endhint %}

## Send your first permission request

The P0 CLI can request any permission that your organization supports: cloud IAM roles, resources, SSH access, and more. This section walks through requesting a Google Cloud IAM role as an example.

### Find available roles

List Google Cloud roles that contain "storage" in the name:

```bash
p0 ls gcloud role storage
```

The output displays matching roles available to you, such as `storage.objectViewer`, `storage.admin`, and others.

{% hint style="info" %}
Use the `--like` flag for multi-term searches. For example, `--like storage,admin` returns roles matching both "storage" and "admin".
{% endhint %}

### Request the role

Request the `storage.objectViewer` role on a Google Cloud project. Replace `<your-project>` with your Google Cloud project ID:

```bash
p0 request gcloud role storage.objectViewer \
  --project <your-project> \
  --reason "Review storage bucket contents" \
  --wait
```

The `--wait` flag blocks until the request is approved and access is provisioned. You see output similar to:

```
Will wait up to 5 minutes for this request to complete...
Your request was approved
Waiting for access to be provisioned
Access to role storage.objectViewer has been provisioned
```

Once provisioned, you can use `gcloud` commands under the granted role immediately.

{% hint style="info" %}
Without `--wait`, the CLI submits the request and returns immediately. You receive a notification (through Slack or your configured channel) when access is approved and provisioned.
{% endhint %}

{% hint style="warning" %}
Google Cloud IAM changes have a propagation delay of 30 seconds to one minute. If a `gcloud` command fails immediately after provisioning, wait briefly and retry.
{% endhint %}

### Other request types

The `p0 request` command supports providers beyond Google Cloud. Run `p0 request --help` to see all options:

| Provider     | Example                                                            |
| ------------ | ------------------------------------------------------------------ |
| AWS          | `p0 request aws role MyReadOnlyRole --account 123456789012`        |
| Google Cloud | `p0 request gcloud role storage.objectViewer --project my-project` |
| Okta         | `p0 request okta group engineering-team`                           |

For the full command reference, see [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request).

## Discover SSH targets

Before connecting, list the SSH destinations available to you:

```bash
p0 ls ssh session destination
```

This displays instances your organization has registered with P0. To filter by cloud provider, add the `--provider` flag:

{% tabs %}
{% tab title="AWS" %}

```bash
p0 ls ssh session destination --provider aws
```

{% endtab %}

{% tab title="Google Cloud" %}

```bash
p0 ls ssh session destination --provider gcloud
```

{% endtab %}

{% tab title="Azure" %}

```bash
p0 ls ssh session destination --provider azure
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Items marked with `*` indicate instances you already have active access to. Listing a destination does not grant access. You still need approval.
{% endhint %}

To see more results, use the `--size` flag:

```bash
p0 ls ssh session destination --size 50
```

## Open your first SSH session

Connect to an instance by name. Replace `<instance-name>` with a destination from the previous step:

{% tabs %}
{% tab title="AWS" %}

```bash
p0 ssh <instance-name> \
  --provider aws \
  --reason "Testing P0 CLI access"
```

{% endtab %}

{% tab title="Google Cloud" %}

```bash
p0 ssh <instance-name> \
  --provider gcloud \
  --reason "Testing P0 CLI access"
```

{% endtab %}

{% tab title="Azure" %}

```bash
p0 ssh <instance-name> \
  --provider azure \
  --reason "Testing P0 CLI access"
```

{% endtab %}
{% endtabs %}

The CLI performs these steps automatically:

1. Generates a temporary SSH key pair.
2. Submits a just-in-time access request to P0 (including your `--reason`).
3. Waits for approval (up to 5 minutes).
4. Provisions access on the cloud provider.
5. Establishes the SSH connection.

You see output similar to:

```
Will wait up to 5 minutes for this request to complete...
Your request was approved
Waiting for access to be provisioned
```

Once provisioning completes, you are connected to the instance.

{% hint style="warning" %}
Most cloud providers have a propagation delay of 10 to 30 seconds after access is approved before the connection succeeds. The CLI retries automatically during this window.
{% endhint %}

{% hint style="info" %}
Use the `--sudo` flag to request sudo access on the remote machine:

```bash
p0 ssh <instance-name> --provider aws --sudo --reason "Install security patch"
```

{% endhint %}

### Run a one-off command

To execute a single command without an interactive session, append it after the destination:

```bash
p0 ssh <instance-name> --provider gcloud -- "df -h /var"
```

### Forward a local port

Use SSH port forwarding to securely access remote services:

```bash
p0 ssh <instance-name> --provider aws -- -L 5432:localhost:5432
```

This forwards local port 5432 to the remote instance's port 5432, useful for connecting to databases.

## Copy files with SCP

The `p0 scp` command works like standard `scp` but includes automatic access requests. Prefix the remote path with the instance name and a colon:

**Download a file from the remote instance:**

```bash
p0 scp <instance-name>:/var/log/app.log ./app.log \
  --provider aws \
  --reason "Collect logs for debugging"
```

**Upload a file to the remote instance:**

```bash
p0 scp ./config.yaml <instance-name>:/tmp/config.yaml \
  --provider gcloud \
  --reason "Deploy updated configuration"
```

## Verify it worked

Confirm your request history by checking the **Access Management > History** page at `https://p0.app/o/<your-org>/access-management/history`. You should see your completed permission request and SSH access request with the reasons you provided.

## Troubleshooting

| Symptom                                                 | Cause                         | Fix                                                                                                                 |
| ------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `The organization ID is required`                       | Missing org argument          | Run `p0 login <your-org>` with your org ID                                                                          |
| `This organization is not configured for SSH access`    | No SSH integration installed  | Ask your admin to install the [SSH integration](/integrations/resource-integrations/ssh)                            |
| `Could not find any instances matching...`              | Incorrect destination name    | Run `p0 ls ssh session destination` to list valid names                                                             |
| `Your request was denied`                               | Approver denied the request   | Check your policies or contact your approver                                                                        |
| Request times out after 5 minutes                       | No approver responded         | Verify your organization's [access policies](/access-management/just-in-time-access/access-policies) are configured |
| `Access did not propagate through <provider> in time`   | Cloud provider delay exceeded | Retry the command. Transient delays resolve on retry                                                                |
| `Hint: The instance name appears to include a username` | Used `user@host` format       | Use the instance name only, without a username prefix                                                               |

For detailed troubleshooting, see [p0 ssh troubleshooting](/p0-cli/troubleshooting/p0-ssh).

## What's next

Now that you can request permissions and SSH into machines from the command line, explore these capabilities:

* [Request access to AWS, Azure, Okta, and more](/p0-cli/p0-commands-and-usage/p0-request) with `p0 request`
* [Integrate P0 SSH with your native SSH config](/integrations/resource-integrations/ssh) to use `ssh <instance-name>` directly
* [Request access for a colleague](/access-management/just-in-time-access/requesting-access/for-another-party) with `p0 grant`
* [Create pre-approvals](/access-management/just-in-time-access/approving-access/pre-approving-access) for frequently accessed instances with `p0 allow`
* [Configure access policies](/access-management/just-in-time-access/access-policies) to auto-approve access for on-call engineers
* Explore all [CLI commands and usage](/p0-cli/p0-commands-and-usage)


# Get started with the P0 Terraform provider

Configure the P0 Terraform provider, authenticate with an API token, and install your first integration as code with an end-to-end AWS just-in-time access example.

This guide shows you how to manage P0 integrations as code with the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs). You configure the provider, authenticate with a P0 API token, and install an AWS account for just-in-time (JIT) access from end to end. By the end, you have a working Terraform configuration that you can extend to any P0 integration.

Managing P0 with Terraform keeps your access infrastructure version-controlled, reviewable, and repeatable across environments—the same workflow your team already uses for the rest of your cloud.

## Steps to complete

1. [Authenticate to P0](#authenticate-to-p0)
2. [Configure the P0 provider](#configure-the-p0-provider)
3. [Initialize Terraform](#initialize-terraform)
4. [Stage the AWS integration](#stage-the-aws-integration)
5. [Create the IAM role P0 uses](#create-the-iam-role-p0-uses)
6. [Finish the installation](#finalize-the-installation)
7. [Apply and verify](#apply-and-verify)

{% hint style="info" %}
This process takes about 15 minutes, assuming you already have an AWS account and permission to create IAM roles.
{% endhint %}

## Prerequisites

Before you begin, confirm the following:

* **P0 account** — You have an account at [p0.app](https://p0.app) and the **Owner** role in your organization. You need the Owner role to install integrations as code.
* **Terraform 1.0 or later** — Install the [Terraform CLI](https://developer.hashicorp.com/terraform/install) and confirm the version with `terraform version`.
* **AWS account** — You have an AWS account and credentials with permission to create IAM roles, along with the [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) configured in your project.

## Authenticate to P0

The provider authenticates to P0 with a bearer token, supplied as the `P0_API_TOKEN` environment variable (or the `api_token` attribute). You have three ways to get one—see [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) for the full details:

* **Google Cloud service-account token** (recommended for CI) — a short-lived token minted for a service account, with no long-lived secret in your pipeline.
* **P0 CLI session** (local development) — run `p0 login <your-org>` and the provider uses your CLI session automatically, with no token to set.
* **API key** (legacy) — a long-lived key generated in the dashboard.

For example, to authenticate a Google Cloud pipeline as a service account, mint an identity token whose audience is your organization URL and export it as `P0_API_TOKEN`:

```bash
export P0_API_TOKEN="$(gcloud auth print-identity-token \
  --impersonate-service-account="p0-ci@my-project.iam.gserviceaccount.com" \
  --audiences="https://p0.app/o/my-org" \
  --include-email)"
```

{% hint style="warning" %}
Never commit a token to version control. Use an environment variable or a secrets manager, and prefer a short-lived token over a long-lived API key.
{% endhint %}

## Configure the P0 provider

Declare the provider in a `.tf` file. The `source` is `p0-security/p0`, and the `org` attribute is your P0 organization identifier.

```hcl
terraform {
  required_providers {
    p0 = {
      source  = "p0-security/p0"
      version = "~> 0.40"
    }
  }
}

provider "p0" {
  org = "my-org"
}
```

The provider reads your token from the `P0_API_TOKEN` environment variable you set earlier, or from your P0 CLI session when no token is set. To pass the token explicitly instead, set the `api_token` attribute—but prefer the environment variable to keep secrets out of your configuration.

## Initialize Terraform

Download the provider and prepare your working directory:

```bash
terraform init
```

Terraform installs the P0 provider and reports `Terraform has been successfully initialized!`.

## Stage the AWS integration

P0 installs an AWS integration in two phases. First, you stage the account so P0 can generate the trust policy and inline policy for the IAM role it uses to manage access.

Add the `p0_aws_iam_write_staged` resource with your AWS account ID:

```hcl
resource "p0_aws_iam_write_staged" "prod" {
  id = "123456789012"
}
```

After you apply this resource, it exposes a `role` attribute that has the role name, trust policy, and inline policy that AWS requires in the next step.

## Create the IAM role P0 uses

Create the AWS IAM role from the staged outputs. P0 assumes this role to manage just-in-time access in your account.

```hcl
resource "aws_iam_role" "p0_iam_manager" {
  name               = p0_aws_iam_write_staged.prod.role.name
  assume_role_policy = p0_aws_iam_write_staged.prod.role.trust_policy

  inline_policy {
    name   = p0_aws_iam_write_staged.prod.role.inline_policy_name
    policy = p0_aws_iam_write_staged.prod.role.inline_policy
  }
}
```

## Complete the installation

Complete the installation with the `p0_aws_iam_write` resource. The `depends_on` argument ensures Terraform creates the IAM role before P0 verifies the installation.

```hcl
resource "p0_aws_iam_write" "prod" {
  id         = p0_aws_iam_write_staged.prod.id
  depends_on = [aws_iam_role.p0_iam_manager]

  login = {
    type = "iam"
    identity = {
      type = "email"
    }
  }
}
```

The `login` block tells P0 how users sign in to the account. This example uses IAM login with email-based identity matching, where each IAM user name is the user's email address. For Identity Center or federated login options, see the [`p0_aws_iam_write` resource reference](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/aws_iam_write).

## Apply and verify

Apply the full configuration:

```bash
terraform apply
```

Review the plan and confirm. Terraform stages the account, creates the IAM role, and finalizes the integration in dependency order.

To verify the installation succeeded, check the resource state:

```bash
terraform state show p0_aws_iam_write.prod
```

The `state` attribute reads `installed` when the integration is fully active:

```
state = "installed"
```

You can also open **Integrations** in the P0 dashboard and confirm the AWS account appears as installed. Users can now request just-in-time access to the account. See [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access) for the request workflow.

## Full example

The following configuration installs an AWS account for just-in-time access from end to end:

```hcl
terraform {
  required_providers {
    p0 = {
      source  = "p0-security/p0"
      version = "~> 0.40"
    }
  }
}

provider "p0" {
  org = "my-org"
}

# Step 1: Stage the account and generate the IAM policies P0 needs.
resource "p0_aws_iam_write_staged" "prod" {
  id = "123456789012"
}

# Step 2: Create the IAM role P0 assumes to manage access.
resource "aws_iam_role" "p0_iam_manager" {
  name               = p0_aws_iam_write_staged.prod.role.name
  assume_role_policy = p0_aws_iam_write_staged.prod.role.trust_policy

  inline_policy {
    name   = p0_aws_iam_write_staged.prod.role.inline_policy_name
    policy = p0_aws_iam_write_staged.prod.role.inline_policy
  }
}

# Step 3: Finalize the installation after the role exists.
resource "p0_aws_iam_write" "prod" {
  id         = p0_aws_iam_write_staged.prod.id
  depends_on = [aws_iam_role.p0_iam_manager]

  login = {
    type = "iam"
    identity = {
      type = "email"
    }
  }
}
```

{% hint style="success" %}
Once `p0_aws_iam_write.prod` reaches the `installed` state, your AWS account is managed entirely as code. Commit the configuration to version control and reuse it across accounts and environments.
{% endhint %}

## Troubleshooting

| Issue                                             | Cause                                             | Fix                                                                                                      |
| ------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `terraform init` fails to find the provider       | The `source` is wrong                             | Confirm the source is `p0-security/p0` and run `terraform init` again.                                   |
| Authentication or `401` errors on apply           | The API token or `org` is wrong                   | Confirm `P0_API_TOKEN` is exported in your shell and that `org` matches your P0 organization identifier. |
| Apply fails creating the IAM role                 | The AWS provider lacks permission to create roles | Confirm your AWS credentials allow `iam:CreateRole` and `iam:PutRolePolicy`.                             |
| `p0_aws_iam_write` stays in the `configure` state | The IAM role was not created before finalizing    | Keep the `depends_on` argument so Terraform creates the role first, then run `terraform apply` again.    |

## Next steps

* **Install more integrations** — The provider supports AWS, Google Cloud, Azure, SSH, Kubernetes, databases, SIEM exports, and more. Browse the full [resource catalog](https://registry.terraform.io/providers/p0-security/p0/latest/docs) in the Terraform Registry.
* **Install Kubernetes access** — For EKS clusters, see [Terraform installation](/integrations/resource-integrations/kubernetes/terraform-installation).
* **Manage access policies as code** — Define access policies and approvals with the `p0_access_policy` resource, which replaces the deprecated `p0_routing_rule`. See [Configure access policies](/getting-started/configuring-access-policies) for the concepts.


# Authenticating with the P0 API

Get a bearer token to authenticate with the P0 Management API and the P0 Terraform provider, using a Google Cloud service-account token, the P0 CLI, or an API key.

P0's programmatic surfaces all authenticate the same way: with a bearer token. This includes the [Management API](/p0-management/management-api), the [just-in-time access API](/access-management/just-in-time-access/just-in-time-api), the [inventory export API](/inventory/inventory-export-api), and the [P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider).

You supply the token in one of two ways:

* **API and direct HTTP calls** — pass it in the `Authorization` header: `Authorization: Bearer <token>`.
* **Terraform provider** — set the `P0_API_TOKEN` environment variable or the provider's `api_token` attribute.

This page explains how to get a token and which method to use.

## Choose an authentication method

| Method                                                                                | Best for                              | Notes                                                              |
| ------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------ |
| [Google Cloud service-account token](#google-cloud-service-account-token-recommended) | CI/CD and automation                  | Recommended. Short-lived, no stored secret. Requires Google Cloud. |
| [P0 CLI token](#p0-cli-token)                                                         | Local development and interactive use | Tied to your P0 login. Short-lived.                                |
| [API key](#api-key-legacy)                                                            | Existing integrations                 | Legacy. Being phased out in favor of a service-account token.      |

The token's privileges come from the P0 role of the identity behind it, so grant each identity the least privilege it needs. See [Role-based access control](/p0-management/role-based-access-control).

## Google Cloud service-account token (recommended)

A workload running on Google Cloud can authenticate as a service account with a short-lived identity token, so no long-lived secret needs to live in your CI system. P0 accepts a Google-issued service-account identity token as a bearer token.

1. **Choose a service account** for the workload to run as.
2. **Grant it a P0 role.** In the P0 dashboard, open **P0 Management** → **Role-Based Access Control** and add the service account's email as a member. The role you assign governs what the identity can do: add it as a **Security Reviewer** for read-only access, or as an **Owner** for a pipeline that creates or modifies integrations, policies, or settings. The service account's email must be a member of your P0 organization, or P0 rejects the token.
3. **Mint a short-lived identity token** and use it as your bearer token. The audience must be your P0 organization URL, `https://p0.app/o/<your-org>`, and the token must include a verified email:

   ```bash
   export P0_API_TOKEN="$(gcloud auth print-identity-token \
     --impersonate-service-account="p0-ci@my-project.iam.gserviceaccount.com" \
     --audiences="https://p0.app/o/my-org" \
     --include-email)"
   ```
4. **Use the token.** With `P0_API_TOKEN` set, the Terraform provider and any API call authenticate as the service account.

{% hint style="info" %}
The token is short-lived (about one hour), so mint it in the same job that uses it. The audience must match your organization URL exactly, and the token must carry a verified email (`--include-email`)—without it, the minted token omits the email claim and P0 rejects it.
{% endhint %}

## P0 CLI token

For local development, authenticate with your own P0 login through the [P0 CLI](/getting-started/getting-started-with-the-p0-cli).

1. Log in to your organization:

   ```bash
   p0 login <your-org>
   ```

   The CLI completes a browser single sign-on flow and stores your session in `~/.p0/identity.json`.
2. Use the session:
   * **With the Terraform provider** — you don't need a token. When `api_token` and `P0_API_TOKEN` are both unset, the provider automatically uses your CLI session.
   * **With the API** — print a bearer token from your session and pass it in the `Authorization` header:

     ```bash
     curl -H "Authorization: Bearer $(p0 print-bearer-token)" \
       https://api.p0.app/o/<your-org>/...
     ```

These tokens are short-lived and tied to your login, so use them for local development rather than durable automation. The token acts as you, with your P0 role.

## API key (legacy)

{% hint style="warning" %}
API keys are legacy and are being phased out in favor of [Google Cloud service-account tokens](#google-cloud-service-account-token-recommended). They still work, but prefer a service-account token for new integrations. A P0 API key carries the full **Owner** role and cannot be scoped to a lesser role.
{% endhint %}

An API key is a long-lived secret you generate in the P0 dashboard. To create one, see [Generating an API key](/p0-management/generating-an-api-key). Use it as your bearer token or export it as `P0_API_TOKEN`.

## Use your token

The token you obtained works the same way regardless of how you got it.

### With the P0 APIs

Pass the token in the `Authorization` header. The base URL includes your organization identifier:

```bash
curl -H "Authorization: Bearer <token>" \
  https://api.p0.app/o/<your-org>/...
```

### With the Terraform provider

Set the token in the `P0_API_TOKEN` environment variable, or pass it as the provider's `api_token` attribute:

```hcl
provider "p0" {
  org       = "my-org"
  api_token = var.p0_api_token
}
```

The provider resolves the token in this order: the `api_token` attribute, then the `P0_API_TOKEN` environment variable, then your P0 CLI session. Prefer the environment variable or a secrets manager over hardcoding the token in configuration.

## Best practices

* **Prefer short-lived tokens.** A Google Cloud service-account token or a CLI token expires on its own; an API key does not.
* **Grant least privilege.** The token inherits the P0 role of its identity. Use a read-only role such as Security Reviewer where write access is not required.
* **Never commit tokens.** Use an environment variable or a secrets manager, and rotate any long-lived secret regularly.

## Related

* [Generating an API key](/p0-management/generating-an-api-key)
* [P0 API overview](/getting-started/p0-api-overview)
* [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider)


# Manage P0 policies and settings as code

Use the P0 Terraform provider to manage access policies, RBAC role assignments, and just-in-time access settings as code, with worked examples and migration guidance.

This guide shows you how to manage P0's access policies, role assignments, and just-in-time (JIT) access settings with the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs). You manage the same policies and settings you would configure in the P0 dashboard. These include who can request access to what, who holds each P0 role, and the durations requestors can choose. You manage them as version-controlled, reviewable configuration.

If you haven't configured the provider yet, start with [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider), which covers authentication and installing your first integration. This guide picks up from a configured provider and focuses on policies and settings.

## Prerequisites

Before you begin, confirm the following:

* **P0 account with the Owner role** — You need the **Owner** role to manage policies, roles, and settings.
* **The P0 Terraform provider, version 0.52.0 or later** — Agentic access policies (the `agent` and `user` attributes) require `0.52.0`. The `p0_access_policy` resource itself is available from `0.51.0`, and the role-binding and JIT-settings resources from `0.50.0`.
* **A configured provider** — You have declared and authenticated the `p0` provider, as described in [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider).

Declare the provider with a version that includes the policy resources:

```hcl
terraform {
  required_providers {
    p0 = {
      source  = "p0-security/p0"
      version = "~> 0.52"
    }
  }
}

provider "p0" {
  org = "my-org"
}
```

The provider reads your token from the `P0_API_TOKEN` environment variable. See [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider) to authenticate, or [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) to get a token. A pipeline that applies policies or settings needs an identity with the **Owner** role.

{% hint style="info" %}
The P0 API has no read endpoint for role assignments or JIT settings, so Terraform cannot detect drift on those resources after they are applied. Manage each setting in one place—Terraform or the dashboard—rather than both.
{% endhint %}

## Manage access policies

An [access policy](/getting-started/configuring-access-policies) controls who can request access to what, and what approvals the request needs. The `p0_access_policy` resource models one policy with three parts:

* `requestor` — who the policy matches (a directory group, a specific user, anyone, or an agent).
* `resource` — the integration and, optionally, the resource filters the policy applies to.
* `approval` — how requests are approved, as an ordered list of approval steps.

The resources a policy references must already exist in P0. For example, the Okta group must come from an installed [Okta directory listing](/integrations/directory-integrations/okta), the `aws` integration from an installed AWS account, and PagerDuty from an in-app connection.

The following policy lets members of an Okta group request AWS access, auto-approved through PagerDuty when they give a reason:

```hcl
resource "p0_access_policy" "aws_developers_oncall" {
  name = "okta-aws-developers-oncall"

  requestor = {
    type   = "group"
    effect = "keep"
    groups = [{
      directory = "okta"
      id        = "00abcdefghijklmno697"
      label     = "AWS Developers"
    }]
  }

  resource = {
    type    = "integration"
    service = "aws"
    filters = {
      "tag" = {
        effect  = "keep"
        key     = "p0_grantable"
        pattern = "1|true"
      }
    }
  }

  approval = [{
    type        = "auto"
    integration = "pagerduty"
    options = {
      require_reason = true
    }
  }]
}
```

### Match agent requestors

For [agentic access policies](/access-management/just-in-time-access/access-policies/agentic-access-policies), set `requestor.type` to `agentic` and describe the agent under `agent`. The `user` block describes the human behind the agent; use `type = "none"` to match headless agent sessions.

This policy denies access to a specific MCP client agent running without a human present:

```hcl
resource "p0_access_policy" "mcp_agent_headless" {
  name = "mcp-agent-headless"

  requestor = {
    type = "agentic"
    agent = {
      type      = "agent-client"
      client_id = "my-agent-client"
    }
    user = {
      type = "none"
    }
  }

  resource = {
    type    = "integration"
    service = "aws"
  }

  approval = [{
    type = "deny"
  }]
}
```

The `agent.type` value selects how the policy matches an agent: `any` matches any agent, `agent-client` matches agents connecting through a specific gateway client, `agent-owner` matches an agent owned by a specific user, `owner-group` matches an agent owned by a member of a directory group, and `provider` matches an agent federated by an identity provider.

## Manage P0 role assignments

The [P0 RBAC roles](/p0-management/role-based-access-control) map to eight role-binding resources—a user and a group variant for each of the four assignable roles:

| Role              | User resource               | Group resource               |
| ----------------- | --------------------------- | ---------------------------- |
| Owner             | `p0_owner_user`             | `p0_owner_group`             |
| Security Reviewer | `p0_security_reviewer_user` | `p0_security_reviewer_group` |
| Assessment User   | `p0_assessment_user`        | `p0_assessment_group`        |
| Assessment Viewer | `p0_assessment_viewer_user` | `p0_assessment_viewer_group` |

Each user resource takes an `email`, and each group resource takes a `group`:

```hcl
resource "p0_owner_user" "admin" {
  email = "alice@example.com"
}

resource "p0_security_reviewer_group" "reviewers" {
  group = "eng-security"
}
```

## Manage just-in-time access settings

Two singleton resources manage the organization's JIT access settings. Declare each at most once.

`p0_access_durations` sets the organization's duration policy. Each field takes a `time` (a whole number) and a `unit` (`s`, `m`, `h`, `d`, or `w`):

```hcl
resource "p0_access_durations" "org" {
  # Maximum time between a request and its approval.
  approvable = {
    time = 14
    unit = "d"
  }

  # Maximum duration access may be granted for.
  max_access = {
    time = 180
    unit = "d"
  }

  # Maximum duration of standing (persistent) access before re-approval.
  standing_access = {
    time = 24
    unit = "h"
  }
}
```

`p0_expiry_options` sets the request-duration presets requestors can select:

```hcl
resource "p0_expiry_options" "org" {
  options = [
    { time = 5, unit = "m" },
    { time = 1, unit = "h" },
    { time = 24, unit = "h" },
    { time = 168, unit = "h" },
  ]
}
```

## Migrate from p0\_routing\_rule

Access policies were previously called routing rules, and the `p0_routing_rule` resource is now deprecated. It shares the same schema and behavior as `p0_access_policy`, but it will be removed in a future release. Migrate any existing `p0_routing_rule` resources to `p0_access_policy`.

To migrate without destroying and recreating the policy, rename the resource type and add a `moved` block, which requires Terraform 1.8 or later:

```hcl
resource "p0_access_policy" "aws_developers_oncall" {
  name = "okta-aws-developers-oncall"
  # ...unchanged policy configuration...
}

moved {
  from = p0_routing_rule.aws_developers_oncall
  to   = p0_access_policy.aws_developers_oncall
}
```

Run `terraform plan` and confirm the plan reports a move rather than a create or destroy, then apply. After the move succeeds, you can remove the `moved` block.

## Apply and verify

Apply your configuration:

```bash
terraform apply
```

Review the plan and confirm. To verify a policy applied, open **Access Policies** in the P0 dashboard and confirm the policy appears, or check the resource state:

```bash
terraform state show p0_access_policy.aws_developers_oncall
```

Confirm role assignments in **Role-Based Access Control** and JIT settings in the JIT access settings, both in the P0 dashboard.

## Next steps

* **Understand access policies** — See [Configure access policies](/getting-started/configuring-access-policies) for the concepts behind requestors, resources, and approvals.
* **Read the resource reference** — The full argument reference for each resource is in the [Terraform Registry](https://registry.terraform.io/providers/p0-security/p0/latest/docs), including [`p0_access_policy`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/access_policy), [`p0_access_durations`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/access_durations), and [`p0_expiry_options`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/expiry_options).
* **Install integrations as code** — See [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider) to install integrations with the provider.


# P0 API overview

A map of every P0 API — access requests, policies, organization settings, inventory export, and integration webhooks — with the shared base URL, authentication, and permissions that apply to all of th

P0 exposes a set of HTTP APIs so you can drive access, configure your organization, and export inventory from your own code instead of the P0 dashboard. This page maps every P0 API and describes the base URL, authentication, and permissions that they share.

If you are new to the P0 API, start with the [Automate access requests with the API](/getting-started/automate-access-requests-with-the-api) walkthrough, then use the API map to find the reference for each endpoint.

## Base URL

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

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

Your `orgId` is the organization slug shown in the P0 dashboard URL and as your org name in the CLI.

## Authentication

The APIs that P0 hosts authenticate with a bearer token. Include the token in the `Authorization` header on every request:

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.p0.app/o/{orgId}/...
```

To get a token, see [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api). We recommend a Google Cloud service-account token; a legacy API key also works.

The Webhook APIs are the exception. Because P0 calls an endpoint that you host, you secure that endpoint on your side rather than with a P0 API key. See each webhook page for its authentication model.

## Permissions

A token carries the P0 role of the identity behind it, so a token is only as privileged as that identity. The exception is an API key, which always carries the **Owner** role and cannot be scoped to a lesser role.

What an identity can do also depends on your access policies. An Owner can change organization settings and revoke any grant. Approving and denying requests follow your access policies rather than the Owner role. Creating a request is open to any member of your organization. Grant each identity the least privilege it needs, and treat a high-privilege token as a sensitive credential:

* Store tokens in a secrets manager or environment variables, and never commit them to version control.
* Rotate long-lived secrets regularly, and delete unused API keys.
* In production, route approvals through your [access policies](/access-management/just-in-time-access/access-policies) rather than approving every request with the same automation identity.

## API map

### Just-in-time access

Request and manage ephemeral access programmatically. See the [Just-in-time API](/access-management/just-in-time-access/just-in-time-api) overview for how these fit together.

| API                                                                                                | Purpose                                                                                                                      |
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| [Command API](/access-management/just-in-time-access/just-in-time-api/command-api)                 | Create an access request. The request body mirrors the [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request) CLI command. |
| [Access Requests API](/access-management/just-in-time-access/just-in-time-api/access-requests-api) | Approve, deny, or revoke an existing request by ID.                                                                          |
| [Access Policies API](/access-management/just-in-time-access/just-in-time-api/access-policies-api) | Create, read, update, and delete the access policies that route requests to approval paths.                                  |

### Organization management

Configure your organization's settings as code. See the [Management API](/p0-management/management-api) overview for the full group.

| API                                                                                  | Purpose                                                                                 |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| [Role Management API](/p0-management/management-api/role-management-api)             | Assign and remove P0 roles for users and groups.                                        |
| [Just-in-time settings API](/p0-management/management-api/just-in-time-settings-api) | Configure custom expiry options and approvable, maximum, and standing access durations. |
| [API Key Management API](/p0-management/management-api/api-key-management-api)       | Create, list, and delete API keys.                                                      |

### Inventory

| API                                                     | Purpose                                                                                                                |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| [Inventory export API](/inventory/inventory-export-api) | List assessed environments and export the grouped inventory of identities, credentials, grants, and resources as JSON. |

### Integration APIs

P0 exposes APIs to configure resource integrations, such as AWS. Rather than calling these APIs directly, we strongly recommend the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs). It simplifies configuration and lets you manage both the P0 install and the cloud resources an integration depends on — for example, the AWS role P0 assumes — in one place.

The integration APIs are available for direct use on request.

### Webhook APIs (you implement)

These integrations reverse the direction: P0 calls an HTTP endpoint that you host. Each page includes an OpenAPI specification for the payloads P0 sends.

| API                                                                      | Purpose                                                                                                           |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| [Custom Resource](/integrations/resource-integrations/custom-resource)   | Receive grant, revoke, and list events so P0 can manage access to a resource P0 does not integrate with natively. |
| [Custom Notifiers](/integrations/notifier-integrations/custom-notifiers) | Receive notification events, such as a created request or pre-approval, to deliver through your own channel.      |

### Related references

| Reference                                                            | Purpose                                                                                                 |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| [Audit log format](/integrations/siem-integrations/audit-log-format) | The payload and field reference for audit events P0 streams to your SIEM, including API-driven actions. |

## Infrastructure as code

You can manage roles, access durations, expiry options, and integration installs as code with the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs) instead of calling the management APIs directly. Managing organization settings requires provider version `0.50.0` or later.

## Related

* [Automate access requests with the API](/getting-started/automate-access-requests-with-the-api) — a step-by-step walkthrough that ties the Command and Access Requests APIs together.
* [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) — get a bearer token to authenticate your requests.
* [Get started with the P0 Terraform provider](/getting-started/get-started-with-the-p0-terraform-provider) — manage P0 as code.


# Automate access requests with the API

Request, approve, deny, and revoke just-in-time access programmatically with the P0 Command and Access Requests APIs. Drive access from bots, CI/CD pipelines, and internal tooling instead of the P0 da

This guide shows you how to drive P0 just-in-time access from your own code. You use the [Command API](/access-management/just-in-time-access/just-in-time-api/command-api) to create an access request, check its status, and then use the [Access Requests API](/access-management/just-in-time-access/just-in-time-api/access-requests-api) to approve, deny, or revoke it.

Automating these steps lets you wire P0 into bots, CI/CD pipelines, and internal security tooling — for example, requesting a short-lived role during a deployment, or auto-approving access when an alert fires. Every request still runs through your organization's access policies, guardrails, and audit trail, exactly as it does in the dashboard.

## Prerequisites

Before you begin, confirm the following:

* **A P0 organization** — You know your organization slug (`orgId`), shown in the P0 dashboard URL and in the CLI as your org name.
* **A P0 API token** — You have a token, stored securely, belonging to an identity in your P0 organization. Approving and denying also require that identity to be an approver under your access policies. See [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) for how to get a token.
* **A configured resource integration** — At least one integration (AWS, Google Cloud, Azure, SSH, and so on) is installed, so there is something to request. See [Resource integrations](/integrations/resource-integrations).
* **A JSON tool** — The examples use [`curl`](https://curl.se/) and [`jq`](https://jqlang.github.io/jq/) to send requests and read responses.

{% hint style="info" %}
A token carries the P0 role of the identity behind it. An Owner can revoke any grant, but approving and denying requests follow your [access policies](/access-management/just-in-time-access/access-policies) rather than the Owner role, and any member of your organization can create a request. In production, route approvals through your access policies rather than approving every request with the same automation identity.
{% endhint %}

## Set your base URL and authentication

Every endpoint lives under your organization's base URL and authenticates with a bearer token. To get a token, see [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api).

```bash
export P0_ORG="your-org-slug"
export P0_API_TOKEN="your-token"
export P0_BASE_URL="https://api.p0.app/o/${P0_ORG}"
```

Include the token in the `Authorization` header on every request:

```bash
curl -H "Authorization: Bearer ${P0_API_TOKEN}" "${P0_BASE_URL}/..."
```

## Create an access request

Send a `POST` request to the Command API at `/o/{orgId}/command`. The body has two fields:

| Field        | Type             | Description                                                                                                                             |
| ------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `argv`       | array of strings | The request arguments, matching the [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request) CLI command with the leading `p0` removed. |
| `scriptName` | string           | The client name to record. Use `"p0"`.                                                                                                  |

The `argv` array mirrors the CLI exactly. The command `p0 request aws role MyReadOnlyRole --account 123456789012 --reason "..."` becomes the array below:

```bash
curl -s -X POST "${P0_BASE_URL}/command" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "argv": [
      "request", "aws", "role", "MyReadOnlyRole",
      "--account", "123456789012",
      "--reason", "Investigating S3 access issues"
    ],
    "scriptName": "p0"
  }'
```

The response confirms that P0 created the request and returns its ID in the `id` field. Capture the ID for the next steps:

```bash
REQUEST_ID=$(curl -s -X POST "${P0_BASE_URL}/command" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "argv": ["request", "aws", "role", "MyReadOnlyRole", "--account", "123456789012", "--reason", "Automated deploy access"],
    "scriptName": "p0"
  }' | jq -r '.id')

echo "Created request ${REQUEST_ID}"
```

{% hint style="info" %}
To find the right `argv` for any resource, run the equivalent CLI command with `--help` — for example `p0 request gcloud --help`. See [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request) for the full list of providers and subcommands. For the complete request and response schema, see the [Command API reference](/access-management/just-in-time-access/just-in-time-api/command-api).
{% endhint %}

## Check the request status

To read the current state of a request from a script, send a `GET` request to `/o/{orgId}/permission-requests/{requestId}`. It returns the full request document as plain JSON:

```bash
curl -s "${P0_BASE_URL}/permission-requests/${REQUEST_ID}" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" | jq
```

Poll this endpoint to wait for a decision — for example, until the status moves from pending to approved and provisioned.

{% hint style="info" %}
The `/command/{requestId}/poll` endpoint streams live updates over Server-Sent Events for interactive clients such as the CLI and web app. For one-shot status checks in automation, use the `GET /permission-requests/{requestId}` endpoint shown above.
{% endhint %}

## Approve, deny, or revoke the request

The Access Requests API acts on an existing request by ID. Each action is a `POST` to `/o/{orgId}/permission-requests/{requestId}/{action}`, where `{action}` is `approve`, `deny`, or `revoke`.

Approve a request:

```bash
curl -s -X POST "${P0_BASE_URL}/permission-requests/${REQUEST_ID}/approve" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Each action returns a success confirmation:

```json
{ "message": "Success" }
```

When you approve a request, you can override the grant duration with an optional body. Set `expirationLength` to a P0 duration such as `30m`, `2h`, or `1d`, and set `isCustomExpiry` to `true`:

```bash
curl -s -X POST "${P0_BASE_URL}/permission-requests/${REQUEST_ID}/approve" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "expirationLength": "2h", "isCustomExpiry": true }'
```

Deny a pending request, or revoke an active grant before it expires, by changing the action:

```bash
# Deny a pending request
curl -s -X POST "${P0_BASE_URL}/permission-requests/${REQUEST_ID}/deny" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" -H "Content-Type: application/json" -d '{}'

# Revoke an active grant
curl -s -X POST "${P0_BASE_URL}/permission-requests/${REQUEST_ID}/revoke" \
  -H "Authorization: Bearer ${P0_API_TOKEN}" -H "Content-Type: application/json" -d '{}'
```

{% hint style="warning" %}
P0 auto-revokes access at expiry, so you only need `revoke` to end a grant early. Denying or revoking a request cannot be undone — submit a new request to restore access.
{% endhint %}

## Verify it worked

Confirm the full lifecycle end to end:

1. **Check the status.** Send a `GET` to `/permission-requests/{requestId}` and confirm the state reflects your action (approved, denied, or revoked).
2. **Confirm provisioning.** For an approved request, verify the underlying grant exists — for example, assume the AWS role with [`p0 aws role assume`](/p0-cli/p0-commands-and-usage/p0-aws-role-assume), or check the resource directly.
3. **Review the audit trail.** Each action writes an audit event (`api.jit.permission-requests.approved`, `.denied`, or `.revoked`). Find it in the P0 dashboard or in your [SIEM integration](/integrations/siem-integrations).

## Troubleshooting

| Symptom                           | Cause                                                         | Fix                                                                                                                                                                                                                                  |
| --------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `401 Unauthorized`                | Missing, invalid, or expired token                            | Confirm the `Authorization: Bearer` header is set and the token is valid. The recommended Google Cloud and CLI tokens are short-lived, so mint a fresh one if it may have expired.                                                   |
| `403 Forbidden`                   | The token's identity lacks permission for this action         | Confirm the identity is a member of your P0 organization. For approve and deny, it must also be an approver under the matching [access policy](/access-management/just-in-time-access/access-policies). Owners can revoke any grant. |
| `404 Not Found` on an action      | Wrong `requestId` or `orgId`, or the request no longer exists | Recheck the ID returned when you created the request and confirm `P0_ORG` matches your organization slug.                                                                                                                            |
| `argv is not an array of strings` | The `argv` field is missing or malformed                      | Send `argv` as a JSON array of strings, and put each flag and its value as separate elements.                                                                                                                                        |
| Request stays pending             | An approval policy requires a human approver                  | Approve it with the Access Requests API, or adjust the matching [access policy](/access-management/just-in-time-access/access-policies).                                                                                             |

## Related

* [Command API](/access-management/just-in-time-access/just-in-time-api/command-api) — create access requests programmatically.
* [Access Requests API](/access-management/just-in-time-access/just-in-time-api/access-requests-api) — approve, deny, and revoke requests.
* [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) — get a token to authenticate these requests.
* [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request) — the CLI command whose arguments the `argv` array mirrors.
* [Management API](/p0-management/management-api) — programmatically configure roles and JIT settings.


# Deploying the P0 AI Gateway

Architecture and deployment of the self-hosted P0 AI Gateway and OAuth server using the batteries-included Helm chart or the Terraform module.

The P0 AI Gateway is the self-hosted runtime enforcement layer for the [P0 AuthZ Control Plane for Agents](/readme/agentic-control-plane). You deploy it into your own Kubernetes cluster, where it sits in the data path between your AI agents and your MCP servers. Because it runs in your environment, your sensitive data and upstream credentials never leave your perimeter.

This guide covers the architecture of the self-hosted components and how to install them.

## Architecture

Agentic authorization spans three runtime planes: two you self-host, and the P0 AuthZ Control Plane™ for Agents, which P0 delivers as SaaS.

```mermaid
flowchart LR
  A["AI agent<br/>(MCP client)"]

  subgraph self["Your environment (self-hosted)"]
    direction TB
    GW["P0 AI Gateway"]
    OS["P0 OAuth Server"]
    UP["Upstream MCP servers"]
  end

  subgraph saas["P0 SaaS"]
    CTRL["AuthZ Control Plane"]
  end

  IDP["Your IdP<br/>(Okta, Entra ID, Google)"]
  RES["Apps & cloud resources"]
  SIEM["Logging sink / SIEM<br/>(Splunk, Elastic, Grafana, …)"]

  A -->|"OAuth token (user + agent)"| GW
  A -.->|"authenticate"| OS
  OS <-->|"federate"| IDP
  GW <-->|"policy sync, approvals"| CTRL
  GW -->|"token exchange, WIF credentials"| OS
  GW -->|"proxied tool calls"| UP
  GW -->|"audit"| SIEM
  UP -->|"access"| RES
  CTRL -->|"session identity and access provisioning"| RES
```

**P0 AI Gateway** (self-hosted). The runtime enforcement point for agent traffic. It proxies MCP tool calls to your upstream MCP servers, verifies the identity token on every call, evaluates policy, and ships audit activity to your logging sink or SIEM. For upstream servers that require just-in-time credentials, it launches an isolated, per-session container for each grant and injects short-lived credentials into it. The gateway periodically syncs MCP server definitions and tool catalogs from the AuthZ Control Plane, and calls the AuthZ Control Plane to drive the access request → approval → grant → revoke lifecycle.

**P0 OAuth Server** (self-hosted). The authorization server and credential broker. It federates with your existing IdP to authenticate the user, issues signed tokens that bind the user and agent identities together, and brokers downstream cloud credentials. When the gateway needs to reach a cloud resource, the OAuth Server mints short-lived Workload Identity Federation (WIF) credentials for that session (for example, exchanging a signed OIDC token with GCP STS or AWS `AssumeRoleWithWebIdentity`), so cloud credentials are minted per session rather than stored.

**P0 AuthZ Control Plane™ for Agents** (SaaS). The authorization and policy layer. It holds your MCP roles and policies, pushes server definitions to the gateway, decides whether each tool call is allowed, coordinates approvals, and provisions session identity and access in your upstream apps and cloud resources for deeper enforcement. The AuthZ Control Plane is not deployed by these charts. The self-hosted components reach it over HTTPS at your tenant URL.

## Prerequisites

* A Kubernetes cluster (EKS, GKE, or AKS).
* A block-storage `StorageClass` for the bundled PostgreSQL. For example, `gp2` on EKS (requires the EBS CSI driver add-on), `standard-rwo` on GKE, or `managed-premium` on AKS.
* A Google OAuth 2.0 client (client ID and client secret) for user sign-in. The self-hosted OAuth server authenticates end users through Google today.
* A P0 SaaS tenant: you need your tenant's P0 URL and audience.
* Control over a public DNS record for the gateway's hostname (required for TLS certificate issuance).
* `helm` (v3, with OCI support) and `kubectl` configured against your cluster.

## Deployment options

P0 publishes three artifacts. Most deployments should use the **batteries-included Helm chart**.

| Option                                                                                                                         | What it is                                                                                                                                                                         | When to use it                                                                                                |
| ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Batteries-included Helm chart**: [`p0security/p0-helm-oauthed-mcp`](https://hub.docker.com/r/p0security/p0-helm-oauthed-mcp) | An umbrella chart that deploys the gateway and OAuth server **plus** everything they depend on: Envoy Gateway, cert-manager (with Let's Encrypt), PostgreSQL, and Valkey.          | Standard installs, and any cluster that doesn't already provide these dependencies. **Recommended.**          |
| **Basic Helm chart**: [`p0security/oauthed-mcp`](https://hub.docker.com/r/p0security/oauthed-mcp)                              | Deploys only the gateway and OAuth server and their wiring. It does **not** include the Gateway API controller, cert-manager/TLS, PostgreSQL, or Valkey. You bring those yourself. | Clusters that already manage those dependencies (for example, a platform team's shared Postgres and ingress). |
| **Terraform module**                                                                                                           | A thin wrapper that installs the batteries-included chart via a `helm_release`.                                                                                                    | Teams that manage infrastructure as code with Terraform.                                                      |

The rest of this guide uses the batteries-included chart.

## Install with the batteries-included Helm chart

### 1. Create the `app-secrets` secret

The chart doesn't create secrets for you. The `app-secrets` Secret must exist in the target namespace before you install. It holds the signing keys and credentials the services need:

| Key                                  | Description                                                                                                                                                                              |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` | Key pair the OAuth server uses to sign and verify tokens. Store each as **base64-encoded PEM**: the OAuth server base64-decodes both values at startup, so a raw PEM breaks the install. |
| `KMS_LOCAL_ENCRYPTION_KEY`           | 32-byte (64 hex character) key for local encryption.                                                                                                                                     |
| `REFRESH_TOKEN_SECRET`               | Base64-encoded secret (≥32 bytes) for refresh tokens.                                                                                                                                    |
| `OIDC_CLIENT_SECRET`                 | The client secret for your Google OAuth client.                                                                                                                                          |
| `POSTGRESQL_PASSWORD`                | Password for the bundled PostgreSQL.                                                                                                                                                     |

Generate the signing key pair and random secrets, then create the Secret in your namespace:

```bash
# Create the namespace (the Secret must exist before you install)
kubectl create namespace oauthed-mcp

# RSA signing key pair for the OAuth server
openssl genrsa -out jwt-private.pem 2048
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem

# Create app-secrets with the generated keys, random secrets, and your OIDC client secret
kubectl -n oauthed-mcp create secret generic app-secrets \
  --from-literal=JWT_PRIVATE_KEY="$(openssl base64 -A -in jwt-private.pem)" \
  --from-literal=JWT_PUBLIC_KEY="$(openssl base64 -A -in jwt-public.pem)" \
  --from-literal=KMS_LOCAL_ENCRYPTION_KEY="$(openssl rand -hex 32)" \
  --from-literal=REFRESH_TOKEN_SECRET="$(openssl rand -base64 32)" \
  --from-literal=OIDC_CLIENT_SECRET="<your-oidc-client-secret>" \
  --from-literal=POSTGRESQL_PASSWORD="$(openssl rand -base64 24)"
```

### 2. Create your values file

{% hint style="info" %}
Google Workspace is currently the only supported identity provider for user sign-in. The `oidcClientId` in the following example is a Google OAuth 2.0 client ID.
{% endhint %}

Create a `my-values.yaml` with the values required for your environment:

```yaml
letsEncrypt:
  email: platform@example.com   # for TLS certificate registration
  env: staging                  # switch to "prod" once staging certs succeed

postgresql:
  storageClass: gp2             # your cluster's block-storage class

oauthed-mcp:
  imageRegistry: p0security     # override only if self-building images

  gateway:
    className: oauthed-mcp      # unique per release; use the release name from the `helm install` command in step 3
    host: gateway.example.com   # public hostname for the gateway
    tlsSecretName: gateway-tls

  oauthServer:
    oidcClientId: <your-client-id>.apps.googleusercontent.com   # Google OAuth 2.0 client ID; user sign-in goes through Google
    p0Url: https://api.p0.app/o/<tenant>
    p0Audience: https://api.p0.app/o/<tenant>
    gatewayIss: https://gateway.example.com

  mcpServer:
    oauthServer: http://oauth-server:52701                 # in-cluster URL
    p0Url: https://api.p0.app/o/<tenant>
    p0Audience: https://api.p0.app/o/<tenant>              # equal to p0Url
    gatewayIss: https://gateway.example.com
    managementAudience: https://gateway.example.com/manage # gateway address + "/manage" path
    manageGcpIssuers: https://accounts.google.com          # accept Google-issued identity tokens on /manage; required for the P0 AuthZ Control Plane to reach the gateway
    manageAllowedEmails: customer-p0-gcp-sa@p0-prod.iam.gserviceaccount.com # P0 service account allowed to call the gateway's /manage endpoint
```

If your cluster already provides any of the bundled dependencies, disable the corresponding subchart (`cert-manager.enabled`, `envoy-gateway.enabled`, `postgresql.enabled`, `valkey.enabled`) and point the services at your managed services instead.

### 3. Install the chart

```bash
helm install oauthed-mcp \
  oci://registry-1.docker.io/p0security/p0-helm-oauthed-mcp \
  --version <chart-version> \
  --namespace oauthed-mcp \
  -f my-values.yaml
```

{% hint style="warning" %}
Don't pass `--wait`. cert-manager can't issue the TLS certificate until you add the DNS record in the next step, so `--wait` would block and eventually time out.
{% endhint %}

### 4. Add the DNS record

Find the load balancer address assigned to the gateway, then create a public DNS record for your gateway host that points at it:

```bash
kubectl -n oauthed-mcp get gateway oauthed-mcp \
  -o jsonpath='{.status.addresses[0].value}'
```

cert-manager retries certificate issuance automatically; the certificate becomes `Ready` once DNS resolves.

### 5. Verify

Confirm the pods are running:

```bash
kubectl -n oauthed-mcp get pods
```

Confirm DNS resolves and the certificate is `Ready` (this may take several minutes):

```bash
kubectl -n oauthed-mcp get certificate
```

Desired output:

```
NAME          READY   SECRET        AGE
gateway-tls   True    gateway-tls   15m
```

Once staging certificates issue successfully, promote to production certificates:

```bash
helm upgrade oauthed-mcp \
  oci://registry-1.docker.io/p0security/p0-helm-oauthed-mcp \
  --namespace oauthed-mcp --reuse-values \
  --set letsEncrypt.env=prod
```

The `prod` certificate is required for testing the backends. The `gateway-tls` certificate is reissued by cert-manager. Wait for it to be `Ready` again.

Confirm both backends respond through the gateway host over public DNS. Replace `<gateway-host>` with your gateway's public hostname:

```bash
# OAuth server (exact-match route) - expect 200
curl -sS -o /dev/null -w '%{http_code}\n' https://<gateway-host>/.well-known/oauth-authorization-server

# Gateway (catch-all route) - expect 200
curl -sS -o /dev/null -w '%{http_code}\n' https://<gateway-host>/health

# HTTP is redirected to HTTPS - expect 301
curl -sS -o /dev/null -w '%{http_code}\n' http://<gateway-host>/health
```

## Install with the Terraform module

{% hint style="info" %}
To register the gateway with Terraform, use the [`p0_agentic_gateway` resource](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_gateway) in the P0 Terraform provider.
{% endhint %}

The Terraform module wraps the batteries-included chart in a single `helm_release`. It requires Terraform ≥ 1.7 and the `hashicorp/helm` provider ≥ 3.0.

```hcl
module "oauthed_mcp" {
  source  = "p0-security/p0-oauthed-mcp/kubernetes"
  version = "<module-version>"

  release_name     = "oauthed-mcp"
  namespace        = "oauthed-mcp"
  create_namespace = true

  # Same YAML as my-values.yaml above, passed through to the chart.
  values = [file("${path.module}/my-values.yaml")]
}
```

The module passes your values straight through to the chart, so the prerequisites are the same: you still create the `app-secrets` Secret and add the DNS record yourself. Each module version pins a specific chart version. Check the module's compatibility matrix.

## Next steps

Once you deploy the gateway, register it with P0 and configure the MCP servers it fronts:

* [Agentic Gateway integration](/integrations/resource-integrations/agentic-gateway): register the gateway and configure upstream MCP servers.


# Share P0 with your team

Use the P0 slackbot to help your users learn how to create access requests through P0.

If you've installed the slack integration, you can use the Slack bot to instruct users on requesting access through P0.

Type `/p0 share` in any channel or DM, and the P0 bot will respond with a message that explains how to use P0 for access requests and includes a button to immediately begin an access request.

<figure><img src="/files/OcEKC8tF9qt88alhT6kg" alt="Slack message from P0 Security bot explaining how to use /p0 request for access requests, with a Request Access button"><figcaption></figcaption></figure>

`/p0 share` also accepts an optional user argument: use the command `/p0 share USER_EMAIL` or `/p0 share @USER_MENTION` to have the bot directly DM the specified user.

This can help your organization transition towards using P0 as the only system for access requests: if somebody makes a request through another means, use `/p0 share` to inform them of P0, and they can quickly get started with their first request.


# Configure access policies

Set up access policies in Policy Studio to control who can request access, what they can request, and who approves it. A step-by-step guide for security administrators.

Access policies give you fine-grained control over your organization's just-in-time access workflow. With access policies, you define who can request access to specific resources, and who approves those requests.

This guide walks you through creating your first access policies in Policy Studio, covering three common scenarios:

1. [Route requests by team to the correct approver](#scenario-1-route-requests-by-team)
2. [Restrict access to production resources](#scenario-2-restrict-access-to-production-resources)
3. [Auto-approve requests for on-call engineers](#scenario-3-auto-approve-on-call-engineers)

{% hint style="info" %}
Access policies require a Pro-tier P0 subscription. If you are on the free tier, your organization uses [default approvals](/access-management/just-in-time-access/approving-access#configuring-approvals) instead.
{% endhint %}

## Prerequisites

Before you begin, confirm the following:

* You have a P0 account with the **Owner** or **Security Reviewer** role.
* You have completed the [Getting Started with Just-in-Time Access](/getting-started/getting-started-with-just-in-time-access) guide, including installing at least one resource integration.
* You have a [directory integration](/integrations/directory-integrations) connected (Google Workspace, Okta, or Microsoft Entra ID) if you plan to route by group.
* *(Optional)* You have a [PagerDuty](/integrations/approval-integrations/pagerduty) or [Incident.io](/integrations/approval-integrations/incidentio) integration installed if you plan to use on-call auto-approvals.

## Open Policy Studio

1. Sign in to [p0.app](https://p0.app).
2. Navigate to **Policy Studio** in the left sidebar, or go directly to `https://p0.app/o/<your-organization>/policies`.

Policy Studio lists your current access policies in a table. To edit them as YAML, select the **View options** (⋯) button in the page header, then select **YAML view**. If this is your first time, the list is empty. Your organization is using default approvals.

{% hint style="info" %}
Saving any policy configuration in Policy Studio switches your organization from default approvals to policy-based approvals. All access requests are then evaluated against your access policies.
{% endhint %}

## Scenario 1: Route requests by team

Route your engineering team's requests to the SRE team for approval, and your data team's requests to the Data Ops team.

1. In Policy Studio, enter the following access policies:

```yaml
- requestor:
    type: group
    id: engineering@yourcompany.com
    label: Engineering
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: sre@yourcompany.com
      label: SRE Team
      directory: workspace
      options: {allowOneParty: false}
- requestor:
    type: group
    id: data-team@yourcompany.com
    label: Data Team
    directory: workspace
  resource:
    type: integration
    service: snowflake
  approval:
    - type: group
      id: data-ops@yourcompany.com
      label: Data Ops
      directory: workspace
      options: {allowOneParty: false}
```

2. Replace the group email addresses with your organization's actual directory groups.
3. Click **Submit** to activate the access policies.

**What this does:**

* Any engineer who requests access to any resource gets routed to the SRE team for approval.
* Any data team member who requests Snowflake access gets routed to Data Ops for approval.
* `allowOneParty: false` prevents requestors from approving their own requests.

{% hint style="warning" %}
If a request does not match any access policy, P0 rejects it with the message: "This resource doesn't exist, or your organization doesn't allow this principal to access this resource." Add a catch-all policy at the end of your workflow if you want unmatched requests to fall through to default approvers.
{% endhint %}

### Add a catch-all policy

To ensure unmatched requests still reach your security reviewers, add this policy at the end of your workflow:

```yaml
- requestor:
    type: any
  resource:
    type: any
  approval:
    - type: p0
```

The `type: p0` approval routes requests to the Security Reviewers configured under **P0 Management** → **Access control**.

## Scenario 2: Restrict access to production resources

Block requests to production AWS accounts while allowing development and staging access with different approval requirements.

1. In Policy Studio, add the following policies. Replace the AWS account IDs and Okta group IDs with your own values:

```yaml
# Development - one-party approval (engineers can self-approve)
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    filters:
      policy: {effect: keep, key: arn, pattern: "^arn:aws:iam::111111111111:policy/"}
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: {allowOneParty: true}

# Staging - peer approval required, reason mandatory
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    filters:
      policy: {effect: keep, key: arn, pattern: "^arn:aws:iam::222222222222:policy/"}
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: {allowOneParty: false, requireReason: true}

# Production - deny all access
- requestor:
    type: any
  resource:
    type: integration
    service: aws
    filters:
      policy: {effect: keep, key: arn, pattern: "^arn:aws:iam::333333333333:policy/"}
  approval:
    - type: deny
```

2. Click **Submit**.

**What this does:**

* **Development** (account `111111111111`): Engineers can self-approve their own requests.
* **Staging** (account `222222222222`): Engineers need a peer to approve, and must provide a reason.
* **Production** (account `333333333333`): P0 denies all requests. No one can request access through P0.

{% hint style="info" %}
Deny policies always take precedence. If any matching policy specifies `type: deny`, the request is denied immediately, regardless of other matching policies.
{% endhint %}

### Exclude dangerous permissions

To further restrict which AWS permissions engineers can request, add filters that remove overly broad policies:

```yaml
resource:
  type: integration
  service: aws
  accessType: policy
  filters:
    policy: {effect: remove, key: arn, pattern: FullAccess}
    tag: {effect: keep, key: P0Grantable, pattern: "^true$"}
```

This removes any policy containing "FullAccess" in its ARN, and only keeps policies tagged with `P0Grantable: true`.

## Scenario 3: Auto-approve on-call engineers

Automatically approve requests from engineers who are currently on-call in PagerDuty or Incident.io, while directing all other requests to the SRE team.

1. Confirm that your [PagerDuty](/integrations/approval-integrations/pagerduty) or [Incident.io](/integrations/approval-integrations/incidentio) integration is installed.
2. In Policy Studio, add the following policies:

{% tabs %}
{% tab title="PagerDuty" %}

```yaml
# On-call engineers get auto-approved
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: pagerduty
      options: {requireReason: true}
    - type: group
      id: sre@yourcompany.com
      label: SRE Team
      directory: workspace
      options: {allowOneParty: false}
```

{% endtab %}

{% tab title="Incident.io" %}

```yaml
# On-call engineers get auto-approved
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: incidentio
      options: {requireReason: true}
    - type: group
      id: sre@yourcompany.com
      label: SRE Team
      directory: workspace
      options: {allowOneParty: false}
```

{% endtab %}
{% endtabs %}

3. Click **Submit**.

**What this does:**

* If the requestor is currently on-call (as determined by PagerDuty escalation policies or Incident.io schedules), P0 automatically approves their request for one hour.
* If the requestor is not on-call, P0 routes the request to the SRE team for manual approval.
* `requireReason: true` ensures all requests include a justification in the audit log, even auto-approved ones.

### Add escalation for urgent requests

You can also configure on-call users as escalation approvers. When a request is pending, the requestor can escalate it to the on-call engineer:

```yaml
approval:
  - type: p0
    options: {requireReason: true, allowOneParty: false}
  - type: escalation
    integration: pagerduty
    options: {requireReason: true, allowOneParty: false}
    services: [PSJXXXG]
```

Replace `PSJXXXG` with your PagerDuty service ID. When the requestor escalates, PagerDuty creates an incident against the specified service, and the on-call engineer receives both a PagerDuty alert and a P0 approval notification.

## Verify your access policies

After saving your access policies, verify that they work as expected:

1. Open Slack and use `/p0 request` (or the [web request modal](/access-management/just-in-time-access/requesting-access/web-request-modal)) to create a test access request that matches one of your policies.
2. Confirm that the correct approver receives the approval notification.
3. If you configured deny policies, submit a request that matches a denied resource and confirm that P0 blocks it.
4. Check the **Activity** page at `https://p0.app/o/<your-organization>/access-management/activity` to view the request and verify the policy decision.

{% hint style="info" %}
When multiple policies with different manual approvers match the same request, P0 notifies all matching approvers. Approval from any one of them is sufficient.
{% endhint %}

## Troubleshooting

| Symptom                                                | Cause                                                                                    | Solution                                                                                                                                                                                  |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Request rejected with "This resource doesn't exist..." | No access policy matches the request.                                                    | Add a catch-all policy or verify that your requestor and resource filters match the request.                                                                                              |
| Wrong approver notified                                | A broader policy matches before the specific one.                                        | Reorder policies so more specific policies come first. Deny policies always take precedence regardless of order.                                                                          |
| Auto-approval not working                              | The requestor is not on-call, or the PagerDuty/Incident.io integration is not installed. | Verify the requestor's on-call status in your incident management tool, and confirm the integration is active in **Integrations**.                                                        |
| Filter not matching                                    | Regex pattern mismatch.                                                                  | Patterns are unanchored by default. Use `^` and `$` to anchor. P0 uses the [JavaScript regex dialect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions). |

## What's next

Now that you have access policies configured, you can:

* Set up [pre-approvals](/access-management/just-in-time-access/approving-access/pre-approving-access) for standing access during projects or maintenance windows.
* Add [resource filters](/access-management/just-in-time-access/access-policies) for fine-grained control over which resources are requestable per service.
* Connect a [SIEM integration](/integrations/siem-integrations) to send access request audit logs to Datadog or Splunk.
* Review the full [access policies reference](/access-management/just-in-time-access/access-policies) for all available requestor, resource, and approval types.


# Access inventory

Browse and query your entire IAM configuration. Combine data from identity providers, IAM policies, access logs, and P0's IAM Privilege Catalog.

The inventory page lets you browse and query your entire IAM configuration, combining data from your identity provider, IAM policies, your access logs, and P0's [IAM Privilege Catalog](https://catalog.p0.dev).

<figure><img src="/files/YZ8d0Xt93rPI8TvMbrYq" alt="Access inventory graph visualization showing identities, entitlements, and resources connected by lateral movement paths" width="563"><figcaption></figcaption></figure>

### Search interface

When you land on the **Inventory** page, you'll be presented with a search interface. At the top of this view you'll see a query control, and, below, all items that match your query. By default results are returned in a asset table. Everything in your IAM configuration matches an empty search, so you'll see everything listed at first.

#### Asset list

To search for something specific, type any text included in that IAM datum into the "where" bar:

<figure><img src="/files/aMZy4hPz53kDXquFyZjU" alt="Inventory search results table showing entitlements for bucket resources with columns for Principal, Privilege set, Resource, and Risks" width="563"><figcaption></figcaption></figure>

If you're new to querying, [Query Language Basics](/inventory/query-search/query-language-basics) walks you through the core search terms and operators from scratch. For detailed information on how to query your data, see [Query search](/inventory/query-search). You can also have P0 help you construct queries by hovering over items:

<figure><img src="/files/A60v3E2uovGWioDR6TBE" alt="Inventory item hover menu showing options to show or hide identities of this type for query refinement" width="563"><figcaption></figcaption></figure>

Clicking "show" or "hide" will update your query to show or hide the selected items.

You can display credentials, entitlements, identities, or resources by selecting these in the "show" selection:

<figure><img src="/files/EnKwErLzMDeqd5Y0bDGb" alt="Inventory Show dropdown menu with Credentials, Entitlements, Identities, and Resources options for filtering results" width="563"><figcaption></figcaption></figure>

For each item, you can see detailed information by selecting "view," which will take you to the relevant [Result details](/inventory/result-details) page.

#### Graph visualization

Results can also be viewed as a graph visualization. You will see all items that can reach your search terms, as well as the access paths that connect them.

<figure><img src="/files/JHULnwQh64nTDKP1MdvJ" alt="Access inventory graph visualization showing identities connected to GCP role bindings, Cloud Storage service, and a storage bucket" width="563"><figcaption></figcaption></figure>

You can get more information on any node in your access graph by clicking on it, which will open a view of all that graph node's properties.

<figure><img src="/files/plNu367bLSyKGxbVqzfS" alt="Graph node detail panel showing GCP Role Binding properties including principal, role, parent project, and cross-resource access status" width="563"><figcaption></figcaption></figure>

### Creating custom monitors

You create custom monitors from the Inventory page. Results from these monitors will appear in [Monitor Results](/posture/monitor-results) on every scan of your environment. To create a custom monitor:

* Select a "show" option and enter a "where" query
* After ensuring that the displayed results match your expectations, click **Save Search**
* Enable the "Create a monitor for this search?" toggle, then follow the prompts to add a title, description, and severity for your monitor

<figure><img src="/files/PXVZWdTrq9RNo5bsF7z0" alt="Create New Monitor dialog with query, label, description, and priority fields for a custom BigQuery grants monitor" width="375"><figcaption></figcaption></figure>


# Result details

Clicking on a query result will open a drawer with that result's details. Details looks like this:

<figure><img src="/files/w9o2ejsicLXhltU43sIu" alt="" width="563"><figcaption></figcaption></figure>

### Query path

At the top of the details you see a graph visualization of why this result matches your query. For instance, if you select an identity with `risk:exfiltration` as your search, you see the entitlements and privileges that lead to data-exfiltration risks. To understand how queries like `risk:exfiltration` work, see [Query Language Basics](/inventory/query-search/query-language-basics).

### Result information

The top of the page shows detailed information for the result. The information displayed depends on the result type.

<table data-header-hidden><thead><tr><th width="123.33333333333331">Result type</th><th width="173">Field</th><th>Description</th></tr></thead><tbody><tr><td><strong>Credential</strong></td><td>Identity</td><td>The identity accessed via this credential</td></tr><tr><td></td><td>Last used</td><td>The most recent date that this credential was used</td></tr><tr><td></td><td>Last rotated</td><td>When this credential was created</td></tr><tr><td></td><td>Entitlements</td><td>All entitlements that can be used for access via this credential</td></tr><tr><td></td><td>Risks</td><td>Access risks reachable from this credentiall, and the privileges that expose those risks</td></tr><tr><td><strong>Entitlement</strong></td><td>Principal</td><td>The principal identity that is assigned this entitlement</td></tr><tr><td></td><td>Role | Policy</td><td>The name of the granted role (for non-AWS systems) or policy (for AWS)</td></tr><tr><td></td><td>Condition</td><td>(GCP role bindings only) this role binding's access condition</td></tr><tr><td></td><td>Resource</td><td>The resource(s) to which this entitlement grants direct access</td></tr><tr><td></td><td>Risks</td><td>Reachable IAM risks for this entitlement, broken down by whether the privilege(s) that yield the risk are used or not within the previous 90 days</td></tr><tr><td></td><td>Accessible by</td><td>The identities that can use this entitlement, including via federation, group membership, or lateral movement</td></tr><tr><td><strong>Identity</strong></td><td>Parent</td><td>The resource in which the identity is defined (e.g. AWS account, Azure subscription, GCP project, etc.)</td></tr><tr><td></td><td>Last Used</td><td>The last time this identity authenticated with its identity provider</td></tr><tr><td></td><td>Accessible by</td><td>(Federation identities only) the identities that can gain access to your system via this federation identity</td></tr><tr><td></td><td>Members</td><td>(Groups only) this group's direct and indirect members</td></tr><tr><td></td><td>MFA</td><td>(Users only) whether two-factor authentication is required for this user</td></tr><tr><td></td><td>Entitlements</td><td>A link to view all of the identity's entitlements</td></tr><tr><td></td><td>Risks</td><td>Access risks reachable from this identity, and the privileges that expose those risks</td></tr><tr><td><strong>Resource</strong></td><td>Parent</td><td>This resource's parent resource in the system's resource hierarchy (e.g. a database table's parent resource will be its enclosing database schema); top-level resources have the service as their parent</td></tr><tr><td></td><td>Children</td><td>A list of all this resource's child resources (e.g. a database schema will have all its tables, indices, views, etc. as children)</td></tr><tr><td></td><td>Accessible by</td><td>All identities that have direct access to this resource</td></tr></tbody></table>


# Inventory export API

List your P0 environments and export their access inventory as JSON. Pull credentials, identities, grants, and resources programmatically for custom integrations.

The Inventory export API lets you pull your cloud [access inventory](/inventory/access-inventory) programmatically instead of using the in-app **Export as JSON** button. Use it to feed inventory into custom integrations, data warehouses, or compliance workflows.

The API has two read-only endpoints:

* **List environments** returns every environment in your organization and its assessment scopes.
* **Export inventory** returns the latest completed assessment for an environment as a single JSON document, grouped by entity type.

Environments are backed by IAM assessments, so an environment's `id` is also its assessment ID. Use the `id` from the list endpoint as the `assessmentId` path parameter when you export.

Authenticate each request with a [bearer token](/getting-started/authenticating-with-the-p0-api) in the `Authorization` header:

```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.p0.app/o/{orgId}/assessment
```

Both endpoints require the `assessment.read` permission. A token from an identity with the **Owner** role includes this permission. To get a token, see [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api).

{% file src="/files/1hQJXIe7jnIweH7Ypuof" %}

## List environments

> Returns every environment in the organization, along with the assessment scopes each environment covers.

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID"}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"schemas":{"EnvironmentList":{"type":"object","properties":{"environments":{"type":"array","items":{"$ref":"#/components/schemas/Environment"}}}},"Environment":{"type":"object","description":"An environment in the organization. Environments are backed by IAM assessments.","properties":{"id":{"type":"string","description":"The environment ID. Pass this as the `assessmentId` path parameter to export the environment's inventory."},"name":{"type":"string","description":"The display name of the environment."},"targets":{"type":"array","description":"The assessment scopes the environment covers.","items":{"$ref":"#/components/schemas/AssessmentScope"}}}},"AssessmentScope":{"type":"object","description":"A single scope covered by an environment's assessment.","properties":{"id":{"type":"string","description":"The identifier of the scoped target, such as a project, account, or subscription ID."},"integration":{"type":"string","description":"The cloud integration the scope belongs to."},"type":{"type":"string","description":"The kind of target the scope covers."}}}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated."}}},"paths":{"/assessment":{"get":{"summary":"List environments","description":"Returns every environment in the organization, along with the assessment scopes each environment covers.","responses":{"200":{"description":"The environments in the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentList"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"}}}}}}
```

## Export inventory

> Exports the inventory of an environment's latest completed assessment as a single JSON document, grouped by entity type. The response is streamed, so it scales to large environments. The \`X-P0-Assessment-Job-Id\` response header identifies the assessment job the inventory came from.

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID"}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"parameters":{"assessmentId":{"name":"assessmentId","in":"path","required":true,"description":"The environment ID returned by the list endpoint. An environment ID is also its assessment ID.","schema":{"type":"string"}}},"schemas":{"InventoryExport":{"type":"object","description":"The environment's inventory, grouped by entity type. Each entity appears once per group; entities that span multiple assessment scopes are de-duplicated by key.","properties":{"credentials":{"type":"array","description":"Keys, tokens, and other credentials discovered in the environment.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"identities":{"type":"array","description":"Users, service accounts, and groups.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"grants":{"type":"array","description":"Access grants that bind a principal to a privilege set on one or more resources. These appear as entitlements in the inventory UI.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"resources":{"type":"array","description":"Cloud resources such as buckets, databases, and compute instances.","items":{"$ref":"#/components/schemas/InventoryEntity"}}}},"InventoryEntity":{"type":"object","description":"A single inventory entity. Every entity carries a stable `key` alongside its own fields. The remaining fields vary by entity type and by cloud provider.","required":["key"],"properties":{"key":{"type":"string","description":"The entity's stable identifier in the inventory graph."}},"additionalProperties":true}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated."},"NotFoundError":{"description":"No environment matches the given ID."},"NoCompletedAssessmentError":{"description":"The environment has no completed assessment to export."},"TooManyRequestsError":{"description":"Too many inventory exports are in progress. Back off and retry shortly."}}},"paths":{"/assessment/{assessmentId}/export":{"get":{"summary":"Export inventory","description":"Exports the inventory of an environment's latest completed assessment as a single JSON document, grouped by entity type. The response is streamed, so it scales to large environments. The `X-P0-Assessment-Job-Id` response header identifies the assessment job the inventory came from.","parameters":[{"$ref":"#/components/parameters/assessmentId"}],"responses":{"200":{"description":"The environment's inventory, grouped by entity type.","headers":{"X-P0-Assessment-Job-Id":{"description":"The ID of the assessment job the inventory came from.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryExport"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"404":{"$ref":"#/components/responses/NotFoundError"},"422":{"$ref":"#/components/responses/NoCompletedAssessmentError"},"429":{"$ref":"#/components/responses/TooManyRequestsError"}}}}}}
```

## The AssessmentScope object

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"components":{"schemas":{"AssessmentScope":{"type":"object","description":"A single scope covered by an environment's assessment.","properties":{"id":{"type":"string","description":"The identifier of the scoped target, such as a project, account, or subscription ID."},"integration":{"type":"string","description":"The cloud integration the scope belongs to."},"type":{"type":"string","description":"The kind of target the scope covers."}}}}}}
```

## The Environment object

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"components":{"schemas":{"Environment":{"type":"object","description":"An environment in the organization. Environments are backed by IAM assessments.","properties":{"id":{"type":"string","description":"The environment ID. Pass this as the `assessmentId` path parameter to export the environment's inventory."},"name":{"type":"string","description":"The display name of the environment."},"targets":{"type":"array","description":"The assessment scopes the environment covers.","items":{"$ref":"#/components/schemas/AssessmentScope"}}}},"AssessmentScope":{"type":"object","description":"A single scope covered by an environment's assessment.","properties":{"id":{"type":"string","description":"The identifier of the scoped target, such as a project, account, or subscription ID."},"integration":{"type":"string","description":"The cloud integration the scope belongs to."},"type":{"type":"string","description":"The kind of target the scope covers."}}}}}}
```

## The EnvironmentList object

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"components":{"schemas":{"EnvironmentList":{"type":"object","properties":{"environments":{"type":"array","items":{"$ref":"#/components/schemas/Environment"}}}},"Environment":{"type":"object","description":"An environment in the organization. Environments are backed by IAM assessments.","properties":{"id":{"type":"string","description":"The environment ID. Pass this as the `assessmentId` path parameter to export the environment's inventory."},"name":{"type":"string","description":"The display name of the environment."},"targets":{"type":"array","description":"The assessment scopes the environment covers.","items":{"$ref":"#/components/schemas/AssessmentScope"}}}},"AssessmentScope":{"type":"object","description":"A single scope covered by an environment's assessment.","properties":{"id":{"type":"string","description":"The identifier of the scoped target, such as a project, account, or subscription ID."},"integration":{"type":"string","description":"The cloud integration the scope belongs to."},"type":{"type":"string","description":"The kind of target the scope covers."}}}}}}
```

## The InventoryEntity object

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"components":{"schemas":{"InventoryEntity":{"type":"object","description":"A single inventory entity. Every entity carries a stable `key` alongside its own fields. The remaining fields vary by entity type and by cloud provider.","required":["key"],"properties":{"key":{"type":"string","description":"The entity's stable identifier in the inventory graph."}},"additionalProperties":true}}}}
```

## The InventoryExport object

```json
{"openapi":"3.0.4","info":{"title":"P0 Inventory Export API","version":"1.0.0"},"components":{"schemas":{"InventoryExport":{"type":"object","description":"The environment's inventory, grouped by entity type. Each entity appears once per group; entities that span multiple assessment scopes are de-duplicated by key.","properties":{"credentials":{"type":"array","description":"Keys, tokens, and other credentials discovered in the environment.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"identities":{"type":"array","description":"Users, service accounts, and groups.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"grants":{"type":"array","description":"Access grants that bind a principal to a privilege set on one or more resources. These appear as entitlements in the inventory UI.","items":{"$ref":"#/components/schemas/InventoryEntity"}},"resources":{"type":"array","description":"Cloud resources such as buckets, databases, and compute instances.","items":{"$ref":"#/components/schemas/InventoryEntity"}}}},"InventoryEntity":{"type":"object","description":"A single inventory entity. Every entity carries a stable `key` alongside its own fields. The remaining fields vary by entity type and by cloud provider.","required":["key"],"properties":{"key":{"type":"string","description":"The entity's stable identifier in the inventory graph."}},"additionalProperties":true}}}}
```

## Response notes

* **Export** groups entities into `credentials`, `identities`, `grants`, and `resources`. Grants appear as **entitlements** in the inventory UI.
* Every entity carries a `key`, its stable identifier in the inventory graph, alongside the entity's own fields. Fields vary by entity type and by cloud provider.
* The export unions every scope of the assessment and de-duplicates entities by `key`, so each entity appears once even when it spans multiple scopes.
* The export contains the raw inventory entities. Each credential, identity, and grant carries a `risk` array with the same risk roll-up the inventory UI shows; resources don't include a `risk` field. Connected-node references that the UI layers on top aren't included.
* The `X-P0-Assessment-Job-Id` response header identifies the assessment job the inventory came from. Use it to correlate an export with a specific assessment run.

### Example export request

```bash
curl -sD - "https://api.p0.app/o/{orgId}/assessment/{assessmentId}/export" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -o inventory.json
```

The response is streamed, so it scales to large environments. The `-o` flag writes the body to `inventory.json`, and `-D -` prints the response headers so you can read the job ID.

## Related

* [P0 API overview](/getting-started/p0-api-overview): a map of every P0 API and the authentication they share.
* [Access Inventory](/inventory/access-inventory): browse and query your inventory in the P0 dashboard.
* [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api): get a token to authenticate these requests.
* [Management API](/p0-management/management-api): other programmatic P0 APIs.


# Query search

P0's environment query searches let you find specific IAM data across your cloud inventory.

Query searches are controlled using two parts:

<figure><img src="/files/mKRu0Tm9XL7pZcO0OhNd" alt="Inventory query search bar with Show dropdown set to Grants and a Where field for entering search terms" width="563"><figcaption></figcaption></figure>

* **show** - controls which kind of data are displayed
* **where** - controls which data to show

### Show control

Currently, you can choose to "show" credentials (access keys or short-term authentication), identities (users, groups, machine identities, and so forth), entitlements (in Google Cloud, a role binding; in AWS, a policy attachment), or resources (projects, accounts, services, and individual resources such as storage buckets).

### Where control

The "where" control is a free-form search box. You can enter any term here, and P0 will find the principals or grants that relate to your search term.

*Example*:

Searching for a permission (in this case `compute.instances.create` in a Google Cloud assessment) will show you all grants that provide that permission:

<figure><img src="/files/ZZUtoUBw9HdiIhqTLgVK" alt="Query results for compute.instances.create showing grants with principals, roles, resources, risk counts, and permission usage" width="563"><figcaption></figcaption></figure>

To see *why* a search result matches your query, you can click on that result's "view" link. The details page will show an "Explanation" section at the bottom, describing how that result satisfies your query:

<figure><img src="/files/Ms91LSv5o6PkESNVbpEr" alt="" width="563"><figcaption></figcaption></figure>

### Learn the query language

A free-form term gets you started, but you can be far more specific by writing query expressions. Two pages cover the full query language:

* [Query Language Basics](/inventory/query-search/query-language-basics): a beginner-friendly walkthrough of the core search terms (`identity`, `credential`, `entitlement`, `risk`) and the operators that connect them. Start here if you're new to querying.
* [Search Reference](/inventory/query-search/search-reference): the complete reference for every operator, search type, and attribute, with Cypher equivalents. Use it to look up specifics.

### Query examples

One of the best resources for constructing queries is to view the search queries for P0's built-in assessment monitors.

For instance, here's the query for detecting unused service account keys:

`show = credential`

```
credential=enabledKey:"true"
credential=last40:"unused"
identity=type:"service-account"
identity=status:"active"
```

This returns all service-account keys that have not been used in the last 40 days. For the syntax behind each term, see the [Search Reference](/inventory/query-search/search-reference).

### Query links

You can also construct queries using tooltips in the displayed data. To do this, hover over an item you want to either include or exclude from your search:

<figure><img src="/files/iaSvAiBwTFlj7vV907o6" alt="" width="563"><figcaption></figcaption></figure>

Select the corresponding "show" or "hide" link to either include or exclude that item in your search results.


# Query language basics

A beginner's guide to querying P0's Access Inventory. Learn the core search terms (identity, credential, entitlement, risk), the colon, arrow, and reverse-arrow operators, the type argument, and how t

This guide introduces the query language you use to search the Access Inventory. It assumes no prior experience. By the end, you can read and write queries like `identity:->identity:->entitlement:->risk:` and understand exactly what they return.

If you want the complete reference (every operator, search term, and attribute), see the [Search Reference](/inventory/query-search/search-reference). For an overview of the search interface itself, see [Query Search](/inventory/query-search).

## Your inventory is a graph

P0 models your cloud IAM configuration as a *graph*: a set of items (called **nodes**) connected by relationships (called **edges**).

Each node is one piece of your access setup, such as a user, an access key, or a permission grant. Each edge records how two nodes relate, such as "this identity holds this entitlement."

You query the inventory by describing a pattern of nodes and the connections between them. P0 finds every part of your graph that matches the pattern and shows you the results.

{% hint style="info" %}
You don't need to know graph theory to write queries. Think of each query as a sentence that describes the access you're looking for, reading from left to right.
{% endhint %}

## The core search terms

The most common nodes you search for are the four that follow. Type one of these words to limit your search to that kind of node.

| Search term   | What it represents                                                            | Example                                          |
| ------------- | ----------------------------------------------------------------------------- | ------------------------------------------------ |
| `identity`    | A user, group, machine identity, or role that can authenticate.               | An employee, a service account, an AWS IAM role. |
| `credential`  | A single way to authenticate, such as an access key or a federated SSO login. | An AWS access key, a short-lived token.          |
| `entitlement` | A grant that gives an identity privileges over one or more resources.         | An AWS policy attachment, a GCP role binding.    |
| `risk`        | A security risk that a privilege carries.                                     | A privilege that allows data exfiltration.       |

These four are the terms you use most often, but they aren't the only ones. The inventory also models `resource`, `privilege`, `usage`, and more. For the complete list, see [Search types](/inventory/query-search/search-reference#search-types).

## The colon operator: `:`

The colon turns a word into a **node-type filter**. On its own, a term followed by a colon matches every node of that type:

```
identity:
```

This query matches every identity in your inventory.

To narrow the search, add a keyword after the colon. P0 then matches nodes of that type whose name contains the keyword:

```
identity:alice
```

This query matches identities whose name contains `alice`.

Read the colon as "that is a." So `identity:alice` reads as "a node *that is an* identity, named `alice`."

{% hint style="info" %}
A colon with nothing after it (`risk:`) is a presence search: it finds every node of that type. Use it to answer questions like "which entitlements carry any risk at all?" with `entitlement:->risk:`.
{% endhint %}

## The type argument

Many node types come in several varieties. An identity might be a user, a group, or a machine role. A credential might be a static key or a short-lived token. To filter by variety, use the `type` argument.

The `type` argument is an *attribute* of a node, so you write it as a second colon-separated value:

```
identity:type:user
```

This reads as "an identity whose `type` is `user`." The pattern is always:

```
<search term>:type:<value>
```

Here are some common examples:

| Query                           | Matches                                          |
| ------------------------------- | ------------------------------------------------ |
| `identity:type:user`            | Human user identities.                           |
| `identity:type:aws-iam-role`    | AWS IAM roles.                                   |
| `identity:type:service-account` | Machine identities (service accounts).           |
| `credential:type:key`           | Static secret credentials, such as access keys.  |
| `credential:type:short-lived`   | Ephemeral credentials, such as temporary tokens. |
| `usage:type:unused`             | Privileges that have gone unused.                |

{% hint style="info" %}
`type` is one of several attributes you can filter on. Each search term supports its own attributes, such as `identity:status` or `credential:last90`. See [Search attributes](/inventory/query-search/search-reference#search-attributes) for the full set and their allowed values.
{% endhint %}

## The inverted operator: `!`

Put a `!` immediately before a keyword to match nodes whose value *does not* contain it. This is the **inverted** (or "NOT") operator.

```
identity:status:!disabled
```

This reads as "an identity whose `status` is *not* `disabled`." It returns every identity that is enabled or whose status is unknown.

The `!` goes after the type and attribute, directly in front of the keyword it inverts. The pattern is:

```
<search term>:<attribute>:!<keyword>
```

{% hint style="info" %}
On its own, an inverted keyword does little, because each identity and grant connects to many values and some won't match anyway. Inverted matches work best combined with a type or attribute, as in `credential:type:!key` (credentials that aren't static keys).
{% endhint %}

The inverted operator (`!`) flips a single keyword. A related operator, the **exclusion** operator (`^`), removes whole results that match a term. For both, see [Inverted matches](/inventory/query-search/search-reference#inverted-matches) and [Exclusion matches](/inventory/query-search/search-reference#exclusion-matches) in the reference.

## Matching with a regular expression: `/.../`

A plain keyword matches any value that *contains* it. When you need more control (a value that *starts* with something, or matches one of several patterns), wrap a **regular expression** in forward slashes.

```
identity:/^svc-/
```

This reads as "an identity whose name matches the pattern `^svc-`," which finds identities whose name *starts with* `svc-`. The `^` anchors the match to the beginning of the value.

You can use the full standard regular expression syntax, including anchors (`^`, `$`), character classes, and alternation:

```
identity:/prod|staging/
```

This matches identities whose name contains either `prod` or `staging`.

Regex works wherever a keyword does, including after an attribute (`identity:status:/^en/`) and combined with the inverted operator (`identity:type:!/role/`).

{% hint style="info" %}
Regex matches are **case sensitive**, unlike plain keyword matches. To match either case, build it into the pattern, for example, `/[Pp]rod/` rather than `/prod/i`.
{% endhint %}

For escaping rules, limitations, and the equivalent Cypher, see [Regex matches](/inventory/query-search/search-reference#regex-matches) in the reference.

## The path operators: `->` and `<-`

A single term finds nodes in isolation. To describe how nodes *connect*, join terms with an arrow. We call this a **path**.

The arrow points along the direction of the graph's edges:

| Operator | Name        | Meaning                                                                  |
| -------- | ----------- | ------------------------------------------------------------------------ |
| `->`     | Child path  | Follow the connection forward, from the left term toward the right term. |
| `<-`     | Parent path | Follow the connection backward, from the left term toward its parent.    |

For example, to find identities that hold an entitlement, follow the connection forward:

```
identity:->entitlement:
```

This reads as "an identity that connects forward to an entitlement." The same relationship viewed from the other side uses the reverse arrow:

```
entitlement:<-identity:
```

Both describe the same connection. You pick the direction that matches how you want to read the query and which nodes you want to start from.

You can chain as many terms as you need, adding one arrow between each pair:

```
identity:->entitlement:->risk:
```

This reads as "an identity that holds an entitlement that carries a risk."

Switch to the graph view to see the matching paths. Each row traces an identity (left) through the entitlement it holds to the risk that entitlement carries (right), with the nodes that match your query highlighted in yellow:

<figure><img src="/files/QUMwzINaCyiXCEXajAbD" alt="Graph view of the query identity arrow entitlement arrow risk, showing identities such as Okta users and AWS IAM roles connected through AWS policy assignments and privileges to critical data-exfiltration risks, with matching nodes highlighted in yellow"><figcaption><p>The graph view for <code>identity:->entitlement:->risk:</code></p></figcaption></figure>

{% hint style="warning" %}
A single path must use one direction throughout. Mixed paths like `a:->b:<-c:` are not supported. Use all `->` or all `<-` within one path.
{% endhint %}

{% hint style="info" %}
Arrows follow the real connections in the graph, so not every combination is valid. For example, `identity:->entitlement:->risk:` works, but `resource:->risk:` doesn't, because resources don't connect directly to risks. The [IAM graph diagram](/inventory/query-search/search-reference#iam-graph) shows which connections exist.
{% endhint %}

## Reading a worked example

Now you can read the example query from the top of this guide:

```
identity:->identity:->entitlement:->risk:
```

Walk through it one hop at a time, left to right:

1. `identity:` - Start from an identity.
2. `->identity:` - Follow the connection forward to another identity that the first one is linked to. For example, a user and a group or role that user can act through.
3. `->entitlement:` - Follow the connection forward to an entitlement that this second ("right") identity holds.
4. `->risk:` - Follow the connection forward to a risk that the entitlement carries.

Put together, the query finds **identities that are linked to other identities, the entitlements those linked identities hold, and the risks those entitlements carry**. It's a way to surface indirect risk: access that one identity gains through another, and the danger that access brings.

The graph view makes each hop visible. Here, Okta users reach groups, which hold AWS policy assignments, whose privileges carry critical exfiltration risks, each matching node highlighted in yellow:

<figure><img src="/files/0i56CvmCJV91MkaMpyCC" alt="Graph view of the query identity arrow identity arrow entitlement arrow risk, showing Okta users connected to Okta groups, then to AWS roles and policy assignments, then to privileges that carry critical data-exfiltration and cryptographic-exfiltration risks, with matching nodes highlighted in yellow"><figcaption><p>The graph view for <code>identity:->identity:->entitlement:->risk:</code></p></figcaption></figure>

## More example queries

| Query                                                | What it finds                                     |
| ---------------------------------------------------- | ------------------------------------------------- |
| `identity:`                                          | Every identity in your inventory.                 |
| `identity:type:service-account`                      | Every service account.                            |
| `credential:type:key`                                | Every static access key.                          |
| `identity:->entitlement:`                            | Identities and the entitlements they hold.        |
| `entitlement:->risk:`                                | Entitlements that carry any risk.                 |
| `entitlement:->risk:CRITICAL`                        | Entitlements that carry a critical risk.          |
| `identity:type:service-account->entitlement:->risk:` | Service accounts whose entitlements carry a risk. |

## Build queries visually with the Query builder

If you'd rather not type the syntax by hand, use the **Query builder**. Click the **+** button next to the **where** box, above the table or graph, to open it.

The Query builder is a form that assembles one search term and inserts it into the **where** box for you. In it, you:

1. Choose a connection: **are** (match the displayed node itself) or **can reach** (match a node it connects to).
2. Select a search type, such as **identities** or **entitlements**, and an attribute to filter on.
3. Pick **containing** (substring) or **equal to** (exact), then enter a keyword.
4. Optionally turn on **Invert match?** to apply the `!` operator, **Remove match?** to apply the exclusion operator (`^`), or **Only first node?** to match just the first node in a chain.

A live preview shows the term as you build it. Click **Add term** to drop it into the **where** box, where you can edit it or chain it with other terms.

{% hint style="info" %}
The Query builder produces ordinary query text, the same syntax this guide describes. It's a quick way to discover operators and exact attribute values; once you know the syntax, typing is often faster.
{% endhint %}

## Next steps

* Try a query: open the **Inventory** page, choose what to display in the **show** control, and type a query in the **where** box. See [Access Inventory](/inventory/access-inventory) for a tour of the search interface.
* Look up the rest of the operators (exact matches, inverted matches, exclusions, and more) with every search term and attribute in the [Search Reference](/inventory/query-search/search-reference).


# Search reference

This page is the complete reference for inventory queries: every query operator, search term, attribute, and allowed value. If you're new to the query language, read [Query Language Basics](/inventory/query-search/query-language-basics) first for a guided introduction. This page is for looking up specifics.

## Query operators

Query expressions let you be more specific in your searches. Here's a summary of every operator:

<table><thead><tr><th width="272.3333333333333">Expression</th><th width="218">See</th><th></th></tr></thead><tbody><tr><td>Key terms</td><td></td><td></td></tr><tr><td><code>keyword</code></td><td><a data-mention href="#contains-matches">#contains-matches</a></td><td>Match data substring</td></tr><tr><td><code>"keyword"</code></td><td><a data-mention href="#exact-matches">#exact-matches</a></td><td>Match data exactly</td></tr><tr><td><code>/keyword/</code></td><td><a data-mention href="#regex-matches">#regex-matches</a></td><td>Match data by regular expression</td></tr><tr><td><code>!keyword</code></td><td><a data-mention href="#inverted-matches">#inverted-matches</a></td><td>Invert keyword matches</td></tr><tr><td>Node terms</td><td></td><td></td></tr><tr><td><code>type:keyterm</code></td><td><a data-mention href="#type-matches">#type-matches</a></td><td>Match data types</td></tr><tr><td><code>type=keyterm</code></td><td><a data-mention href="#first-matches">#first-matches</a></td><td>Match first datum only</td></tr><tr><td><code>type:attribute:keyterm</code> or <code>type:{attribute1:keyterm1 attribute2:keyterm2 ...}</code></td><td><a data-mention href="#attribute-matches">#attribute-matches</a></td><td>Match data attributes</td></tr><tr><td>Path terms</td><td></td><td></td></tr><tr><td><code>keyterm->childpath</code></td><td>Child paths</td><td>Match a directed path in child direction</td></tr><tr><td><code>keyterm&#x3C;-parentpath</code></td><td>Parent paths</td><td>Match a directed path in parent direction</td></tr><tr><td><code>keyterm&#x3C;>eitherpath</code></td><td>Either paths</td><td>Match a directed path in either parent or child direction</td></tr><tr><td><code>childpath</code> | <code>parentpath</code> | <code>eitherpath</code></td><td><a data-mention href="#via-matches">#via-matches</a></td><td>Place multiple conditions on a data connection</td></tr><tr><td>Exclusion term</td><td></td><td></td></tr><tr><td><code>^pathterm</code></td><td><a data-mention href="#exclusion-matches">#exclusion-matches</a></td><td>Remove matches from results</td></tr><tr><td>Compound expression</td><td></td><td></td></tr><tr><td><code>expression term</code></td><td><a data-mention href="#multiple-matches">#multiple-matches</a></td><td>Require multiple data connections</td></tr></tbody></table>

*Cypher equivalents*

Each detailed explanation below also describes the equivalent statement in the Cypher query language (replace `{show}` with the corresponding label of the "show" control).

When displaying data as a table, the displayed data are the nodes matching the "show" type (represented by `s` in the equivalent queries); when displaying data as a graph visualization, the displayed data are the returned paths (represented by `p` in the equivalent queries).

### "Contains" matches

In its simplest usage, P0 matches IAM data if it includes your search keyword. For example, searching for `compute` shows you grants that allow access to any compute resource, give any privileges with "compute" in their name, grant a permission set with "compute" in its name, or grant access to compute service principals.

"Contains" matches are case insensitive; use [#exact-matches](#exact-matches "mention") to find data with exact casing.

*Cypher equivalent*:

```cypher
CALL () {
    MATCH p=(s:{show})-[*]->(r)
    RETURN p, r, s
    UNION
    MATCH p=(r)-[*]->(s:{show})
    RETURN p, r, s
}
WITH p, r, s
WHERE ANY(k IN KEYS(r) WHERE r[k] =~ '(?i).*{keyword}.*')
RETURN p, s
```

### Exact matches

To match data exactly, enclose your keyword in double quotes. For example, searching for `compute.instances.get` in GCP matches extra permissions, such as `compute.instances.getEffectiveFirewalls`. Searching for `"compute.instances.get"` limits results to only those grants that have that specific permission.

{% hint style="info" %}
When using exact matches with type and attribute matches, the quotes surround the keyword only: `type:attribute:"keyword"`.

You can also use quotes to search for data where the data includes a colon without triggering a type or attribute match. For example, `risk:"exfiltration:data"` returns all results that yield a data exfiltration risk.
{% endhint %}

*Cypher equivalent*:

```cypher
CALL () {
    // per "contains" CALL statement
}
WITH p, r, s
WHERE ANY(k IN KEYS(r) WHERE r[k] = '{keyword}')
RETURN p, s
```

### Regex matches

To match data against a regular expression, enclose your pattern in forward slashes. For example, `/^arn:aws:iam::\d+:role\//` matches values that start with an AWS IAM role ARN prefix, and `/prod|staging/` matches values containing either `prod` or `staging`.

P0 evaluates the pattern with the standard JavaScript regular expression engine, so anchors (`^`, `$`), character classes, quantifiers, and alternation (`|`) all work as you'd expect.

Regex matches combine with type and attribute matches the same way other keywords do. The slashes surround the pattern only: `type:/pattern/` or `type:attribute:/pattern/`. To invert a regex match, place the `!` before the pattern: `type:attribute:!/pattern/`. See [#inverted-matches](#inverted-matches "mention").

{% hint style="warning" %}
Regex matches are **case sensitive**, unlike [#contains-matches](#contains-matches "mention"), which are case insensitive. JavaScript inline flags such as `/pattern/i` aren't supported, so express case insensitivity inside the pattern, for example, `/[Pp]rod/`.
{% endhint %}

{% hint style="info" %}
To match a literal forward slash inside the pattern, escape it as `\/`, for example, `/role\/Admin/`. An empty pattern (`//`), an unclosed pattern (`/abc`), or a syntactically invalid pattern is rejected. P0 also rejects patterns that are vulnerable to [ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) (catastrophic backtracking), such as `/(a+)+$/`; simplify the pattern if you hit this.
{% endhint %}

*Cypher equivalent*:

```cypher
CALL () {
    // per "contains" CALL statement
}
WITH p, r, s
WHERE ANY(k IN KEYS(r) WHERE r[k] =~ '{keyword}')
RETURN p, s
```

### Inverted matches

You can search for grants and principals that are connected to data that *don't* match a keyword by typing a `!` in front of your search keyword.

Note that using an inverted match on its own usually doesn't do much, as grants and principals connect to *many* data, and some of these data are likely to not match your keyword. Instead, inverted matches are usually best when combined with type or attribute matches, or with exclusion matches. For example, `identity:mfa:!enabled` shows you all users that have either disabled or unknown MFA status.

{% hint style="info" %}
When using inverted matches with type and attribute matches, the `!` comes *after* the type and attribute: `type:attribute:!keyword`.
{% endhint %}

*Cypher equivalent*:

```cypher
CALL () {
    // per "contains" CALL statement
}
WITH p, r, s
WHERE ANY(k IN KEYS(r) WHERE NOT ({keyterm condition}))
RETURN p, s
```

### Exclusion matches

You can search for items that aren't connected to data that match a term by typing a `^` in front of your entire search term.

For example, searching for `^usage:type:"unused"` shows all grants that have only used or unknown permission usage. Searching for `^usage:type:!"unused"` shows all grants where *all* permissions are unused.

{% hint style="info" %}
When using exclusion matches, the `^` comes *before* the type and attribute: `^type:attribute:keyterm`.
{% endhint %}

*Cypher equivalent*:

```cypher
MATCH (s1:{show})
WHERE (COUNT {
    // pathterm query w/out "RETURN"
    AND s=s1
}) = 0
RETURN s1
```

### Type matches

Limit your search to a specific type of IAM data using a type prefix. For example, adding `risk:CRITICAL` in front of your search limits your search to only showing grants that allow a critical IAM risk (as defined by the [IAM Privilege Catalog](https://catalog.p0.dev/scores)). See [#search-types](#search-types "mention") for a list of all possible types.

{% hint style="info" %}
You can use a type match without a keyword to search for the presence of data.

For example, `condition:` shows you all conditional grants.
{% endhint %}

*Cypher equivalent*:

<pre class="language-cypher"><code class="lang-cypher"><strong>CALL () {
</strong>    // per "contains" CALL statement
}
WITH p, r, s
WHERE r:{type} AND ANY(k IN keys(r) WHERE r[k] =~ '(?i).*{keyword}.*')
RETURN p, s
</code></pre>

### "First" matches

Data may connect to a chain of items of the same type. For instance, a grant on a resource gives access to all that resource's children, and all those child resources' children, and so forth. Or, grants may target directory groups with nested group membership.

To restrict your search to only the first item in such a chain, use a first match. For example, `identity=alice@my.co` shows only the `alice@my.co` user, and not the groups to which that user belongs.

*Cypher equivalent*:

```cypher
CALL () {
    // per "contains" CALL statement
}
WITH p, r, s
WHERE NOT (p)-[*]->(:{type})-[*]->(r)
  AND NOT (r)-[*]->(:{type})-[*]->(p)
  AND ANY(k IN keys(r) WHERE r[k] =~ '(?i).*{keyword}.*')
RETURN p, s
```

### Attribute matches

Attribute expressions allow you to make even more specific searches. For example, searching for `credential:last90:unused` limits displayed principals to those with at least one credential that hasn't been used in the previous 90 days. See [#search-attributes](#search-attributes "mention") for a list of all possible attributes.

{% hint style="info" %}
You can combine first and attribute matches using the syntax `{type}={attribute}:{keyterm}`.
{% endhint %}

*Cypher equivalent*:

```cypher
CALL () {
    // per "contains" CALL statement
}
WITH p, r, s
WHERE r:{type} AND r.{attribute} =~ '(?i).*{keyword}.*'
RETURN p, s
```

### Multiple matches

Return items that connect to multiple data by separating search terms using whitespace.

For instance, `resource:one resource:two` shows you grants that give access to both resources "one" and "two".

*Cypher equivalent*:

```cypher
CALL () {
    // Result of first query
    RETURN p AS p1, s AS s1
}
WITH p1, s1
CALL () {
    // Result of second query
    RETURN p AS p2, s AS s2
}
WITH p2, s2
WHERE s1 = s2
RETURN s1, p1, p2
```

### Via matches

Your IAM data are modeled as a directed graph. You can require data to connect to your search results according to multiple search terms using a via match.

To use a via match, connect two or more terms using `->` (for child relationships), `<-` (for parent relationships), or `<>` (for either child or parent relationships).

For example, to find all entitlements that have unused permissions that create a data exfiltration risk, you can search for `usage:type:"unused"->risk:"exfiltration:data"`.

You can chain multiple terms together using more `->`. For example `usage:type:"unused"->privilege:s3->risk:"exfiltration:data"`.

{% hint style="warning" %}
Paths must consistently use child, parent, or "either" relationship syntax. Mixed paths like `a->b<-c` are not currently supported.
{% endhint %}

You can reference how data are connected in this graph using [#iam-graph](#iam-graph "mention").

{% hint style="info" %}
Exclusion matches combined with via matches return results where one or more of the via conditions don't match.
{% endhint %}

*Cypher equivalent for* `s<>r`:

```cypher
CALL () {
    MATCH p=(s:{show})-[*]->(q)-[*]->(r)
    RETURN p, q, r, s
    UNION
    MATCH p=(r)-[*]->(q)-[*]->(s:{show})
    RETURN p, q, r, s
}
WITH p, q, r, s
WHERE
    // constraints on q
    AND
    // constraints on r
RETURN p, s
```

## Search types

These are all the possible search types, and their meaning:

* **awsPolicy** - an AWS policy
* **condition** - a grant condition
* **consumer** - the entity that authenticates using a credential; this is typically an IP address
* **credential** - a single authentication credential; search matches the name of the credential (e.g. ID of an API key); will be "federated" if the principal authenticates using SSO
* **entitlement** - a set of privileges to use one or more resources, granted to an identity (an entitlement may be an AWS policy assignment, GCP role binding, or Azure / Kubernetes / Okta / Workspace role assignment)
* **identity** - an IAM identity; search matches the name of the identity (e.g. email; group name, AWS role name, etc.)
* **lateral** - represents potential machine-identity impersonation; each path is represented by two graph nodes, one attached to the grant that allows impersonation, the other attached to the principal that can be impersonated (see `lateral:flow` below)
* **permissionSet** - an AWS permission set
* **privilege** - a grantable privilege (this can be an AWS action, or Azure, Google Cloud, Okta, or Workspace permission)
* **resource** - an IAM resource
* **risk** - a security risk associated with holding a privilege; possible risks are listed in the [IAM Privilege Catalog](https://catalog.p0.dev/risks); you can also search for risk severity scores (e.g. `CRITICAL`)
* **role** - an Azure, Google Cloud, Kubernetes, Microsoft Entra ID (directory role, such as Global Administrator), Okta, or Workspace role (note that this is *not* an AWS role; use `identity:type:aws-iam-role` to search AWS roles)
* **usage** - represents privilege usage (in the last 90 days):
  * `used` - the privilege was used in the last 90 days
  * `unused` - the privilege has been unused for all of the previous 90 days
  * `unknown` - P0 lacks evidence to determine if the privilege is used or unused

## Search attributes

Search attributes allow more specific type searches. Allowable attributes are:

{% hint style="info" %}
Microsoft Entra data uses two provider values: `entra-id` for the current Microsoft Entra ID integration and `azure-ad` for the legacy Azure AD integration. Search the value that matches your installed integration. Azure cloud resources (subscriptions, role assignments) use the separate `azure` provider.
{% endhint %}

* **condition:expression** - a grant condition's expression
* **credential:stale90** - represents whether this credential is stale: created more than 90 days ago (`true`) or more recently (`false`)
* **credential:last40** & **credential:last90** - represents if this authentication method has been used in the previous 40 or 90 days (respectively); values are `used` or `unused`
* **credential:type** - the type of the authentication credential; may be one of
  * `federated` - a credential from an external IAM system
  * `key` - a static secret credential
  * `password` - a user password credential (for example, a Microsoft Entra ID user password)
  * `short-lived` - represents all ephemeral credentials, including JWTs, temporary keys, account impersonation, and the like
* **entitlement:cross** - true if access is granted to an identity managed outside of the IAM resource (e.g. a GCP role assigned to an Okta user)
* **entitlement:parent** - the scope (AWS account, Azure subscription, GCP project, etc.) in which this entitlement is defined
* **entitlement:principal** - the principal identity granted access by this entitlement
* **entitlement:principalType** - the identity type of this entitlement's principal identity (see `identity:type` below for possible values)
* **entitlement:provider** - the service in which this entitlement is defined; possible values are `aws`, `azure`, `azure-ad`, `entra-id`, `gcp`, `k8s`, `okta`, or `workspace`
* **entitlement:resource** - the resource(s) to which this entitlement grants access
* **entitlement:role** - the role granted by this entitlement
* **identity:accessAdd** - who can add users to this group (only available for Workspace groups):
  * `admin` - only group administrators can add users
  * `group` - anyone in the group can add users
  * `owner` - only the group owners can add users
  * If not present, no one can directly add users
* **identity:accessApprove** - who can approve group join requests (only available for Workspace groups):
  * `admin` - only group administrators can approve requests
  * `group` - anyone in the group can approve requests
  * `owner` - only the group owners can approve requests
  * If not present, no one can approve requests
* **identity:accessJoin** - who can join this group without approval (only available for Workspace groups):
  * `public` - anyone on the Internet can join
  * `domain` - anyone in the Workspace domain can join
  * `invited` - users can join if they've received an invite
  * If not present, users can only be directly added to the group
* **identity:accessView** - who can view this group's content (only available for Workspace groups; content is the group's messages):
  * `public` - anyone on the Internet can view
  * `domain` - anyone in the Workspace domain can view
  * `group` - anyone in the group can view
  * `admin` - only group administrators can view
* **identity:external** - true if the identity is managed outside the assessed environment
* **identity:parent** - the scope (AWS account, Azure subscription, GCP project, etc.) that manages this identity
* **identity:provider** - the service that manages this identity; possible values are `aws`, `azure`, `azure-ad`, `entra-id`, `gcp`, `k8s`, `okta`, or `workspace`
* **identity:status** - one of:
  * `active` - the principal can authenticate
  * `disabled` - the principal's authentication is disabled
* **identity:type** - the type of the IAM principal; may be one of
  * `aws-iam-role` - an AWS IAM role
  * `aws-permission-set-role` - an AWS IAM role automatically generated by AWS when assigning an AWS permission set to an account
  * `entra-app-registration` - a Microsoft Entra ID application registration (the application object that defines an app and its permissions)
  * `federated` - an identity used to provide access to identities from another provider (e.g. an AWS IAM role with `Principal.Federated` in its trust relationship); the identity's parents will be the federated identities
  * `group` - a directory group
  * `public` - any identity
  * `service-agent` - a provider-managed account
  * `service-account` - a machine identity (in AWS this is usually an AWS role)
  * `service-principal` - a Microsoft Entra ID service principal (the local instance of an application or managed identity within a tenant)
  * `user` - a user identity
* **lateral:type** - the mechanism via which lateral escalation can be achieved:
  * `grant` - lateral movement via a granted privilege (e.g. GCP `iam.serviceAccounts.actAs` or AWS `sts:assumeRole`)
  * `resource` - lateral movement via usage of a service-linked resource (e.g. lateral movement to a compute service identity via shell access)
  * Note: federated access is represented as a direct principal-to-principal relationship, and is not modeled via lateral-movement
* **privilegeSet:provider** - the service that manages this role or AWS policy; possible values are `aws`, `azure`, `azure-ad`, `entra-id`, `gcp`, `k8s`, `okta`, or `workspace`
* **resource:service** - the resource's parent cloud service; use the API path of the service (e.g. `sso` instead of `Identity Center`)
* **resource:type** - the resource's type (e.g. `bucket`)
* **risk:score** - the access risk score from the [IAM Privilege Catalog](https://catalog.p0.dev/risks); one of `CRITICAL`, `HIGH`, `MEDIUM`, `BOOST`, `EVASION`, or `LOW`

## IAM graph

Your IAM data are connected in a directed graph, as shown, with each node label indicating the datum's respective type:

<div data-full-width="true"><figure><img src="/files/eaDEI372kpVB57bLgdPt" alt=""><figcaption></figcaption></figure></div>

{% hint style="info" %}
Via queries should only be used for types matching along a directed path in this graph. E.g., `identity:->usage:->risk:` will produce matches, but `resource:->risk:` will not.
{% endhint %}

## Query builder

You can assemble a single search term with the **Query builder** instead of typing it. Click the **+** button beside the **where** box, above the table or graph, to open the builder form. As you set each control, the builder shows a live preview of the term, and clicking **Add term** inserts it into the **where** box.

Each control maps to an operator described above:

| Builder control                                   | Effect on the term                                                                   |
| ------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Show … that are** vs **can reach**              | Matches the displayed node itself (`=`) or a node it reaches (`:`).                  |
| Search type (**identities**, **entitlements**, …) | Adds the [type prefix](#type-matches), such as `identity:`.                          |
| **with** *(attribute)*                            | Adds an [attribute filter](#attribute-matches).                                      |
| **containing** vs **equal to**                    | [Substring match](#contains-matches) or [exact match](#exact-matches) (`"keyword"`). |
| **Invert match?**                                 | Applies the `!` [inverted](#inverted-matches) operator to the keyword.               |
| **Remove match?**                                 | Applies the `^` [exclusion](#exclusion-matches) operator to the term.                |
| **Only first node?**                              | Uses `=` instead of `:` to match only the [first node](#first-matches) in a chain.   |

The builder constructs one term at a time and produces ordinary query text, the same as what you would type by hand. To require several conditions, add multiple terms (see [Multiple matches](#multiple-matches)).


# Posture overview

Detect access vulnerabilities with automated monitors. View scan results, define custom policies, and enforce organization-specific access controls with P0.

When P0 scans your environment, it automatically runs P0-provided monitors against your data to detect access vulnerability issues. You can also define custom monitors to enforce your organization-specific access policies.

When you select **Posture** in the P0 app sidebar, you see the results of each monitor:

<figure><img src="/files/En5gA1PKFJUuR8KQeqoO" alt="Posture overview page showing findings summary with urgent, new, and average age metrics, filter controls, and a list of monitors with severity and count"><figcaption></figcaption></figure>

#### Filtering results

You can narrow results to a specific target scope (e.g. AWS account, Azure subscription, or GCP project) or monitor.

By default, this display shows you all open findings. Select the filter icon to reveal the filter controls.

Use the **Status** dropdown to filter by finding status:

* `Open`: show open findings (issues appearing in the latest run that are not manually ignored)
* `Ignored`: show only ignored findings (see [Finding details](/posture/finding-details#ignoring-findings) to learn how)
* `Resolved`: show findings that have been resolved

Enable the **Unassigned** checkbox to show only findings that haven't been assigned. Clear it to show all findings.

Fixed findings are resolved automatically by P0 when they are no longer detected in your latest assessment run.

#### Results export

Select "Export" to download these results as a TSV or JSON file.

#### Result details

Selecting a monitor takes you to that monitor's [Monitor results](/posture/monitor-results) page.

#### Custom monitors

To create a custom monitor, run a query search in [Access inventory](/inventory/access-inventory), then follow the instructions in [Access inventory](/inventory/access-inventory#creating-custom-monitors).


# Create a custom monitor

Build a custom posture monitor in P0 to continuously detect a specific access vulnerability, such as unused privileged grants, across every environment scan.

P0 runs its built-in monitors against your environment on every scan to surface common access vulnerabilities. When you need to enforce a policy that's specific to your organization (for example, flagging unused privileged grants or credentials that violate your rotation policy), you create a custom monitor.

A custom monitor turns an [inventory query](/inventory/query-search) into a recurring check. P0 re-runs the query on every scan and reports matches as findings in [Monitor Results](/posture/monitor-results), alongside your built-in monitors.

## Prerequisites

Before you start, make sure you have:

* A P0 account with the **Owner** or **Admin** role. See [Role-Based Access Control](/p0-management/role-based-access-control).
* At least one [resource integration](/integrations/resource-integrations) connected to an [environment](/environments/creating-an-environment).
* A completed access scan. If you haven't run one yet, follow [Getting Started with Access Inventory](/getting-started/getting-started-with-access-inventory).
* Familiarity with P0 [query expressions](/inventory/query-search). This guide uses them to define what the monitor detects.

## Build and save the monitor

You create custom monitors from the **Inventory** page, where you can write and test a query before promoting it to a monitor.

1. Select **Inventory** in the P0 app sidebar.
2. Select what the monitor evaluates from the **show** control: `credentials`, `identities`, `entitlements`, or `resources`. For example, select `entitlements` to monitor grants.
3. Enter a query in the **where** field that isolates the vulnerability you want to detect. To find entitlements that grant critical-risk privileges that no one has used, enter:

   ```
   usage:type:"unused"->risk:CRITICAL
   ```

   This matches entitlements connected to an unused privilege that carries a `CRITICAL` risk in the [IAM Privilege Catalog](https://catalog.p0.dev/risks).
4. Review the results. Adjust the query until only the grants you want to flag appear. Select **view** on any result to see its **Query Path**, which shows why the result matched.
5. Select **Save Search**, then select **Create from Scratch** (or **Create from Template** to start from an existing monitor). The **Save This Search** dialog opens.
6. Enable the **Create a monitor for this search?** toggle.
7. Enter a **Monitor Name** and **Description**, set a **Priority**, and select the **Scope** (the integrations the monitor applies to). Write the description so a finding's assignee understands the risk and how to resolve it.
8. Save the monitor.

{% hint style="info" %}
To model a new monitor on an existing one, open a built-in monitor in [Monitor Results](/posture/monitor-results) and reuse its query as a starting point. For example, the built-in unused service-account key monitor uses `show = credential` with:

```
credential=enabledKey:"true"
credential=last40:"unused"
identity=type:"service-account"
identity=status:"active"
```

{% endhint %}

## Verify the monitor

Confirm the monitor is active and reporting:

1. Select **Posture** in the sidebar.
2. Find your monitor by its name in the monitor list. It appears with the priority you assigned.
3. Select the monitor to open its [Monitor Results](/posture/monitor-results) page and review the findings from the most recent scan.

P0 re-evaluates the monitor on every scan. New matches appear as open findings, and P0 resolves findings automatically once a grant no longer matches the query.

From the results, you can [assign, ignore, or review fixes](/posture/finding-details) for findings, and route them to your [ticketing system](/integrations/tracker-integrations).

## Troubleshooting

| Problem                                    | Cause                                               | Fix                                                                                                                                                |
| ------------------------------------------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| The monitor returns no findings            | The query is too narrow, or no current data matches | Re-run the query on the Inventory page and confirm it returns the expected results before saving                                                   |
| The monitor returns too many findings      | The query is too broad                              | Add [type, attribute, or via matches](/inventory/query-search) to narrow scope, for example, scope to one provider with `entitlement:provider:aws` |
| The monitor isn't in the Posture list      | Findings appear after the next scan completes       | Wait for the next scan, or trigger a scan from the [environment settings](/environments/settings)                                                  |
| Results changed unexpectedly between scans | The underlying access data changed                  | Open a finding and review its **Attack Path** and **Details** to see why it now matches                                                            |

## What's next

* Tune detection with the full [query expression reference](/inventory/query-search/search-reference).
* [Analyze and resolve findings](/posture/finding-details) the monitor produces.
* Connect a [tracker integration](/integrations/tracker-integrations) to assign findings automatically.


# Monitor results

Each monitor results page shows details of the monitor itself at the top of the page:

* The monitor name
* The monitor severity
* A detailed description of the monitor
* The history of new and resolved findings for this monitor

<figure><img src="/files/141CrIKrrpMMcQ0LnEQA" alt="Monitor results page for AWS Policies With Unused Privileged Access showing severity, findings history chart, and a table of policy findings" width="563"><figcaption></figcaption></figure>

Below is a list of findings for the monitor, filterable by finding status and target scope (AWS account, Azure subscription, GCP project, etc.).

#### Finding details

Click "view" next to a finding to get its detailed view. See [Finding details](/posture/finding-details) for documentation on this display.

#### Bulk actions

You can [assign](/posture/finding-details#assigning-findings), [ignore](/posture/finding-details#ignoring-findings), or review fixes for multiple findings at once. If no findings are selected, operations apply to all findings.


# Finding details

To view extended information for a single finding, click "view" next to a monitor result. This opens that finding's details:

<figure><img src="/files/2zdfyiAEzfqElwIxNG8P" alt="Finding details page showing attack path visualization, actions for Assign, Ignore, and Review fix, and risk details for an AWS IAM policy" width="563"><figcaption></figcaption></figure>

### Actions

#### Assign

If you've integrated P0 with Jira, you can assign findings for resolution. By default, you manually assign the assignee. If you've configured P0 for automatic assignment, the configured resource owner for the target scope in which the finding was discovered determines the assignee.

The created Jira ticket contains the finding description, finding context, and, if available, the resolution commands.

#### Ignore

To ignore a finding, click the "Ignore" button on that finding's details. The finding no longer appears in your results, unless you select "Ignored" in the results views.

#### Review fix

For P0-provided monitors, P0 provides cloud shell commands that resolve the finding. For example, for the "Unused Privileged Access" finding, P0 provides commands to replace the vulnerable entitlement with a least-privilege entitlement.

<figure><img src="/files/RjndgWnpG66UvxOQCynO" alt="Remediation commands showing a least-privilege IAM policy replacement for an overly permissive AmazonEC2FullAccess policy" width="375"><figcaption></figcaption></figure>

#### Add notes

Add notes, including business justifications, to findings by typing on the finding's details page.

### Attack path

P0 displays a graphical representation of the finding's attack path: how an actor holding the identity can gain risky access to your system.

The attack path view has the same capabilities as the [Access inventory](/inventory/access-inventory#graph-visualization) in the Access Inventory.

### Detailed information

The remainder of this page shows detailed information for the finding, and explains why this finding matched the monitor's search. For an explanation of this information, see [Result details](/inventory/result-details).


# Just-in-time access

Set up ephemeral, on-demand access to production resources. Requests are approved, provisioned, and automatically revoked within minutes using P0 Security.

Just-in-time (JIT) access allows your organization's users to gain ephemeral access to individual production resources on demand, replacing standing access. Access is requested, approved, provisioned, and automatically revoked, all within minutes.

{% hint style="info" %}
**New to P0?**

* **Developers requesting access:** Start with the [request access quickstart](/getting-started/request-access-quickstart) to submit your first request in minutes.
* **Administrators setting up P0:** Start with the [Getting started with just-in-time access](/getting-started/getting-started-with-just-in-time-access) guide to configure integrations and approval workflows.
  {% endhint %}

## How it works

1. A user **requests** access to a specific resource (via Slack, the P0 web app, or the [P0 CLI](/p0-cli/p0-commands-and-usage/p0-request)).
2. P0 **routes** the request to the appropriate approver based on your organization's policies.
3. An approver **reviews and approves** (or denies) the request.
4. P0 **provisions** access automatically and notifies the requestor.
5. Access **expires** after the approved duration, or the requestor relinquishes it early.

## Request access

Learn how to create, track, and manage access requests:

* [Requesting access](/access-management/just-in-time-access/requesting-access): Create access requests via Slack or the P0 web app, discuss with approvers, and manage active sessions.
* [Request on behalf of another user](/access-management/just-in-time-access/requesting-access/for-another-party): Submit access requests for a colleague or service account.
* [Web request modal](/access-management/just-in-time-access/requesting-access/web-request-modal): Use the P0 web app to request access directly from your browser.

You can also request access using the P0 CLI. See [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request), [`p0 aws role assume`](/p0-cli/p0-commands-and-usage/p0-aws-role-assume), and [`p0 ssh`](/p0-cli/p0-commands-and-usage/p0-ssh) for command-line workflows.

## Approve access

Configure approval policies and review incoming requests:

* [Approving access](/access-management/just-in-time-access/approving-access): Configure approvers, review requests, and manage approval workflows.
* [Pre-approving access](/access-management/just-in-time-access/approving-access/pre-approving-access): Set up standing approvals so that specific users or groups receive access automatically.
* [P0 Allow modal, CLI, and UI](/access-management/just-in-time-access/approving-access/p0-allow-workflows): Grant pre-approved access through the web app or CLI.

## Configure access policies

Control who can request what, and who approves:

* [Access policies](/access-management/just-in-time-access/access-policies): Define policies that route requests to specific approvers based on the requestor, resource, and access type.
  * [Google Cloud filtering](/access-management/just-in-time-access/access-policies/google-cloud-filtering)
  * [AWS filtering](/access-management/just-in-time-access/access-policies/aws-filtering)
  * [Microsoft Azure filtering](/access-management/just-in-time-access/access-policies/microsoft-azure-filtering)
  * [SSH filtering](/access-management/just-in-time-access/access-policies/ssh-filtering)

## Monitor and audit

* [Session recording](/access-management/just-in-time-access/session-recording): View detailed activity logs for privileged sessions to answer questions about what happened during access.

## Automate with the API

* [Just-in-time API](/access-management/just-in-time-access/just-in-time-api): Programmatically manage access workflows, including the [Command API](/access-management/just-in-time-access/just-in-time-api/command-api), [Access Requests API](/access-management/just-in-time-access/just-in-time-api/access-requests-api), and [Access Policies API](/access-management/just-in-time-access/just-in-time-api/access-policies-api).


# Requesting access

Request just-in-time access to cloud resources using P0.

Need temporary access to a cloud resource? P0 lets you request time-limited access that's automatically provisioned after approval and revoked when it expires.

You can request access through any of these methods:

| Method              | Best for                                      | Details                                                     |
| ------------------- | --------------------------------------------- | ----------------------------------------------------------- |
| **Slack**           | Quick requests when you already use Slack     | [Request via Slack](#request-via-slack)                     |
| **Microsoft Teams** | Quick requests when you already use Teams     | [Request via Microsoft Teams](#request-via-microsoft-teams) |
| **Web app**         | Browser-based requests without Slack or Teams | [Request via the web app](#request-via-the-web-app)         |
| **CLI**             | Terminal workflows and automation             | [Request via the CLI](#request-via-the-cli)                 |

## How access requests work

1. You **request** access to a specific resource, role, or permission.
2. P0 **routes** the request to the appropriate approver based on your organization's policies.
3. An approver **reviews and approves** (or denies) the request.
4. P0 **provisions** access automatically and notifies you.
5. Access **expires** after the approved duration, or you relinquish it early.

{% hint style="info" %}
Most IAM systems have a propagation delay of 10-60 seconds after access is provisioned before you can use it.
{% endhint %}

## Request via Slack

If your organization has installed the [Slack integration](/integrations/notifier-integrations/slack), you can request access directly from Slack.

### Using the Slack request modal

Open the interactive modal to discover available resources and access modes:

* Type `/p0 request` in any Slack channel
* Or click the **Run Shortcut** icon in the message draft bar, then search for and select **Request access**

<figure><img src="/files/DTQwubIk7vMN7kPCnTWG" alt="" width="375"><figcaption></figcaption></figure>

Select a resource and access type, then fill out the remaining fields. You may skip optional fields.

<figure><img src="/files/uTk7OYw7KVCRjhHHE1My" alt="" width="375"><figcaption></figcaption></figure>

{% hint style="info" %}
The "reason" field is optional, but highly recommended. Filling this out helps your request get approved more quickly.
{% endhint %}

Once you've filled out all required fields, click **Request**. P0 then sends you a DM with details of your request.

<figure><img src="/files/1pmITc1dqKrCdLkE1j63" alt="" width="375"><figcaption></figcaption></figure>

### Using Slack slash commands

If you already know what you need, use slash commands for faster requests:

```
/p0 request gcloud role my-project viewer --reason "investigating production issue"
```

Add `--help` to any command to see available options. You can also type an incomplete command (for example, `/p0 request aws policy`) to open a filled modal.

## Request via Microsoft Teams

If your organization has installed the [Microsoft Teams integration](/integrations/notifier-integrations/microsoft-teams), you can request access by messaging the P0 Security bot directly in Teams.

### Sending a request message

Open a direct message with the **P0 Security** bot and type your request. For example:

```
request gcloud role my-project viewer --reason "investigating production issue"
```

The bot presents an interactive card where you can fill in resource details, select an access type, and submit your request.

### Supported commands

You can use the following commands when messaging the P0 Security bot:

| Command   | Purpose                              |
| --------- | ------------------------------------ |
| `request` | Request access to a resource         |
| `ls`      | List available resources             |
| `help`    | Display available commands and usage |

Type `help` to see the full list of available commands and options.

## Request via the web app

1. Open the [P0 app](https://p0.app) and navigate to **Access Management**.
2. Click **Request Access** in the header.
3. Fill in the resource, role, duration, and reason.
4. Submit for approval.

You can also repeat an earlier request without re-entering its details: open the request from the **Access Management** page and click **Request again** to open the modal prefilled with that request's values.

For a detailed walkthrough, see [Web request modal](/access-management/just-in-time-access/requesting-access/web-request-modal).

## Request via the CLI

The [P0 CLI](/p0-cli/installing-p0-cli) provides command-line access requests that integrate with your terminal workflow.

After [installing](/p0-cli/installing-p0-cli) and [logging in](/p0-cli/p0-commands-and-usage/p0-login), use these commands:

| Command                                                                                      | Purpose                                                  |
| -------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| [`p0 request`](/p0-cli/p0-commands-and-usage/p0-request)                                     | Request access to any supported resource                 |
| [`p0 aws role assume`](/p0-cli/p0-commands-and-usage/p0-aws-role-assume)                     | Request and assume an AWS IAM role                       |
| [`p0 aws permission-set assume`](/p0-cli/p0-commands-and-usage/p0-aws-permission-set-assume) | Request and assume an AWS Identity Center permission set |
| [`p0 ssh`](/p0-cli/p0-commands-and-usage/p0-ssh)                                             | Request and establish an SSH session                     |
| [`p0 kubeconfig`](/p0-cli/p0-commands-and-usage/p0-kubeconfig)                               | Request Kubernetes access and configure kubeconfig       |

Use `--wait` with any request command to block until the system provisions access, then automatically execute the underlying tool command.

## Request access to specific resources

The methods described earlier in this topic work for every resource type. For request syntax, access levels, and details specific to a resource, see its integration guide:

| Resource            | Guide                                                                                                             |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| AWS                 | [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)                                |
| Google Cloud        | [Requesting Google Cloud access](/integrations/resource-integrations/google-cloud/requesting-access)              |
| Microsoft Azure     | [Requesting Microsoft Azure access](/integrations/resource-integrations/microsoft-azure/requesting-access)        |
| Kubernetes          | [Requesting Kubernetes access](/integrations/resource-integrations/kubernetes/requesting-access)                  |
| SSH                 | [SSH](/integrations/resource-integrations/ssh)                                                                    |
| PostgreSQL          | [Requesting PostgreSQL access](/integrations/resource-integrations/postgresql-new/requesting-postgresql-access)   |
| MySQL               | [Requesting MySQL access](/integrations/resource-integrations/mysql/requesting-access)                            |
| Snowflake           | [Snowflake](/integrations/resource-integrations/snowflake)                                                        |
| GitHub              | [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access)                          |
| Salesforce          | [Requesting Salesforce access](/integrations/resource-integrations/salesforce/requesting-access)                  |
| Cloudflare          | [Requesting Cloudflare access](/integrations/resource-integrations/cloudflare/requesting-access)                  |
| Grafana Cloud       | [Requesting Grafana Cloud access](/integrations/resource-integrations/grafana-cloud/requesting-access)            |
| Oracle Cloud        | [Requesting Oracle Cloud access](/integrations/resource-integrations/oracle-cloud/requesting-access)              |
| Tailscale           | [Requesting Tailscale access](/integrations/resource-integrations/tailscale/requesting-access)                    |
| Cisco Secure Access | [Requesting Cisco Secure Access](/integrations/resource-integrations/cisco-secure-access/requesting-access)       |
| Microsoft Entra ID  | [Requesting Microsoft Entra ID access](/integrations/directory-integrations/microsoft-entra-id/requesting-access) |

For the full list of supported resources, see [Resource integrations](/integrations/resource-integrations).

## Discussing your request with approvers

After you submit a request, a message appears in your organization's P0 approval channel (in Slack or Microsoft Teams) asking for approval. Your approver may:

* Approve or deny the request immediately
* Respond in a thread to ask for more information

{% hint style="info" %}
You can't approve your own requests unless you are a configured approver *and* your organization allows one-party approvals.
{% endhint %}

If your organization has configured automatic approvals and you meet the approval conditions (for example, you are on-call on a specified PagerDuty escalation policy), access is granted automatically.

## Gaining access

After approval, P0 provisions access and notifies you. Propagation times vary by system:

| System                                  | Time to use                      |
| --------------------------------------- | -------------------------------- |
| AWS                                     | 10-15 seconds                    |
| Directories (Okta, Entra ID, Workspace) | Depends on SCIM propagation time |
| Google Cloud                            | 30 seconds - 1 minute            |
| PostgreSQL                              | Immediate                        |
| Snowflake                               | Immediate                        |

## Relinquishing access

Access expires automatically after the approved duration. If you finish early, click the **Relinquish** button in your P0 notification (in Slack or Microsoft Teams) or on the **Access Management** page in the P0 app to give up access. This helps avoid unintentional use of elevated permissions.

## Request on behalf of someone else

You can submit access requests for a colleague or service account. See [Request for another party](/access-management/just-in-time-access/requesting-access/for-another-party).

## Related

* [Approving access](/access-management/just-in-time-access/approving-access) -- How approvers review and manage requests
* [Access policies](/access-management/just-in-time-access/access-policies) -- How your organization controls who can request what
* [Pre-approving access](/access-management/just-in-time-access/approving-access/pre-approving-access) -- Set up automatic approvals for specific scenarios


# For another party

In addition to using P0 to request just-in-time access for yourself, you can use P0 to request access for another account.

For instance, if you use Terraform to deploy infrastructure, you may need to temporarily escalate the privileges of Terraform's service account during deploy.

## :pray: Creating a 2nd-party request

To make a 2nd-party access request, use the `/p0 grant` slash command in Slack.

The arguments for this command are exactly the same as `/p0 request` (see [Requesting access](/access-management/just-in-time-access/requesting-access#using-slack-slash-commands)), with a couple changes:

* You must add a `--to <email>` option to your request, using the email identifier of the principal to which you want to grant access
* When requesting access to Google Cloud, use `--principal-type group` or `--principal-type service-account` to grant access to users groups or service accounts, respectively

The principal issuing the `grant` command does not have to be a valid approver. The principal in the `--to` argument must be a valid requestor based on [access policies](/access-management/just-in-time-access/access-policies).

## :speech\_balloon: Discussing your request

After you make your request, an approval message will be sent to your approvals channel. This approval message is exactly the same as for a first-party access request, except that the approval message indicates that you are making the request on behalf of the email you specified:

<figure><img src="/files/rTiTPCxK9zXKBM3lFiyv" alt="Slack approval message showing an on-call approver notified that one user requests access to a storage role on behalf of another user" width="563"><figcaption></figcaption></figure>


# Web request modal

## Overview

The **Web Request Modal** in **P0 Security** allows engineers to request **just-in-time** (JIT) and **short-lived** access to cloud resources. You can launch the modal directly from the **P0 App**. This process automates approvals, reduces wait times, and ensures access is provisioned and revoked according to the specified duration, thereby strengthening security by granting permissions only when needed.

## Requesting Access

1. Open the **P0 App**.
2. Click **Access Management**. Click **Request Access**.

<figure><img src="/files/7FIOpdfPOHrpCQMMSrEt" alt="P0 app Access Management page with Activity, Pre Approvals, History, and Shell tabs and the Request Access button highlighted" width="439"><figcaption></figcaption></figure>

3. Fill in the form with resource, role, duration, and reason.
4. Submit for approval.

### Typical Workflow

Request → Approval → Automatic Provisioning → Automatic Revocation

Access expires automatically after the requested duration.

## Request again

To repeat a previous request, reuse it instead of filling out the modal from scratch:

1. On the **Access Management** page, open a request to view its details.

<figure><img src="/files/mfhxbLAJjnd6J9CGao04" alt="P0 Request Details panel with the Request again button highlighted, showing its tooltip explaining it creates a new access request prefilled with this request&#x27;s details"><figcaption></figcaption></figure>

2. Click **Request again**. The **Request Access** modal opens, prefilled with the original request's resource, access type, access arguments, reason, and duration.
3. Edit any field as needed.

<figure><img src="/files/Vb8IcAbI6LHERfgErbx6" alt="Request Access modal prefilled with the Secure Shell (SSH) resource, single-instance access type, and destination miguel-node-2, ready for editing" width="259"><figcaption></figcaption></figure>

4. Submit for approval.

You can start from any request, including one submitted by another user. P0 doesn't carry over the requestor or principal, so submitting always creates a request for you and routes it through normal policy evaluation.

{% hint style="info" %}
If a prefilled value is no longer valid (for example, a deleted role), the field shows the stored value so you can select a new one. If the resource is no longer requestable, P0 shows an error instead of a partial form.
{% endhint %}

**Request again** isn't shown for a request whose access you currently hold—relinquish that access instead, or start from a different request.

If you run into any issues, please reach out to <support@p0.dev> for assistance. We're here to help!


# Approving access

This page describes how to review and approve just-in-time access requests

This page walks you through the lifecycle of an access request and review.

* :gear: [Configuring approvals](#configuring-approvals)
* :bell: [Request notifications](#request-notifications)
* :white\_check\_mark: [Approving (and denying) access](#approving-and-denying-requests)
* :thinking: [Reviewing requests](#reviewing-requests)

## :gear: Configuring approvals

There are two ways to configure approvals with P0:

* Default approvals
* Access policies

### Default approvals

To use the default approvals, you must configure who can approve and revoke access requests. Do this on [p0.app](https://p0.app)'s "P0 Management" page, under "Access control".

<figure><img src="/files/zeTDW3y3F4f2LurpLh0B" alt="" width="563"><figcaption></figcaption></figure>

Configure *who* can approve access requests by entering approvers' emails in the "Security Reviewers" section. Approvers must have accounts in Slack using the same email addresses.

{% hint style="info" %}
Approvers' email addresses may be from outside your domain.
{% endhint %}

#### Two-party and one-party approvals

By default, a requestor cannot approve their own access requests. If you want to allow requestors to approve their own requests, allow one-party approvals.

#### Auto approvals

In addition to approvals by humans, P0 also allows you to automatically approve requests if the requestor is currently on-call on an escalation policy. See [Approval Integrations](/integrations/approval-integrations) for more details.

#### Escalated approvals

In addition to normal approval flow, P0 allows the requestor to escalate the request using PagerDuty or Incident.io and notify on-call users to approve pending requests. See [Approval Integrations](/integrations/approval-integrations) for setup details.

### Access policies

If you need more fine-grained control over approvals based on who is requesting access, and to what, use access policies. See the [Access Policies](/access-management/just-in-time-access/access-policies) reference for more details.

The remainder of this guide assumes your organization is using default approvals.

## :bell: Request notifications

When an access request is made, P0 creates an approval message in your Slack integration's configured channel.

<figure><img src="/files/nMdyGqFgjO8UE1fFVE8H" alt="" width="375"><figcaption></figcaption></figure>

With default approvals, P0 mentions the `@p0approvers` Slack group, which contains all configured approvers.

If you use access policies with directory group approvers, P0 instead DMs each approver with a link to the approval message.

## :white\_check\_mark: Approving (and denying) requests

To **Approve** this request, first choose an access duration from the "Select expiry" dropdown, then click "Approve".

{% hint style="warning" %}
If you are not in the P0 approvers group, you will receive an error when attempting to approve or deny access.
{% endhint %}

To **Deny** this request, click "Deny".

#### Requesting further justification

If the requestor's justification for requesting access is incomplete or needs follow-up, reply to the request message *in a thread*. The request conversation thread is linked to the access request, and this discussion will be available in future access reviews.

## :thinking: Reviewing requests

You can review all requests made via P0, whether approved or denied, by visiting p0.app and navigating to "Access Management". You see a dashboard of all requests:

<figure><img src="/files/o74EuHdS9z2e7S3mZa4M" alt="" width="563"><figcaption></figcaption></figure>

Clicking the Slack icon in the request description will take you to the approval-message conversation, where you can view any conversation around justification.

You can also get more details on the lifecycle of an individual grant by clicking on a request row to open the "Request Details" drawer:

<figure><img src="/files/OvoYGLmKVBoREExGjAJT" alt="" width="563"><figcaption></figcaption></figure>

Finally, you can export all requests as a tab-separated values list (`.tsv`) by clicking "Export requests".


# Auto-approve access for on-call engineers

Configure P0 to automatically grant just-in-time access to engineers who are on-call, using your PagerDuty or Incident.io schedule. Give responders fast, time-boxed access during incidents without man

This guide shows you how to grant just-in-time access automatically to engineers who are currently on-call, using your PagerDuty escalation policies or Incident.io schedules. When an on-call responder requests a covered resource, P0 approves the request without waiting for a human approver, then expires the access after one hour.

Use this pattern for break-glass and incident-response workflows. Responders need fast access during an incident, but you still want every grant to be time-boxed, logged, and tied to an active on-call shift.

## Prerequisites

Before you begin, confirm the following:

* **P0 organization owner role**: You configure integrations and access policies as an organization owner.
* **An on-call provider**: Either a PagerDuty account where you are an admin, or an Incident.io account where you are an administrator with API key access.
* **A resource integration**: At least one resource integration (AWS, Google Cloud, Azure, SSH, Kubernetes, or another) installed, so there is something to grant access to. See [Resource integrations](/integrations/resource-integrations).
* **Matching identities**: The email addresses your engineers use in PagerDuty or Incident.io match the identities they use to sign in to P0.

{% hint style="warning" %}
Anyone who can place themselves on-call for an enabled schedule can grant themselves access without further approval. Restrict who can edit your on-call schedules and overrides accordingly.
{% endhint %}

## Step 1: Connect your on-call provider

Install the approval integration for the provider you use.

{% tabs %}
{% tab title="PagerDuty" %}

1. Navigate to **Integrations** on [p0.app](https://p0.app), then select **PagerDuty**.
2. Click **Install integration**. P0 redirects you to PagerDuty's consent screen.
3. Approve the installation to return to P0.

For full details, see [PagerDuty](/integrations/approval-integrations/pagerduty).
{% endtab %}

{% tab title="Incident.io" %}

1. Navigate to **Integrations** on [p0.app](https://p0.app), then select **Incident.io**.
2. Create an API key in your Incident.io account with permissions to view data, create and edit incidents, manage organization settings, view catalog types and entries, and read schedules. See the [Incident.io API documentation](https://docs.incident.io/integrations/api-overview#where-can-i-find-the-api-keys).
3. Enter the API key in P0 and click **Install integration**. P0 validates the key and creates a custom field in your Incident.io account.

For full details, see [Incident.io](/integrations/approval-integrations/incidentio).
{% endtab %}
{% endtabs %}

## Step 2: Select the schedules that grant access

Tell P0 which on-call rotations qualify a requestor for automatic approval.

{% tabs %}
{% tab title="PagerDuty" %}

1. On the **PagerDuty** integration page, find the escalation policy selector.
2. Select one or more escalation policies whose active on-call members receive automatic approval.
3. Save your changes.

P0 approves a request when the requestor is currently on-call for one of the selected escalation policies.
{% endtab %}

{% tab title="Incident.io" %}

1. On the **Incident.io** integration page, open the auto-approval settings.
2. Select one or more schedules from the dropdown. P0 fetches available schedules from your Incident.io account.
3. Save your changes.

P0 approves a request when the requestor is currently on-call for one of the selected schedules.
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
For Incident.io, you must select at least one schedule. If you select none, auto-approval denies every request.
{% endhint %}

## Step 3: Add an auto-approval rule to your access policy

The integration tells P0 *who* is on-call. An access policy tells P0 *which requests* to auto-approve. Add an `auto` approval rule to your policy configuration.

1. Navigate to **Policy Studio** on [p0.app](https://p0.app).
2. Add a rule with the requestor, resource, and approval sections shown in the following example.
3. Save the policy configuration.

The following policy auto-approves any AWS request from an on-call engineer and requires a reason on every request:

{% tabs %}
{% tab title="PagerDuty" %}

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: pagerduty
      options: { requireReason: true }
```

{% endtab %}

{% tab title="Incident.io" %}

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: incidentio
      options: { requireReason: true }
```

{% endtab %}
{% endtabs %}

The system automatically approves a matching request for one hour when the requester is on-call. The `requireReason` option records why each grant was necessary, useful for incident review. Set it to `false` to skip the reason prompt.

{% hint style="info" %}
Change `service: aws` to the integration you want to cover (`gcloud`, `azure`, `ssh`, `k8s`, and others), or use `resource: { type: any }` to cover every resource. To narrow the rule to specific resources, add [filters](/access-management/just-in-time-access/access-policies#integration). For the full policy format, see [Access Policies](/access-management/just-in-time-access/access-policies).
{% endhint %}

## Step 4 (optional): Fall back to manual approval

On-call auto-approval pairs well with a manual approver for everyone else. List both approval types in the same rule. P0 combines them so that an on-call requestor is auto-approved, while anyone off-call routes to your security reviewers.

{% tabs %}
{% tab title="PagerDuty" %}

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: pagerduty
      options: { requireReason: true }
    - type: p0
```

{% endtab %}

{% tab title="Incident.io" %}

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: auto
      integration: incidentio
      options: { requireReason: true }
    - type: p0
```

{% endtab %}
{% endtabs %}

The `p0` approval type routes to the security reviewers configured on your **Access control** settings. See [Approving access](/access-management/just-in-time-access/approving-access#configuring-approvals).

## Verify it worked

Confirm the rule before you rely on it during an incident:

1. Make sure a test user is on-call for one of the selected schedules (add a temporary override in PagerDuty or Incident.io if needed).
2. As that user, request a covered resource, for example, run `p0 request aws role MyReadOnlyRole --account 123456789012 --reason "Testing on-call auto-approval"`.
3. Confirm the request is approved automatically, without a human approver acting on it.
4. Open **Access management > History** at `https://p0.app/o/<your-org>/access-management/history` and confirm the request shows automatic approval and the reason you supplied.
5. As a user who isn't on-call, request the same resource and confirm the request routes to manual approval (if you added the Step 4 fallback) or the system denies it (if you didn't).

## Troubleshooting

| Symptom                                                     | Cause                                                                   | Fix                                                                                                                                                                                         |
| ----------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| On-call user's request is denied                            | The user's P0 email doesn't match their PagerDuty or Incident.io email  | Align the email addresses across both systems                                                                                                                                               |
| Every request is denied (Incident.io)                       | No schedules selected in auto-approval settings                         | Select at least one schedule on the Incident.io integration page                                                                                                                            |
| Request routes to manual approval instead of auto-approving | The requestor isn't currently on-call, or their schedule isn't selected | Confirm the user's active on-call shift and select their escalation policy or schedule in Step 2                                                                                            |
| Rule has no effect                                          | The resource or requestor in the policy doesn't match the request       | Check the `service` value and requestor type against the request; see [Evaluation of access policies](/access-management/just-in-time-access/access-policies#evaluation-of-access-policies) |
| Access expires too soon                                     | Auto-approval grants last one hour by design                            | For longer standing access, use [pre-approvals](/access-management/just-in-time-access/approving-access/pre-approving-access) instead                                                       |

## What's next

* [Escalate stuck requests to on-call approvers](/access-management/just-in-time-access/access-policies#escalation) with the `escalation` approval type, so on-call users can approve *others'* requests during an incident.
* [Pre-approve access](/access-management/just-in-time-access/approving-access/pre-approving-access) for planned work that's not tied to an on-call shift.
* [Configure access policies](/access-management/just-in-time-access/access-policies) to route different resources to different approvers.
* [PagerDuty](/integrations/approval-integrations/pagerduty) and [Incident.io](/integrations/approval-integrations/incidentio) integration reference.


# Pre-approving access

Approvers can pre-approve access before the requestor creates an access request.

For instance, the approver may want to allow certain users to elevate their permissions automatically for the duration of a project, without having to wait for the approver to approve manually every time.

## 🕒 Allowing access ahead of time

To allow access requests for the future, use the `/p0 allow` slash command in Slack.

The arguments for this command are similar to `/p0 request` (see [Requesting access](/access-management/just-in-time-access/requesting-access#using-slack-slash-commands)), with a few additional required options:

* `--to <email>`\
  Use the email identifier of the principal to which you want to grant access
* `--length <duration>`\
  Duration for auto-approved access. The requestor will be automatically approved between the `start` time of the `allow` command up to this duration. Format like `'10 minutes'`, `'2 hours'`, `'5 days'`, or `'1 week'`.
* `--request-duration <duration>`\
  Access duration for individual requests. Once the requestor submits an access request, it will be automatically approved for this duration. Format like the `--length` option.

Optionally you can specify the `--start` option, which is the start time for auto-approved access. Defaults to now if not specified. Use ISO 8601 format, like `'2021-01-01T00:00:00Z'` or `2021-01-01`.

The principal issuing the `allow` command must be a valid approver for the principal in the `--to` argument, and for the resources specified, based on [access policies](/access-management/just-in-time-access/access-policies).


# P0 allow modal, CLI, and UI

Configure P0 allow pre-approvals from Slack, the CLI, or the P0 web app.

## Overview

`p0 allow` creates **standing pre-approvals** so specific principals can receive access automatically when they request it. Team members can configure these allows from three entry points:

* **Slack allow modal** for guided form-based setup
* **`p0 allow` CLI** for scripted or bulk automation
* **P0 App UI** for point-and-click management across environments

Every allow captures the same core fields: the target resource, the principal (`--to`), how long the auto-approval window remains active with `--start` and `--length`.

> You must be an approver for the target resource according to your [access policies](/access-management/just-in-time-access/access-policies) before you can create or edit an allow.

## Slack Allow Modal

Slack provides an interactive modal when you launch `/p0 allow` without full arguments. Use it when you prefer a visual form but want to stay in Slack.

### Launching the modal

* Type `/p0 allow` in any direct message or channel where the P0 bot is installed.
* (Optional) Include a partial command like `/p0 allow aws` to pre-populate the provider and resource fields.
* Press **Enter**. Slack opens the **Allow access** modal.

### Completing the form

The modal walks you through:

1. **Principal**: Email or directory identifier for the user, group, or service account.
2. **Provider & target**: Choose the cloud/platform and specify the resource (for example, IAM role, project, permission set).
3. **Auto-approval window**: Select start/end or a duration (maps to `--start` and `--length`). The modal defaults to the current time plus one week.
4. **Per-request duration**: How long each automatically approved request lasts (`--requested-duration`).
5. **Reason (optional)**: Appears in audit logs and request history when requestors receive access.

Submit the modal to create the allow. P0 confirms in DM and posts to the approval channel if configured. The modal’s interactive blocks provide inline validation (including date/time pickers), so you see errors such as missing `--to` or malformed durations before submission.

## CLI `p0 allow`

Use the CLI when you need automation, version-controlled workflows, or provider-specific flags. The command accepts the same data as the modal but allows you to script or template it.

```bash
p0 allow <provider> <subcommand> [resource args…] \
  --to <principal> \
  --length <duration> \
  [--start <timestamp>] \
  [--reason <text>] \
  [--wait]
```

Key tips:

* Run `p0 allow <provider> --help` for resource-specific arguments (for example, `--project`, `--account`).
* Durations accept natural language strings such as `"4 hours"`, `"10 days"`, or `"1 month"`.
* Combine with `p0 ls` to discover requestable resources before issuing the allow.
* Use `--wait` when you want the CLI to block until backend provisioning completes.

See `p0-cli/p0-commands-and-usage/p0-allow.md` for full examples covering AWS, GCP, SSH, and other providers, plus troubleshooting guidance in `p0-cli/troubleshooting/p0-allow.md`.

## Operational Best Practices

* **Align access policies first.** Ensure the allow creator appears as an approver for the resource; otherwise the modal and UI block creation.
* **Default to shorter windows.** Use the smallest `--length` that satisfies the project needs and rely on renewals when necessary.
* **Capture justification.** Require reasons via access policy options (`requireReason: true`) so audit logs show why the standing access exists.
* **Review regularly.** Schedule periodic reviews of the **Pre Approvals** tab in Access Management or use the CLI to script an inventory (`p0 allow list --json`).

## Related Resources

* [Pre-approving Access](/access-management/just-in-time-access/approving-access/pre-approving-access)
* [p0 allow CLI reference](/p0-cli/p0-commands-and-usage/p0-allow)
* [Access Policies](/access-management/just-in-time-access/access-policies)


# Access policies

Access policies allow you to limit and direct just-in-time access requests, based on *who* is making the request and *which* resource is requested.

Use this page to configure and manage access policies.

* [Configuring access policies](#configuring-access-policies)
* [Access policy format](#access-policy-format)
* [Examples](#examples)
* [Evaluation of access policies](#evaluation-of-access-policies)

## Configuring access policies

To use access policies, go to p0.app and navigate to **Policy Studio**. Saving a policy configuration here configures your organization to use policy-based approvals, rather than default approvals.

<figure><img src="/files/AalhKnpEIkq5zVIooJ4h" alt="" width="563"><figcaption></figcaption></figure>

You can also manage access policies programmatically instead of in Policy Studio:

* [**Access Policies API**](/access-management/just-in-time-access/just-in-time-api/access-policies-api) — create, read, update, and delete policies over HTTP. Use this to script incremental changes or to build policy management into your own tools.
* [**Terraform provider**](/getting-started/manage-p0-policies-and-settings-as-code) — manage policies as version-controlled, reviewable configuration with the [`p0_access_policy`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/access_policy) resource, alongside your RBAC role assignments and JIT access settings.

All three methods manage the same single policy configuration, so a change made through one is reflected in the others.

## Access policy format

When working with access policies directly, use this reference page for specific definitions of how to format your request. This feature of P0 is powerful and customizable, and is accessible through the [Access Policies API](/access-management/just-in-time-access/just-in-time-api/access-policies-api) and the [Terraform provider](/getting-started/manage-p0-policies-and-settings-as-code).

### Structure of a policy configuration

A policy configuration is a collection of access policies. To change how P0 routes your requests (such as sending engineers' requests to their managers or restricting access to a particular prod resource to a small group), submit a new policy configuration.

You only have one policy configuration active at any given time, but that one configuration can have as many individual access policies as you want. You don't need to separate out different rule types or anything like that.

Here's an example policy configuration that you can copy into your app as a starting point:

```yaml
- requestor:
    type: group
    id: engineering@yourorg.com
    label: Engineering
    directory: workspace
  resource:
    type: integration
    service: snowflake
  approval:
    - type: group
      id: dataops@yourorg.com
      label: Data Ops Team
      directory: workspace
      options: {allowOneParty: false}
```

### Structure of a "Rule"

The core of policy configurations are the rules, so let's break them down.

### Requestor

The first part of a rule is a "requestor", which matches who makes the request. There are four types of requestors you can choose from:

#### "Any"

```yaml
requestor:
  type: any
```

This will match all requestors. Useful for global rules like fall-back restrictions of sensitive resources.

#### "Group"

```yaml
requestor:
  type: group
  id: <group identifier>
  label: <human-readable name>
  directory: entra-id|okta|workspace
```

This rule will match any requestor that is a member of a group in your IdP. Currently, this supports Google Workspace, Microsoft Entra ID, and Okta.

**id**: For Google Workspace, this is the group email address (ie, <engineering@yourco.com>). For Entra, this is the Entra ID group's UUID. For Okta, this is the group ID found in the URL of the group's page in the admin console. See [Okta docs](https://support.okta.com/help/s/article/how-to-find-group-ids-through-the-okta-user-interface?language=en_US).

**label**: This is any friendly human-readable name you like, although P0 suggests using the same name as displayed in your directory.

**directory**: For Google Workspace this is "workspace", for Okta this is "okta", and for Entra ID, this is "entra-id".

#### "User"

```yaml
requestor:
  type: user
  uid: <user's email address>
```

This rule will match only a specific user, as identified by their email address.

#### "Agentic"

```yaml
requestor:
  type: agentic
  agent: <agent rule>
  user: <user rule>
```

This rule matches requests made by AI agents through the [P0 AI Gateway](/readme/agentic-control-plane), based on the agent and user combination. See [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) for the agent rule types and examples.

### Resource

The second part of an access policy is the resource this rule should apply to.

#### "Any"

```yaml
resource:
  type: any
```

This will match all resources. This is useful, for instance, when you want to route access for a particular user, regardless of what resource or service they are looking to access.

#### "Integration"

```yaml
resource:
  type: integration
  service: <target service>
  accessType: <target access type>
```

This rule will match a specific type of access request. For instance, maybe you want to create a rule that routes all AWS requests to your DevOps team. Or you want to restrict access to GCP to only a select group.

**service**: For GCloud, "gcloud", for AWS, "aws", for Microsoft Azure "azure", for Snowflake, "snowflake", and for SSH, "ssh".

**accessType** (Optional): The access type within the service that the rule should apply to, or "any" meaning the rule will match requests of any access type. Defaults to "any" if omitted.

**filters** (Optional):\
Filters allow you to apply the rule only to the service components that match the filtering condition.

```yaml
resource:
  type: integration
  service: aws|azure|azure-ad|gcloud|k8s|okta|snowflake|ssh
  filters:
    <access-type>:
      effect: keep|remove|removeAll
      key: <property>
      pattern: <regex pattern>
```

Each filter has a `filter-name` that refers to the type of the requested object. For instance, when "service" is "aws", the filter with the name "policy" will only allow requesting policies whose ARNs match the `pattern`.

Filters are independent when the access type is omitted or set to "any." For example, if you specify a `filter-name=permission` filter for `service=gcloud` requests, but omit a `filter-name=role` filter, then roles are unaffected by the filter, and all roles remain requestable.

Each filter has three potential properties:

* **effect** - Describes how this filter is applied
  * **keep** - This rule will retain objects that match the pattern as requestable
  * **remove** - This rule will only retain objects that do not match the pattern
  * **removeAll** - This rule disables the object type entirely.
  * If you want to allow all objects of a type as requestable, omit the filter altogether
* **key** - Describes which property of the object the filter must match; only applies if "effect" is one of "keep" or "remove". For instance, if `service=aws` and `access-type=permission-set` then the key can be "name" or "arn". If "name", the regex pattern will be matched against the name of permission sets. If "arn", the entire arn is matched.
* **pattern** - A regex pattern used to match against the specified property; only applies if "effect" is one of "keep" or "remove"

{% hint style="info" %}
Patterns are unanchored. Use line-start (`^`) and line-end (`$`) markers to anchor patterns.

P0 uses the [JavaScript regex dialect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions).
{% endhint %}

*Filter behavior*

The behavior of filters depends on the requested access type.

For example, the "role" filter in the Gcloud integration has a slightly different meaning depending on which access type is used in the request:

* When making a GCP request with "role" as the access type (e.g. `p0 request gcloud role`), the filter limits the roles the user can request access to
* When making a GCP request with "resource" as the access type (e.g. `p0 request gcloud resource`), the filter limits the roles that the user can request on the resource they are requesting

By setting the rule's access type to either "role" or "resource", you can explicitly specify which of these requests is allowable.

{% hint style="warning" %}
To ensure predictable filtering behavior, P0 recommends setting an `accessType` constraint when using filters.
{% endhint %}

Valid filter filter-name / key combinations are summarized in the following table. For more detailed information on the filters, see the documentation for [AWS](/access-management/just-in-time-access/access-policies/aws-filtering), [Microsoft Azure](/access-management/just-in-time-access/access-policies/microsoft-azure-filtering), [Google Cloud](/access-management/just-in-time-access/access-policies/google-cloud-filtering), and [SSH](/access-management/just-in-time-access/access-policies/ssh-filtering).

<table><thead><tr><th width="155">service</th><th width="147">access-type</th><th width="136">key</th><th>Notes</th></tr></thead><tbody><tr><td>aws</td><td>tag</td><td>&#x3C;tag key></td><td>Only filters policies and permission sets. AWS managed policies can't be tagged.</td></tr><tr><td></td><td>group</td><td>name</td><td>Filters IAM group names</td></tr><tr><td></td><td>permission-set</td><td>arn | name</td><td>Filters Identity Center permission sets</td></tr><tr><td></td><td>policy</td><td>arn</td><td>Filters policies with matching ARNs</td></tr><tr><td></td><td>resource</td><td>arn | name | service</td><td>Filters resource requests based on the resource ARN, the resource name, or the service the resource belongs to.</td></tr><tr><td>azure</td><td>subscription</td><td>id</td><td>Filters requests at a subscription level</td></tr><tr><td></td><td>resource</td><td>id | name</td><td>Filters requests based on what resource is requested</td></tr><tr><td></td><td>role</td><td>id | name</td><td>Filters requests based on the role requested</td></tr><tr><td>azure-ad|okta</td><td>group</td><td>id | label</td><td>Filters directory groups</td></tr><tr><td>gcloud</td><td>permission</td><td>id</td><td>Filters IAM permissions</td></tr><tr><td></td><td>role</td><td>id</td><td>Filters IAM roles based on full ID (for example, the ID for "Owner" is <code>roles/owner</code>)</td></tr><tr><td></td><td>resource</td><td>name | type | full-resource-name</td><td>Filters resource requests based on the resource name, resource type, or the full resource name.</td></tr><tr><td>snowflake</td><td>role</td><td>name</td><td>Filters roles</td></tr><tr><td>k8s</td><td>resource</td><td>kind | name | namespace</td><td>Filters resources</td></tr><tr><td></td><td>role</td><td>name</td><td>Filters roles and cluster roles</td></tr><tr><td></td><td>cluster</td><td>name</td><td>Filters clusters based on the cluster name specified when installed in P0</td></tr><tr><td>ssh</td><td>provider</td><td>id</td><td>Filters by cloud provider (<code>aws</code>, <code>gcloud</code>, <code>azure</code>, or <code>self-hosted</code>)</td></tr><tr><td></td><td>destination</td><td>arn | name | full-resource-name</td><td>Filters by instance identifier</td></tr><tr><td></td><td>group</td><td>name</td><td>Filters by SSH group name</td></tr><tr><td></td><td>parent</td><td>id</td><td>Filters by parent resource ID (AWS account ID, GCP project ID, or Azure subscription ID)</td></tr><tr><td></td><td>region</td><td>id</td><td>Filters by AWS region, GCP zone, or Azure region</td></tr><tr><td></td><td>sudo</td><td><em>(boolean)</em></td><td>Filters by whether sudo privileges are requested</td></tr></tbody></table>

Filters are not currently available for AWS or GCP resource-level grants, nor for Snowflake SQL grants.

*Example that covers each possible access type in the AWS service:*

1. Exclude all permission sets and policies containing "FullAccess"
2. Only allow AWS policies and permission sets where the tag "P0Grantable" is equal to the string "true"
3. Do not allow requesting any AWS groups

<pre><code><strong>- resource:
</strong>    type: integration
    service: aws
    accessType: permission-set
    filters:
      permission-set:
        effect: remove
        key: name
        pattern: FullAccess
      tag:
        effect: keep
        key: P0Grantable
        pattern: ^true$
  requestor:
    type: any
  approval:
    - type: any

- resource:
    type: integration
    service: aws
    accessType: policy
    filters:
      policy:
        effect: remove
        key: name
        pattern: FullAccess
      tag:
        effect: keep
        key: P0Grantable
        pattern: ^true$
  requestor:
    type: any
  approval:
    - type: any
    
- resource:
    type: integration
    service: aws
    accessType: group
  requestor:
    type: any
  approval:
    - type: deny
</code></pre>

### Approval

The final part of an access policy is the "approval". Unlike the requestor and resource parts, the approval part is an array of multiple rules, referring to the people/groups/services that can approve (or deny) access requests.

#### "p0"

```json
approval:
  - type: p0
    options: { allowOneParty: true|false, requireReason: true|false, breakGlassApprover: true|false }
```

This approval type is analogous to the default approval behavior. This rule routes approvals to the people designated as "Security Reviewers" on the Settings page of your app. The "options" key is optional. You can use it to:

* allow the requestor to approve their own requests for this flow with the `allowOneParty: true` setting. Defaults to `false`.
* require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.
* designate this approver as a break glass approver for SSH "all" access requests with the `breakGlassApprover: true` setting. Defaults to `false`. See [SSH Filtering](/access-management/just-in-time-access/access-policies/ssh-filtering#break-glass-access) for details.

#### "Group"

```yaml
approval:
  - type: group
    id: <group identifier>
    label: <human readable name>
    directory: entra-id|okta|workspace
    options: { allowOneParty: true|false,  requireReason: true|false, breakGlassApprover: true|false }
```

This rule will match any requestor who is a member of a group in your Identity Provider. Currently, P0 supports Google Workspace, Microsoft Entra ID, and Okta.

**id**: For Google Workspace, this is the group email address (ie, <engineering@yourco.com>). For Entra ID, this is the Entra ID group's UUID. For Okta, this is the group ID found in the URL of the group's page in the admin console. See [Okta docs](https://support.okta.com/help/s/article/how-to-find-group-ids-through-the-okta-user-interface?language=en_US).

**label**: This is any friendly human-readable name you like, although P0 suggests using the same name as displayed in your directory. This label will be printed in approval notifications.

**directory**: For Google Workspace this is "workspace", for Okta this is "okta", and for Entra ID this is "entra-id".

**options**: See ["p0" section](#p0)

#### "Auto"

<pre class="language-yaml"><code class="lang-yaml"><strong>approval:
</strong>  - type: auto
    integration: pagerduty|incidentio
    options: { requireReason: true|false }
</code></pre>

This rule automatically approves matching access requests for one hour.

**integration**: `"pagerduty"` or `"incidentio"`. P0 automatically approves the request if the requestor is on-call. For PagerDuty, P0 checks the escalation policies configured on the integration page. For Incident.io, P0 checks the schedules selected on the [Incident.io integration settings page](/integrations/approval-integrations/incidentio#selecting-schedules).

**options**: The "options" key is optional. You can use it to require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.

#### "Escalation"

<pre><code><strong>approval:
</strong>  - type: escalation
    integration: pagerduty|incidentio
    options: { allowOneParty: true|false,  requireReason: true|false, breakGlassApprover: true|false }
    services: [&#x3C;Service or Schedule ID>]
</code></pre>

This rule routes approval to users who are currently on-call for the specified services or schedules. When combined with other approval rules, it enables on-call users to approve requests that have been escalated.

**integration**: `"pagerduty"` or `"incidentio"`.

* For PagerDuty, on-call status is determined by the escalation policies of the configured services. An incident is created against the specified services when the request is escalated.
* For Incident.io, on-call status is determined by the specified schedule IDs. Users who are on-call for any of the listed schedules can approve the request.

**options**: See ["p0" section](#p0).

**services**: List of PagerDuty service IDs or Incident.io schedule IDs, depending on the configured integration.

#### "Always allowed"

```yaml
approval:
  - type: persistent
```

Access is always granted automatically.

**options**: The "options" key is optional. You can use it to require the requestor to specify a reason when submitting requests with the `requireReason: true` setting. Defaults to `false`.

#### "Deny"

```yaml
approval:
  - type: deny
```

This rule will deny all requests that match. This supersedes other matching rules: if at least one "deny" rule matches, the request will be denied.

## Examples

Access policies can support a variety of use cases.

#### Different access depending on organizational role

Configure different approvers and different resources for developers and customer-success engineers:

```yaml
- requestor:
    type: group
    id: devs@you.co
    label: Developers
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: sre@you.co
      label: SREs
      directory: workspace
      options: { allowOneParty: true }
- requestor:
    type: group
    id: customer-success@you.co
    label: Customer Success
    directory: workspace
  resource:
    type: integration
    service: snowflake
  approval:
    - type: group
      id: data-ops@you.co
      label: Data Ops
      directory: workspace
      options: { allowOneParty: false }
```

#### Different rules by AWS account

Development AWS account (123456789) allows one-party approvals. Staging AWS account (234567891) allows peer approvals but no one-party approvals. Production AWS account (345678912) requires a manager approver represented by an Okta group. Assumes an AWS Identity Center setup where resource filters allow requesting customer-managed policies in specific AWS accounts, enforced by an ARN match that includes the AWS account ID. Permission sets are implicitly allowed because the `permission-set` access type filter is not defined.

<pre class="language-yaml"><code class="lang-yaml"><strong>- requestor:
</strong>    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::123456789:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: { allowOneParty: true }
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::234567891:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 00g5j4jojlGZMzfhM69
      label: Engineers
      directory: okta
      options: { allowOneParty: false, requireReason: true }
- requestor:
    type: group
    id: 00g5j4jojlGZMzfhM69
    label: Engineers
    directory: okta
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      policy: { effect: keep, key: arn, pattern: ^arn:aws:iam::345678912:policy/ }
      group: { effect: removeAll }
  approval:
    - type: group
      id: 01f5j4jfjlGZMzfhN99
      label: Managers
      directory: okta
      options: { allowOneParty: false, requireReason: true }
</code></pre>

#### Filter access to AWS policies and permission sets by tag

Restrict which AWS policies and permission sets can be requested to those for which a tag matches a specific value:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: aws
    accessType: policy
    filters:
      tag: { effect: keep, key: P0Grantable, pattern: ^true$ }
  approval:
    - type: p0
- requestor:
    type: any
  resource:
    type: integration
    service: aws
    accessType: permission-set
    filters:
      tag: { effect: keep, key: P0Grantable, pattern: ^true$ }
  approval:
    - type: p0
```

#### Org- and resource-specific access policies

Allow either the requestor's manager or the service owner to approve requests:

```
- requestor:
    type: group
    id: devs@you.co
    directory: workspace
  resource:
    type: any
  approval:
    - type: group
      id: eng-managers@you.co
      label: Eng managers
      directory: workspace
- requestor:
    type: group
    id: devs@you.co
    directory: workspace
  resource:
    type: integration
    service: gcloud
  approval:
    - type: group
      id: gcloud-owners@you.co
      label: GCloud owners
      directory: workspace
```

#### Mandatory reason for a specific resource type

Make reason mandatory for AWS requests

<pre class="language-yaml"><code class="lang-yaml"><strong>- resource:
</strong>    type: any
  approval:
    - options: {requireReason: false, allowOneParty: true}
      type: p0
  requestor:
    type: any
- resource:
    service: aws
    type: integration
  approval:
    - options: {requireReason: true}
      integration: pagerduty
      type: auto
  requestor:
    type: any
</code></pre>

#### Exclude cluster-admin role and only allow default namespace in Kubernetes

```yaml
- resource:
    type: integration
    service: k8s
    accessType: resource
    filters:
      resource:
        effect: keep
        key: namespace
        pattern: ^default$
      role:
        effect: remove
        key: name
        pattern: ClusterRole/cluster-admin
  approval:
    - type: p0
  requestor:
    type: any
```

#### Escalate request using PagerDuty for priority approval when requesting google cloud requests

```
- resource:
    service: gcloud
    type: integration
  approval:
    - type: p0
      options: {requireReason: true, allowOneParty: false}  
    - type: escalation
      integration: pagerduty
      options: {requireReason: true, allowOneParty: false}
      services: [PSJXXXG]
  requestor:
    type: any
```

#### Allow users to request access to Google Cloud roles, but not permissions

```
- resource:
    service: gcloud
    type: integration
    accessType: role
  approval:
    - type: p0
  requestor:
    type: any
- resource:
    service: gcloud
    type: integration
    accessType: permission
  approval:
    - type: deny
  requestor:
    type: any
```

#### Deny access to specific roles, but allow users to request others

```
- resource:
    service: gcloud
    type: integration
    accessType: role
    filters:
      role:
        effect: keep
        key: name
        pattern: roles/owner
  approval:
    - type: deny
  requestor:
    type: any
- resource:
    service: gcloud
    type: integration
    accessType: role
  approval:
    - type: p0
  requestor:
    type: any
```

## Evaluation of access policies

1. Match Rules to the Request

   Identify rules that match the request. A rule matches if both the requestor and the resource criteria in the rule align with the request details.
2. Handle Missing Matches

   If no rules match, the access request is not created, and further evaluation stops. The requestor will see the following message:

   *This resource doesn't exist, or your organization doesn't allow this principal to access this resource*
3. Evaluate Access Control of Matching Rules

   For matching rules, review the Access Control field in the following order:

   * Deny all access\
     If *any* matching rule specifies "Deny all access", the request is denied immediately, and further evaluation stops.
   * Always allowed\
     If any rule specifies "Always allowed," the request is automatically approved and provisioned, and further evaluation stops.
   * Third-party auto-approval\
     If a rule specifies a third-party auto-approval, such as a PagerDuty or Incident.io on-call rule, the request is evaluated for automatic approval. If it cannot be auto-approved, proceed to the next step.
   * Manual approval\
     For all other Access Control values, the request requires manual approval by a designated person or group. When multiple rules with different manual approvers match, approval from any one approver is sufficient to provision the request. All approvers are notified.

### Key points to remember

* **Deny all access:** Always takes precedence and stops further evaluation immediately.
* **Always allowed:** Overrides other Access Control values and is prioritized in the evaluation process.
* **Multiple matching rules:** When multiple rules match the requestor and resource, approvers are combined using an **ANY** relationship, meaning approval from any one approver is sufficient.

### Examples

1. **Always allowed rule takes precedence over other allow rules**

Consider a scenario where two rules apply to the same resource, **`node1`** in the **devstack** group:

* An "Always allow" rule grants access to **`node1`**.
* Another "Allow" rule requires approval before granting access.

In this case, the **"Always allow" rule** takes precedence. As a result:

* The user is immediately granted access to **`node1`** without requiring additional approval.

2. **Deny Rules are evaluated first, before ANY allow rules**

Using the same example as before, if there is an additional rule that **denies access** to any instance containing **`node`** in its name, the system will evaluate the "Deny" rule first.

In this case:

* The "Deny" rule matches because **`node1`** contains the string **`node`** in its name.
* The user's request is immediately denied, and is not evaluated further.

This highlights the priority of "Deny" rules, ensuring they are enforced before any other rules are considered.


# Configure your first access policy

Create and manage access policies in P0 Security's Policy Studio to control who can request access to which resources and how requests are approved.

Access policies give you fine-grained control over who can request access, which resources they can request, and how those requests are approved. This guide walks you through creating your first policy in Policy Studio.

{% hint style="info" %}
Access policies require a **Pro-tier** P0 subscription.
{% endhint %}

## Prerequisites

Before you begin, confirm the following:

* A P0 account with a **Pro-tier** subscription.
* **Owner** or **Security Reviewer** role in your P0 organization.
* At least one [resource integration](/integrations/resource-integrations) installed (AWS, Google Cloud, Azure, Kubernetes, or another supported resource).
* A [directory integration](/integrations/directory-integrations) configured (Google Workspace, Okta, or Microsoft Entra ID) if you plan to use group-based policies.

## Open Policy Studio

1. Sign in to [p0.app](https://p0.app).
2. Navigate to **Policy Studio** in the sidebar, or go to `https://p0.app/o/<your-organization>/policies`.

You see a list of existing policies. If this is your first time, the list may be empty or contain a default policy.

{% hint style="info" %}
Toggle between **Table view** and **YAML view** from the **View options** menu (the ellipsis button in the page header) to see your policies in different formats.
{% endhint %}

## Create a new policy

1. Click **Add New Policy**.

   The policy editor opens in **Guided editor**, which provides a visual form for building your policy. To edit raw YAML instead, select **YAML editor** from the dropdown menu in the header.
2. Enter a descriptive name for your policy in the name field at the top (for example, "Engineering - AWS read-only").

The editor has three sections: **Identity**, **Resource(s)**, and **Actions**. Configure each one in order.

## Define the identity (who can request)

The **Identity** section specifies which users can make requests that match this policy.

1. In the **Identity** section, select one of the following:
   * **Any user**: Matches all users in your organization.
   * **Specific users**: Matches a specific user or directory group.
2. If you selected **Specific users**, choose one of the following in the **Match by** row:
   * **Specific user**: Enter the user's email address.
   * **Directory group**: Select your directory provider and enter the group identifier.

{% hint style="info" %}
**Finding your group identifier:**

* **Google Workspace:** The group email address (for example, `engineering@yourcompany.com`).
* **Okta:** The group ID from the admin console URL. See the [Okta documentation](https://support.okta.com/help/s/article/how-to-find-group-ids-through-the-okta-user-interface?language=en_US) for details.
* **Microsoft Entra ID:** The group's UUID from the Entra admin center.
  {% endhint %}

### Example

To create a policy for your engineering team using Google Workspace:

* Select **User** then **Directory group**.
* Directory: **Google Workspace**
* Group ID: `engineering@yourcompany.com`
* Label: `Engineering`

## Define the resource (what they can request)

The **Resource(s)** section specifies which resources this policy applies to.

1. In the **Resource(s)** section, select one of the following:
   * **Any resource**: Matches all integrated resources.
   * A specific integration (for example, **AWS**, **Google Cloud**, **Kubernetes**).
2. If you selected a specific integration, optionally configure:
   * **Access type**: Restrict to a specific type (for example, `role`, `permission-set`, or `resource` for AWS).
   * **Filters**: Narrow the scope further by matching resource properties.

### Add a resource filter

Filters let you control which specific resources within an integration can be requested.

1. Click **Add New Resource Filter** in the resource section.
2. Select the **filter type** (for example, `policy`, `role`, `resource`).
3. Choose the **effect**:
   * **Include only**: Allow requests for resources that match the pattern.
   * **Exclude only**: Block requests for resources that match the pattern.
   * **Exclude all**: Disable this resource type entirely.
4. Enter the **pattern** (a regular expression) to match against.

{% hint style="info" %}
Patterns are unanchored by default. Use `^` and `$` to anchor patterns. For example, `^true$` matches the exact string "true", while `FullAccess` matches any resource containing "FullAccess" in its name.
{% endhint %}

### Example

To restrict AWS access to non-admin permission sets:

* Integration: **AWS**
* Access type: **permission-set**
* Filter: `permission-set`, Effect: **Exclude only**, Pattern: `FullAccess|Admin`

## Define the actions (how requests are approved)

The **Actions** section specifies how matching requests are handled.

1. Select an approval type:

| Approval type              | Behavior                                                                                                                                                                                  |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **P0 Security Reviewers**  | Routes to your configured Security Reviewers                                                                                                                                              |
| **Group approval**         | Routes to members of a specific directory group                                                                                                                                           |
| **Always allowed**         | Automatically grants access (standing access)                                                                                                                                             |
| **Deny all**               | Denies all matching requests                                                                                                                                                              |
| **Auto-approve (on-call)** | Automatically approves if the requestor is on-call (requires [PagerDuty](/integrations/approval-integrations/pagerduty) or [Incident.io](/integrations/approval-integrations/incidentio)) |
| **Escalation service**     | Routes to on-call users for priority approval                                                                                                                                             |
| **Manager approval**       | Routes to the requestor's manager, read from your directory                                                                                                                               |

2. Configure approval options as needed:
   * **Require reason with request**: Requestors must give a justification.
   * **Allow self-approvals**: Requestors can approve their own requests.
3. To add multiple approvers, click **Add New Access Rule** to create additional approval rules. When multiple approvers are configured, approval from any one approver is sufficient.

### Example

To route engineering AWS requests to the SRE team with mandatory justification:

* Approval type: **Group**
* Directory: **Google Workspace**
* Group ID: `sre-team@yourcompany.com`
* Label: `SRE Team`
* Enable **Require reason with request**

## Save and enable the policy

1. Review your policy configuration across all three sections.
2. Click **Create Policy**. (When you later edit an existing policy, this button reads **Save Policy**.)
3. Confirm the policy appears in the policies list with the **Enabled** toggle turned on.

{% hint style="warning" %}
Policies take effect immediately after saving. When you save a new policy, it becomes part of your active workflow and applies to all later access requests.
{% endhint %}

## Verify the policy

1. Have a user who matches the identity criteria make a test access request via Slack (`/p0 request`), the [P0 CLI](/p0-cli/p0-commands-and-usage/p0-request), or the [web request modal](/access-management/just-in-time-access/requesting-access/web-request-modal).
2. Confirm the following:
   * The request is created (not blocked by a missing match).
   * The correct approvers receive the approval notification.
   * Resources outside the policy's filters are not available for request.

{% hint style="info" %}
If the requestor sees *"This resource doesn't exist, or your organization doesn't allow this principal to access this resource"*, no policy matches their request. Review the identity and resource criteria in your policy.
{% endhint %}

## Common policy patterns

### Separate approvers by environment

Route development requests to peer engineers with self-approval, and production requests to managers without self-approval:

```yaml
# Development - peer approval with self-approve
- requestor:
    type: group
    id: engineering@yourcompany.com
    label: Engineering
    directory: workspace
  resource:
    type: integration
    service: aws
    filters:
      policy:
        effect: keep
        key: arn
        pattern: "^arn:aws:iam::111111111111:policy/"
  approval:
    - type: group
      id: engineering@yourcompany.com
      label: Engineering
      directory: workspace
      options: { allowOneParty: true }

# Production - manager approval, no self-approve
- requestor:
    type: group
    id: engineering@yourcompany.com
    label: Engineering
    directory: workspace
  resource:
    type: integration
    service: aws
    filters:
      policy:
        effect: keep
        key: arn
        pattern: "^arn:aws:iam::222222222222:policy/"
  approval:
    - type: group
      id: eng-managers@yourcompany.com
      label: Engineering Managers
      directory: workspace
      options: { allowOneParty: false, requireReason: true }
```

### Auto-approve on-call engineers

Automatically approve requests from engineers who are currently on-call in PagerDuty:

```yaml
- requestor:
    type: group
    id: engineering@yourcompany.com
    label: Engineering
    directory: workspace
  resource:
    type: any
  approval:
    - type: auto
      integration: pagerduty
      options: { requireReason: true }
```

### Block sensitive roles

Deny all requests for the Google Cloud Owner role:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: gcloud
    accessType: role
    filters:
      role:
        effect: keep
        key: id
        pattern: roles/owner
  approval:
    - type: deny
```

## Understand policy evaluation order

When a request matches multiple policies, P0 evaluates them in this order:

1. **Deny**: If any matching policy specifies **Deny**, the request is denied immediately.
2. **Persistent**: If any matching policy specifies **Persistent** (always allowed), the request is automatically approved.
3. **Auto**: If a matching policy specifies auto-approval, P0 checks the requestor's on-call status.
4. **Manual approval**: For all other matches, P0 notifies the combined set of approvers. Approval from any one approver is sufficient.

{% hint style="info" %}
For a complete reference of policy syntax, filter options, and evaluation logic, see the [Access Policies](/access-management/just-in-time-access/access-policies) reference.
{% endhint %}

## Next steps

* Add [resource-specific filters](/access-management/just-in-time-access/access-policies/aws-filtering) to fine-tune which AWS, [Google Cloud](/access-management/just-in-time-access/access-policies/google-cloud-filtering), [Azure](/access-management/just-in-time-access/access-policies/microsoft-azure-filtering), or [SSH](/access-management/just-in-time-access/access-policies/ssh-filtering) resources users can request.
* Configure [PagerDuty](/integrations/approval-integrations/pagerduty) or [Incident.io](/integrations/approval-integrations/incidentio) for on-call auto-approvals.
* Use the [Access Policies API](/access-management/just-in-time-access/just-in-time-api/access-policies-api) to manage policies programmatically.


# Google Cloud filtering

We'll go through all the available access-types for Google Cloud request filtering.

### Filtering permission requests

To filter on permission requests, we can use the `permission` access-type. There is a single available key, `id`, which refers to the permission ID (list available in Google's docs [here](https://cloud.google.com/iam/docs/permissions-reference))

#### Rule structure:

```
resource:
  type: integration
  service: gcloud
  filters:
    permission:
      effect: keep|remove|removeAll
      key: id
      pattern: <regex pattern>
```

#### Allow requesting only bigquery permissions:

```
resource:
  type: integration
  service: gcloud
  filters:
    permission:
      effect: keep
      key: id
      pattern: ^bigquery.
```

#### Allow requesting any permissions except compute.instances.delete

```
resource:
  type: integration
  service: gcloud
  filters:
    permission:
      effect: remove
      key: id
      pattern: ^compute.instances.delete$
```

### Filtering role requests

To filter on permission requests, we can use the `role` access-type. There is a single available key, `id`, which refers to the role ID (list available in Google's docs [here](https://cloud.google.com/iam/docs/understanding-roles)). Note that this is the ID that is prefixed with `roles/`

#### Rule structure:

```
resource:
  type: integration
  service: gcloud
  filters:
    role:
      effect: keep|remove|removeAll
      key: id
      pattern: <regex pattern>
```

#### Allow requesting only compute roles

```
resource:
  type: integration
  service: gcloud
  filters:
    role:
      effect: keep
      key: id
      pattern: ^roles/compute.
```

#### Allow requesting any roles except the basic roles (viewer, editor, owner)

```
resource:
  type: integration
  service: gcloud
  filters:
    role:
      effect: remove
      key: id
      pattern: ^roles/editor$|^roles/viewer$|^roles/owner$
```

### Filtering resource requests

To filter on permission requests, we can use the `resource` access-type. There are 3 available keys:

* `name`: This is the name of the resource.
* `type`: This is the type of the resource. The available values for `type` are below:

| Resource type        | "type" value     |
| -------------------- | ---------------- |
| BigQuery Dataset     | `dataset`        |
| BigQuery Table       | `table`          |
| Compute Zone         | `zone`           |
| Compute Instance     | `instance`       |
| IAM Service Account  | `serviceaccount` |
| Cloud Storage Bucket | `bucket`         |
| Cloud Storage Object | `object`         |

* `full-resource-name`: This is the Google API full resource name, including the service, type, and name. Available formats for the `full-resource-name` are below.

| Resource type        | "type" value                                                                            |
| -------------------- | --------------------------------------------------------------------------------------- |
| BigQuery Dataset     | `//bigquery.googleapis.com/projects/PROJECT_ID/datasets/DATASET_NAME`                   |
| BigQuery Table       | `//bigquery.googleapis.com/projects/PROJECT_ID/datasets/DATASET_NAME/tables/TABLE_NAME` |
| Compute Zone         | `//compute.googleapis.com/zones/ZONE_NAME`                                              |
| Compute Instance     | `//compute.googleapis.com/projects/PROJECT_ID/zones/ZONE_NAME/instances/INSTANCE_NAME`  |
| IAM Service Account  | `//iam.googleapis.com/projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL`        |
| Cloud Storage Bucket | `//storage.googleapis.com/BUCKET_NAME`                                                  |
| Cloud Storage Object | `//storage.googleapis.com/BUCKET_NAME/objects/OBJECT_PATH`                              |

#### Rule structure:

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: keep|remove|removeAll
      key: name|type|full-resource-name
      pattern: <regex pattern>
```

#### Allow requesting only the Bigquery Dataset "customer-data" in project "test"

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: keep
      key: full-resource-name
      pattern: ^//bigquery.googleapis.com/projects/test/datasets/customer-data$
```

#### Allow requesting any Cloud Storage bucket:

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: keep
      key: type
      pattern: ^bucket$
```

#### Allow requesting any resource with "application-1" in the name

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: keep
      key: name
      pattern: application-1
```

#### Allow requesting any resource except compute instances with names starting with "prod" in project "test" and zone "us-west1-a"

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: remove
      key: full-resource-name
      pattern: ^//compute.googleapis.com/projects/test/zones/us-west1-a/instances/prod 
```

#### Allow requesting only Cloud Storage buckets with names starting with dev

```
resource:
  type: integration
  service: gcloud
  filters:
    resource:
      effect: keep
      key: full-resource-name
      pattern: ^//storage.googleapis.com/dev
```


# AWS filtering

This page covers all the available access-types for AWS request filtering.

### Filtering on tags

Policies and permission sets can be filtered based on their tags in AWS. To do this, use the `tag` access-type. The key is the tag key, and the pattern matches on the tag value.

#### Rule structure:

```
resource:
  type: integration
  service: aws
  filters:
    tag:
      effect: keep|remove|removeAll
      key: <tag key>
      pattern: <regex pattern>
```

#### Allow requesting only policies and permission sets with the tag "environment" set to "dev" :

```
resource:
  type: integration
  service: aws
  filters:
    tag:
      effect: keep
      key: environment
      pattern: ^dev$
```

### Filtering on IAM groups

To filter on IAM group requests, use the `group` access-type. There is a single available key, `name`, which refers to the name of the IAM group.

#### Rule structure:

```
resource:
  type: integration
  service: aws
  filters:
    group:
      effect: keep|remove|removeAll
      key: name
      pattern: <regex pattern>
```

#### Allow requesting any IAM groups except for "Admin" :

```
resource:
  type: integration
  service: aws
  filters:
    group:
      effect: remove
      key: name
      pattern: ^Admin$
```

### Filtering on permission sets

To filter on Identity Center permission set requests, use the `permission-set` access-type. There are two available keys, `name` (the name of the permission set) and `arn` (the ARN of the permission set).

#### Rule structure:

```
resource:
  type: integration
  service: aws
  filters:
    permission-set:
      effect: keep|remove|removeAll
      key: name | arn
      pattern: <regex pattern>
```

#### Allow requesting only permission sets with "project-1" in the name:

```
resource:
  type: integration
  service: aws
  filters:
    permission-set:
      effect: keep
      key: name
      pattern: project-1
```

### Filtering on policies

To filter on IAM policy requests, use the `policy` access-type. There is a single available key, `arn`, which refers to the ARN of the IAM policy.

#### Rule structure:

```
resource:
  type: integration
  service: aws
  filters:
    policy:
      effect: keep|remove|removeAll
      key: arn
      pattern: <regex pattern>
```

#### Allow requesting only AmazonS3 predefined policies

```
resource:
  type: integration
  service: aws
  filters:
    policy:
      effect: keep
      key: arn
      pattern: ^arn:aws:iam::aws:policy/AmazonS3
```

### Filtering on resources

To filter on permission requests, use the `resource` access-type. There are 3 available keys:

* `name`: This is the name of the resource.
* `service`: This is the AWS service that the resource belongs to: for example, `s3`, or `sagemaker`. It will found in the resource ARN, after `arn:aws:`. For example, if the ARN is `arn:aws:iam::391052057035:role/AmazonEKSNodeRole` the service is `iam`.
* `arn`: This is the ARN of the resource.

#### Rule structure:

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: keep|remove|removeAll
      key: name|service|arn
      pattern: <regex pattern>
```

#### Allow requesting only S3 resources

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: keep
      key: service
      pattern: ^s3$
```

#### Allow requesting any resource except for IAM resources

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: remove
      key: service
      pattern: ^iam$
```

#### Allow requesting any resource containing "project-1" in the name

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: keep
      key: name
      pattern: project-1
```

#### Allow requesting only S3 buckets with names starting with "dev"

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: keep
      key: arn
      pattern: ^arn:aws:s3:::dev
```

#### Allow requesting any resource except for the S3 bucket named "top-secret-bucket"

```
resource:
  type: integration
  service: aws
  filters:
    resource:
      effect: remove
      key: arn
      pattern: ^arn:aws:s3:::top-secret-bucket$
```


# Microsoft Azure filtering

This document covers the various ways fine-grained just-in-time access for Microsoft Azure can be configured by using P0's access policies.

### Filtering on subscription

Requests can be filtered at the level of an entire subscription by adding a filter based on the subscription's `id`

#### Rule structure:

```
resource:
  type: integration
  service: azure
  filters:
    subscription: {
      effect: keep|remove|removeAll
      key: id
      pattern: <regex pattern>
    }
```

Deny all requests to the subscription with id \<subscription id>

```
resource:
  type: integration
  service: azure
  accessType: any
  filters:
    subscription: {effect: keep, key: id, pattern: <subscription id>}
approval:
  - type: deny
```

### Filtering on resource

Requests can be filtered by details pertaining to the `resource` being requested. There are two available keys for `resource` filters, `name` and `id` .

#### Rule structure:

```
resource:
  type: integration
  service: azure
  filters:
    resource: {
      effect: keep|remove|removeAll
      key: name
      pattern: <regex pattern>
    }
```

#### Examples:

Auto-approve any requests for the resource with an `id` of `/subscriptions/<subscription number>/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus`

```
resource:
  type: integration
  service: azure
  filters:
    resource: {
      effect: keep, 
      key: id, 
      pattern: /subscriptions/<subscription number>/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus
    }
approval:
  - type: persistent
    
```

Auto-approve on-call requests for any resource except for the one named "sensitive-virtual-network"

<pre><code><strong>resource:
</strong>  type: integration
  service: azure
  accessType: any
  filters:
    resource: {effect: remove, key: name, pattern: sensitive-virtual-network}
approval:
  - type: auto
    integration: pagerduty
    options: {}
</code></pre>

### Filtering on roles

Requests can be filtered by details pertaining to the `role` being requested. There are two available keys for `role` filters, `name` and `id` .

#### Rule structure:

```
resource:
  type: integration
  service: azure
  filters:
    role: {
      effect: keep|remove|removeAll
      key: id | name
      pattern: <regex pattern>
    }
```

#### Examples:

Allow approvals of requests to the `role` named "P0 Developer Role" to be approved by users with the DevOpsManager profile property in Okta

```
resource:
  type: integration
  service: azure
  accessType: any
  filters:
    role: {effect: keep, key: name, pattern: P0 Developer Role}
approval:
  - type: requestor-profile
    directory: okta
    options: {}
    profileProperty: DevOpsManager
```

Allow requests to the role with `id` of `/subscriptions/<subscription id>/providers/Microsoft.Authorization/roleDefinitions/5bc02df6-6cd5-43fe-ad3d-4c93cf56cc16` to be approved by users defined in P0

```
resource:
  type: integration
  service: azure
  accessType: any
  filters:
    role: {
      effect: keep, 
      key: id, 
      pattern: /subscriptions/<subscription id>/providers/Microsoft.Authorization/roleDefinitions/5bc02df6-6cd5-43fe-ad3d-4c93cf56cc16
    }
approval:
  - type: p0
    options: {}
```


# SSH filtering

SSH access policies allow you to control access to SSH resources based on provider, destination, group, region, parent resource, and sudo privileges. You can also configure **break glass** access, which grants unrestricted SSH access to all resources for emergency scenarios.

{% hint style="info" %}
Before configuring SSH access policies, ensure you have installed the SSH integration. See [SSH integration setup](/integrations/resource-integrations/ssh) for details.
{% endhint %}

## SSH access types

The SSH integration supports four access types. Each access type represents a different scope of resources you can grant access to:

| Access type | Description                                                                                                  |
| ----------- | ------------------------------------------------------------------------------------------------------------ |
| `session`   | Access to an individual compute instance. This is the most granular level of SSH access.                     |
| `group`     | Access to a tagged collection of instances within a service provider. A group may contain one or more nodes. |
| `parent`    | Access to all instances within a hierarchical unit, an AWS account, GCP project, or Azure subscription.      |
| `all`       | A global access request that grants access to all SSH resources. This is **break glass** access.             |

Outside of `session`, the other access types control access to collections of instances, which may include one or more nodes at a time.

You can scope an access policy to a specific access type using the `accessType` field:

```yaml
resource:
  type: integration
  service: ssh
  accessType: session
```

If you omit `accessType` or set it to `any`, the rule applies to all SSH access types.

## Available filters

SSH access policies support six filter types:

| Filter        | Key                                    | Description                                                                                                                                                                                                    |
| ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`    | `id`                                   | Cloud provider managing the instance. Must be `aws`, `azure`, `gcloud`, or `self-hosted`.                                                                                                                      |
| `destination` | `arn`, `name`, or `full-resource-name` | Instance identifier. Use `arn` for AWS instances, `full-resource-name` for GCP instances, or `name` for instance name across providers. To list available instance names, run `p0 ls ssh session destination`. |
| `group`       | `name`                                 | SSH group name, the tagged collection of instances within a provider.                                                                                                                                          |
| `parent`      | `id`                                   | Parent resource identifier. Use a GCP project name, AWS account ID, or Azure subscription ID.                                                                                                                  |
| `region`      | `id`                                   | AWS region, GCP zone, or Azure region.                                                                                                                                                                         |
| `sudo`        | *(boolean filter)*                     | Whether the request includes sudo privileges. When set, you can control who can approve or deny sudo access.                                                                                                   |

### Filter rule structure

```yaml
resource:
  type: integration
  service: ssh
  filters:
    <filter-name>:
      effect: keep|remove|removeAll
      key: <property>
      pattern: <regex pattern>
```

Each filter has three properties:

* **effect**: How the filter is applied:
  * `keep`: Retain resources that match the pattern
  * `remove`: Retain resources that do *not* match the pattern
  * `removeAll`: Disable this filter type entirely
* **key**: The property to match against
* **pattern**: A regex pattern to match (uses the [JavaScript regex dialect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions))

{% hint style="info" %}
Patterns are unanchored. Use line-start (`^`) and line-end (`$`) markers to anchor patterns.
{% endhint %}

### Sudo filter

The `sudo` filter uses a boolean format rather than a regex pattern:

```yaml
resource:
  type: integration
  service: ssh
  filters:
    sudo:
      effect: keep
      value: true
```

## Filter examples

### Restrict SSH access to a specific cloud provider

Allow SSH requests only for AWS-managed instances:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    filters:
      provider:
        effect: keep
        key: id
        pattern: ^aws$
  approval:
    - type: p0
```

### Restrict SSH access to a specific group

Allow SSH access only to instances in the "web-servers" group:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    accessType: group
    filters:
      group:
        effect: keep
        key: name
        pattern: ^web-servers$
  approval:
    - type: p0
```

### Restrict SSH access by AWS account

Allow SSH access only to instances in a specific AWS account:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    filters:
      parent:
        effect: keep
        key: id
        pattern: ^123456789012$
  approval:
    - type: group
      id: sre-team@yourco.com
      label: SRE Team
      directory: workspace
```

### Deny sudo access

Deny all SSH requests that include sudo privileges:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    filters:
      sudo:
        effect: keep
        value: true
  approval:
    - type: deny
```

### Restrict SSH to a specific region

Allow SSH access only to instances in `us-east-1`:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    filters:
      region:
        effect: keep
        key: id
        pattern: ^us-east-1$
  approval:
    - type: p0
```

## Break glass access

Break glass provides emergency SSH access to **all** resources covered by your access policies. It's designed for incident response, when there is an outage and an engineer needs broad access to production systems to troubleshoot. Instead of requesting access to each instance individually and waiting for approval, they can use break glass access.

### How break glass works

1. A user requests SSH break glass access (access type `all`) via the CLI: `p0 request ssh all`
2. P0 identifies all access policies where the user is a matching requestor **and** the policy has a `breakGlassApprover` configured
3. P0 creates a **separate request for each matching access policy**. If you are a requestor in five different access policies, P0 creates five requests, and each must be individually approved.
4. Each request is routed to the break glass approver(s) configured on that policy
5. Once a break glass request for an access policy is approved, individual session requests to instances covered by that policy are **auto-approved**. You still make the individual session requests, but they no longer require manual approval
6. If no access policies with `breakGlassApprover` match the user, the request is rejected with: *"No access policy allows break-glass access"*

{% hint style="warning" %}
Break glass access grants broad SSH access. Restrict the `breakGlassApprover` option to a small, trusted group of approvers, for example, a network operations center (NOC) staffed 24/7.
{% endhint %}

{% hint style="info" %}
Break glass doesn't provision access to all instances at once. Instead, it pre-approves later individual session requests. You request specific instances as needed, and those requests are automatically approved based on the break glass grant.
{% endhint %}

### Configuring break glass

Break glass requires **two** types of access policies working together:

1. A **regular rule** with a standard approver, this handles day-to-day access to individual instances
2. A **break glass rule** with a `breakGlassApprover`, this handles the emergency `all` access request

Both rules must exist. The regular rule defines which resources are accessible and who approves normal requests. The break glass rule defines who can approve the emergency override.

{% hint style="warning" %}
If an access policy uses `accessType: session` filters, break glass requests (`accessType: all`) do not match that policy. Ensure your break glass policy either omits `accessType` or explicitly sets `accessType: all`.
{% endhint %}

#### Example: complete break glass configuration

```yaml
# Rule 1: Regular SSH access to customer stack
# Day-to-day access, approved by engineering managers
- requestor:
    type: group
    id: engineers@yourco.com
    label: Engineers
    directory: workspace
  resource:
    type: integration
    service: ssh
    accessType: session
    filters:
      destination:
        effect: keep
        key: name
        pattern: ^customer-node
  approval:
    - type: group
      id: eng-managers@yourco.com
      label: Engineering Managers
      directory: workspace

# Rule 2: Break glass access to customer stack
# Emergency access, approved by NOC team
- requestor:
    type: group
    id: engineers@yourco.com
    label: Engineers
    directory: workspace
  resource:
    type: integration
    service: ssh
    accessType: all
  approval:
    - type: group
      id: noc-team@yourco.com
      label: NOC Team
      directory: workspace
      options: { breakGlassApprover: true }
```

In this example:

* **Rule 1** grants engineers access to specific instances matching `customer-node*`, approved by engineering managers
* **Rule 2** grants engineers break glass access to all SSH resources, approved by the NOC team (staffed 24/7)
* When an engineer runs `p0 request ssh all`, P0 creates a break glass request routed to the NOC team
* Once the NOC team approves, the engineer can request individual sessions (for example, `customer-node-1`) and those requests are auto-approved

### Break glass with PagerDuty escalation

You can combine break glass with PagerDuty escalation for incident-driven emergency access:

```yaml
- requestor:
    type: any
  resource:
    type: integration
    service: ssh
    accessType: all
  approval:
    - type: escalation
      integration: pagerduty
      options: { breakGlassApprover: true, requireReason: true }
      services: [PSJXXXG]
```

In this example:

* Any user can request break glass SSH access
* The request requires a reason
* PagerDuty routes the request through the configured escalation policy
* Only the on-call responder from the configured PagerDuty service can approve

### Multiple access policies and break glass

When you are a requestor in multiple access policies, a break glass request creates a **separate request for each matching policy**. Each request:

* Routes the request to the break glass approver configured on that specific rule
* Must be individually approved
* Grants auto-approval for instances covered by that rule only

This means you can have different break glass approvers for different sets of resources. For example, the NOC team approves break glass for production systems, while the SRE team approves break glass for staging systems.

### Separating break glass from regular approvals

P0 routes requests to the correct approvers based on whether `breakGlassApprover` is set:

* Regular SSH requests (`session`, `group`, `parent`) are routed to approvers on rules **without** `breakGlassApprover`
* Break glass SSH requests (`all`) are routed to approvers on rules **with** `breakGlassApprover: true`

This separation ensures that break glass approvals require explicit authorization from designated approvers, while regular access follows the standard approval flow.

### Supported approval types for break glass

The `breakGlassApprover` option is available on the following approval types:

| Approval type       | Description                                        |
| ------------------- | -------------------------------------------------- |
| `p0`                | P0 security reviewers                              |
| `group`             | Members of a directory group                       |
| `escalation`        | PagerDuty escalation                               |
| `requestor-profile` | The requestor's manager from the directory profile |

{% hint style="info" %}
The `auto`, `persistent`, and `deny` approval types do not support the `breakGlassApprover` option.
{% endhint %}


# Agentic access policies

When an AI agent requests access through the [P0 AI Gateway](/readme/agentic-control-plane), the request is evaluated against your access policies just like a human request, but you can match it with a dedicated **agentic** requestor type. An agentic rule matches on the *agent and user combination*: which agent is acting, and which human it is acting for.

Configure these rules in **Policy Studio**, alongside your other access policies. See [Access Policies](/access-management/just-in-time-access/access-policies) for the general policy format.

## The agentic requestor

```yaml
requestor:
  type: agentic
  agent: <agent rule>
  user: <user rule>
```

Both halves must match: the `agent` rule matches the agent identity bound to the session, and the `user` rule matches the human on whose behalf the agent acts.

{% hint style="info" %}
Agent-driven requests only match policies whose requestor type is `agentic` or `any`. Policies with a `user` or `group` requestor never match agent-driven requests, so your existing human-only rules don't accidentally apply to agents. Conversely, an `agentic` rule never matches a request a human makes directly.
{% endhint %}

### Agent rules

#### "Any agent"

```yaml
agent:
  type: any
```

Matches any agent. Use this for blanket rules such as "all agent requests require human approval."

#### "Gateway client"

```yaml
agent:
  type: agent-client
  clientId: <client id>
```

Matches an agent using a specific gateway client, identified by the `client_id` issued when the agent registered with P0.

{% hint style="info" %}
This rule type was previously named `mcp-client`. Existing policies that use `type: mcp-client` continue to work, but new policies use `type: agent-client`.
{% endhint %}

#### "User owned"

```yaml
agent:
  type: agent-owner
  owner: <owner's email address>
```

Matches any agent owned by a specific user.

#### "Group owned"

```yaml
agent:
  type: owner-group
  groups:
    type: group
    effect: keep|remove
    groups:
      - id: <group identifier>
        label: <human-readable name>
        directory: entra-id|okta|workspace
```

Matches agents whose owner is (`keep`) or isn't (`remove`) a member of one of the listed IdP groups. The group `id`, `label`, and `directory` fields work the same way as in [group requestor rules](/access-management/just-in-time-access/access-policies#group).

#### "Federated"

{% hint style="info" %}
The Federated agent rule is in preview.
{% endhint %}

```yaml
agent:
  type: provider
  providerId: <identity provider id>
  subjectPattern: <regular expression>   # optional
```

Matches agents that authenticate to the gateway by federating a token from an external identity provider, rather than registering as a P0 gateway client. Use this rule to define policy for federated agents by *provider* instead of per agent.

* `providerId` is the identifier of an identity provider enrolled with your P0 AI Gateway (for example, `okta` or `azure-ad`). The rule matches when the acting agent was federated by this provider.
* `subjectPattern` is an optional regular expression matched against the acting agent's token subject. Set it to narrow the match to specific agents; omit it to match any agent from the provider.

Federated agents act without a bound human user, so a Federated rule always pairs with `user: { type: none }`. In Policy Studio, selecting the **Federated** agent type fixes the **User** selector to **No user**.

### User rules

The `user` half accepts the same rule types as a top-level requestor:

* `type: any` matches any user
* `type: user` with `uid: <email>` matches a specific user
* `type: group` with `id`, `label`, and `directory` matches members of an IdP group

See [Access Policies](/access-management/just-in-time-access/access-policies#requestor) for field definitions.

## Headless agents

Autonomous agents that don't act on a specific person's behalf (for example, a scheduled reporting job or a CI pipeline) make requests without a bound user identity. Their requests appear in P0 attributed to the agent itself ("Agent *\<client id>* requests…").

Constrain the **agent** side to match these sessions: use `agent-client`, `agent-owner`, `owner-group`, or `provider` and pair it with `user: { type: any }`. The `provider` rule instead pairs with `user: { type: none }`, which matches only sessions that have no human user. A `user` or `group` user rule never matches a headless session, since there is no human identity to match.

Headless agents run unattended, so their policies typically pair [automatic approval](/access-management/just-in-time-access/access-policies#always-allowed) with a narrowly scoped resource: the agent gets exactly the access its job needs, without waking a human. For example, auto-approve a nightly reporting agent's AWS requests, but only for a single reports bucket:

```yaml
- requestor:
    type: agentic
    agent:
      type: agent-client
      clientId: nightly-reporting-agent
    user:
      type: any
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      resource:
        effect: keep
        key: arn
        pattern: ^arn:aws:s3:::nightly-reports$
  approval:
    - type: persistent
```

Any request the agent makes outside this scope (a different bucket, a different service) doesn't match this rule and falls through to your other policies.

{% hint style="warning" %}
`user: { type: any }` matches both headless and user-driven sessions. To govern a headless agent distinctly, give it its own gateway client registration and match on its `clientId`.
{% endhint %}

## Resource and approval

The `resource` and `approval` parts of an agentic policy stay the same. The resource is the underlying service the agent is requesting (for example, `aws` when an agent requests AWS access through the [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws)). The same [resource filters](/access-management/just-in-time-access/access-policies#resource) you use for human requests apply to agent requests too.

Any [approval rule](/access-management/just-in-time-access/access-policies#approval) can be attached: route agent requests to human approvers (`p0`, `group`), deny them outright (`deny`), or approve them automatically based on on-call status (`auto`, `escalation`).

## Examples

Require Security Reviewer approval for everything any agent requests:

```yaml
- requestor:
    type: agentic
    agent:
      type: any
    user:
      type: any
  resource:
    type: any
  approval:
    - type: p0
```

Block a specific gateway client from requesting any AWS access:

```yaml
- requestor:
    type: agentic
    agent:
      type: agent-client
      clientId: my-ci-agent
    user:
      type: any
  resource:
    type: integration
    service: aws
  approval:
    - type: deny
```

Let engineers' agents (provided the Platform team owns the agent) request S3 access, with approval routed to Platform:

```yaml
- requestor:
    type: agentic
    agent:
      type: owner-group
      groups:
        type: group
        effect: keep
        groups:
          - id: platform@yourorg.com
            label: Platform
            directory: workspace
    user:
      type: group
      id: engineering@yourorg.com
      label: Engineering
      directory: workspace
  resource:
    type: integration
    service: aws
    filters:
      resource:
        effect: keep
        key: service
        pattern: ^s3$
  approval:
    - type: group
      id: platform@yourorg.com
      label: Platform
      directory: workspace
```

Auto-approve AWS access for agents federated by Okta whose subject starts with `ci-`, and only for a single S3 bucket:

```yaml
- requestor:
    type: agentic
    agent:
      type: provider
      providerId: okta
      subjectPattern: ^ci-
    user:
      type: none
  resource:
    type: integration
    service: aws
    accessType: resource
    filters:
      resource:
        effect: keep
        key: arn
        pattern: ^arn:aws:s3:::ci-artifacts$
  approval:
    - type: persistent
```

## Related documentation

* [Access Policies](/access-management/just-in-time-access/access-policies): the general policy format, resource filters, and approval options
* [Requesting access through the Agentic Gateway](/integrations/resource-integrations/agentic-gateway/requesting-access): how agents submit and use the requests these policies govern


# Access bundles

Group several resource accesses into a single requestable Access Bundle in P0 Security, so users request everything they need for a task in one step.

An Access Bundle groups multiple resource accesses into a single requestable item. Instead of filing separate requests for an AWS role, a Google Cloud role, and a Kubernetes namespace, a user requests one bundle and P0 provisions every resource in it after approval.

Bundles are useful when a task consistently requires the same set of permissions across integrations, for example onboarding an engineer to a service or granting a standard "incident responder" access set.

{% hint style="info" %}
Access Bundles are in **beta**. The feature and its interface may change.
{% endhint %}

## How Access Bundles work

1. An **Owner** creates a bundle in Policy Studio and adds 2-15 resources from your installed integrations.
2. A user **requests** the bundle from the web app or Slack.
3. P0 **routes** the request to the approvers configured on the bundle.
4. After approval, P0 **provisions** every resource in the bundle through its own integration.
5. Access **expires** after the requested duration, the same as any other just-in-time request.

Each item in a bundle keeps the access behavior of its underlying integration. A bundle is a way to request several accesses together, not a new kind of grant.

## Prerequisites

Before you begin, confirm the following:

* The **Owner** role in your P0 organization (required to create and manage bundles).
* At least two [resource integrations](/integrations/resource-integrations) installed and configured, so the bundle has resources to include.

## Create an Access Bundle

### Open the Access Bundles view

1. Sign in to [p0.app](https://p0.app).
2. Navigate to **Policy Studio** in the sidebar.
3. Select **Access Bundles** in the category control at the top of the page.

<figure><img src="/files/CyEB7Fwof9G1I0VLfKTe" alt="" width="563"><figcaption></figcaption></figure>

You see a list of existing bundles. The list is empty the first time you open it.

### Start a new bundle

1. Click **Create Bundle**.

   The bundle editor opens with three sections: **Identity**, **Resource(s)**, and **Actions**. Configure each one in order.
2. Enter a name for the bundle in the name field at the top (for example, "Incident responder").

<figure><img src="/files/s2xeGYvfWaSeb1jJauJI" alt="" width="435"><figcaption></figcaption></figure>

{% hint style="info" %}
Access Bundles are edited only in the visual editor. Unlike access policies, they can't be edited as YAML.
{% endhint %}

### Define who can request the bundle

The **Identity** section specifies which users can request this bundle.

<figure><img src="/files/1CNDDiamWn9pZYzYReSY" alt="" width="388"><figcaption></figcaption></figure>

1. In the **Identity** section, select one of the following:
   * **Any user**: Any user in your organization can request the bundle.
   * **Specific users**: A specific user or directory group can request the bundle. Then, in the **Match by** row, select **Specific user** or **Directory group**.
2. If you selected **Specific users**, choose a specific user's email address or a directory group.

### Add resources to the bundle

The **Resource(s)** section defines what the bundle grants. A bundle must contain at least 2 and at most 15 resources.

<figure><img src="/files/aTivjciSYTLB16SsF1cL" alt=""><figcaption></figcaption></figure>

1. Click **Add to Access Bundle**.
2. Select the integration for the resource (for example, **AWS**, **Google Cloud**, or **Kubernetes**).
3. Select the access type and the specific resource for that integration.
4. Repeat for each resource you want to include. Resources are grouped by integration in the editor.

{% hint style="info" %}
If you add the same resource and access type twice, the editor flags the duplicate and excludes it from the saved bundle.
{% endhint %}

### Define how requests are approved

<figure><img src="/files/IAeG675Zag8QKLDt9Ylw" alt="" width="240"><figcaption></figcaption></figure>

The **Actions** section specifies how P0 handles requests for the bundle. This works the same way as approvals on an [access policy](/access-management/just-in-time-access/access-policies/configure-your-first-access-policy#define-the-actions-how-requests-are-approved).

1. Select an approval type (for example, **P0 Security Reviewers**, **Group approval**, or **Auto-approve (on-call)**).
2. Configure approval options as needed, such as **Require reason with request**.

If you don't configure an approval, P0 routes bundle requests to your configured Security Reviewers by default.

### Save the bundle

1. Review the bundle across all three sections.
2. Click **Create Access Bundle**.

The bundle appears in the Access Bundles list with the **Enabled** toggle turned on. It is now available for users who match the identity you configured.

## Request an Access Bundle

Users request a bundle the same way they request any other resource, by selecting **Access Bundle** as the resource type. Bundles can be requested from the web app and from Slack.

### Request from the web app

<figure><img src="/files/ZMQ6X9hPY04gJOTHBWI0" alt="" width="388"><figcaption></figcaption></figure>

1. Open the [P0 app](https://p0.app) and navigate to **Access Management**.
2. Click **Request Access**.
3. Select **Access Bundle** as the resource, then select the specific bundle.
4. Set the duration and, optionally, a reason.
5. Submit the request for approval.

### Request from Slack

If your organization has installed the [Slack integration](/integrations/notifier-integrations/slack), type `/p0 request` and select **Access Bundle** in the request modal, then choose the bundle to request.

For more ways to request access, see [Requesting Access](/access-management/just-in-time-access/requesting-access).

## Manage bundles

From the **Access Bundles** list, an Owner can:

* **Edit a bundle**: Click a bundle row to open the editor and change its identity, resources, or approval.
* **Disable a bundle**: Turn off the **Enabled** toggle. Disabled bundles aren't available for requests.
* **Delete a bundle**: Click the delete icon on the bundle row. This action can't be undone.

## Related

* [Configure your first access policy](/access-management/just-in-time-access/access-policies/configure-your-first-access-policy): Control who can request individual resources and how those requests are approved.
* [Requesting Access](/access-management/just-in-time-access/requesting-access): All the ways users can request access in P0.
* [Approving Access](/access-management/just-in-time-access/approving-access): How approvers review and manage requests.


# Session recording

Session Recording in P0 allows you to view detailed activity that occurs within a privileged session.

You can see what actions users took, when they took them, who took them, and from where, empowering you to answer questions like:

* *What did a user do after gaining access?*
* *Were any sensitive operations performed?*
* *Did anything unexpected happen during a session?*

{% hint style="info" %}
Access logging is not generally available at the moment.

Please contact P0 support to learn more about this feature.
{% endhint %}

{% content-ref url="/pages/VVT1O9x1aG5hisaTgRIz" %}
[AWS](/access-management/just-in-time-access/session-recording/aws)
{% endcontent-ref %}


# AWS

View CloudTrail activity taken within a privileged session.

### **Step 1: Create a CloudTrail and CloudLake Data Store**

> Note: AWS CloudTrail is enabled by default for all AWS accounts, capturing management events in the Event history. However, to [retain logs beyond 90 days](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events.html#event-history-limitations) and to integrate with services like P0, you need to create a trail that delivers queryable logs to an Amazon S3 bucket.

<figure><img src="/files/sT0VRtbMy0NyFTUd9KT0" alt=""><figcaption><p>Enabling a CloudTrail trail allows more flexibility than the default logging settings.</p></figcaption></figure>

<figure><img src="/files/VaQ5GbGQpgoUJtgG6vfm" alt=""><figcaption><p>CloudTrail Lake allows you to run SQL-based queries on events.</p></figcaption></figure>

1. **Sign in** to the AWS Management Console and open the [CloudTrail console](https://console.aws.amazon.com/cloudtrail/).
2. Refer to [AWS's official documentation](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html) for up-to-date instructions on creating a trail and the accompanying CloudLake Data Store.
3. After creation, **copy the ARN** of the event data store for integration with P0.

### **Step 2: Integrate CloudTrail with P0** <a href="#id-6e974fe1-e164-4738-b8c2-a8c14de22e9b" id="id-6e974fe1-e164-4738-b8c2-a8c14de22e9b"></a>

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2F8jp4v6x8B1cMMNd5050e%2Fsession-recording-install-demo.mp4?alt=media&token=22f54e0f-c6b4-4081-ba2d-fcdaf5855263>" %}
Add AWS Access Logging Installation
{% endembed %}

#### **A. Adding a New AWS Account in P0** <a href="#f48495ec-2a34-482f-ada8-ca929eb8ba0a" id="f48495ec-2a34-482f-ada8-ca929eb8ba0a"></a>

1. In P0, navigate to **Integrations** > **AWS** > **Access Logging**.
2. Click **Add account**.
3. Select your AWS account identifier (Account must have previously been set up with IAM Management).
4. Paste the **CloudTrail Lake Event Data Store ARN** copied earlier.
5. Click **Next**. P0 will provide setup instructions, including Shell and Terraform options.

#### **B. Apply the IAM Role Policy** <a href="#id-7583ea12-5b5a-4904-92ff-645495f0625b" id="id-7583ea12-5b5a-4904-92ff-645495f0625b"></a>

1. Use the AWS CLI or CloudShell to run the `aws iam put-role-policy` command provided by P0.
   * This command grants the P0 Service Account permissions to query the specified CloudTrail Lake event data store.
   * This feature requires `StartQuery` and `GetQueryResults` within CloudLake, in addition to `GetRole` and `GetRolePolicy` from IAM.

### **Step 3: Viewing Sessions in P0** <a href="#a443529c-e368-488e-99db-02764834784f" id="a443529c-e368-488e-99db-02764834784f"></a>

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2F2Vz9DiceJTYVcMZPjIir%2Fsession-recording-search-demo.mp4?alt=media&token=02edbc17-ce53-44ea-b179-ab404fe56e4f>" %}
Search Logs
{% endembed %}

{% embed url="<https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FSQNwGQz62W737pY0FzVb%2Fuploads%2FYWZ4F4pVKm4pxss2Jhjv%2Fsession-recording-usage-demo.mp4?alt=media&token=14811433-97c2-4b71-9143-722a9c69c1ab>" %}
Export Logs
{% endembed %}

After integrating CloudTrail with P0:

1. **Access Requests**: When a user requests access (e.g., to assume a role), and it’s approved, the session is initiated.
2. **Session History**:
   * Navigate to Access Management > Activity or Access Management > History in P0.
   * Click ‘view’ for specified request
   * Click on ‘View Session History’
   * Here, you can see:
     * **Event names**: e.g., `GetCallerIdentity`, `DescribeAccessEntry`.
     * **Source**: e.g., `sts.amazonaws.com`, `eks.amazonaws.com`.
     * **User Identity**: Who performed the action.
     * **User Agent**: The tool or service used.
     * **Event Timestamps**: When the action occurred.
     * **Error Indicators**: Toggle off to only show successful events.
3. **Raw Logs**:
   * Click on individual entries to view raw CloudTrail logs.
   * Details include IP addresses, request IDs, session context, etc.
4. **Search:**
   * Search by event name, source, user agent, error, IP address, or resource ARN.
5. **Export:**
   * Export as either CSV or JSON
6. **Pagination:**
   * Adjust the number of rows requested (10/20/50/100).
   * Navigate through pages to view older logs.

> **Note**: CloudTrail logs may take up to **5 minutes** to appear after an event occurs.

### **Additional Considerations** <a href="#f00739ea-41b0-4da7-8bb8-0d20710ed35a" id="f00739ea-41b0-4da7-8bb8-0d20710ed35a"></a>

* **Data Events**: Enable only if necessary, as they can significantly increase log volume and costs.
* **Multi-Region Trails**: Recommended to capture events across all regions.


# Just-in-time API

P0’s Just-In-Time APIs enable secure, automated access provisioning with fine-grained control. These APIs are purpose-built for modern DevOps and security teams who need to grant ephemeral access based on intent, not static policies.

**Key API Groups:**

* [**Command API**](/access-management/just-in-time-access/just-in-time-api/command-api): Submit actionable commands that trigger access workflows, such as requesting temporary AWS roles or starting pre-approved tasks. Ideal for CLI or bot-driven automation.
* [**Access Requests API**](/access-management/just-in-time-access/just-in-time-api/access-requests-api): Programmatically approve, deny, or revoke access requests with full context. These APIs allow security teams and automated systems to control access lifecycles in real time.
* [**Access Policies API**](/access-management/just-in-time-access/just-in-time-api/access-policies-api): Define dynamic access policies that map incoming requests to specific approval paths. This enables custom access control based on user, group, environment, or resource context.

All endpoints are protected with bearer tokens and designed for high-trust, auditable interactions.

For a step-by-step walkthrough of creating, checking, approving, and revoking a request in code, see [Automate access requests with the API](/getting-started/automate-access-requests-with-the-api). For a map of all P0 APIs and the authentication they share, see the [P0 API overview](/getting-started/p0-api-overview).

## JIT Settings in P0 Management

P0 Management provides a user-friendly interface to configure key Just-in-time (JIT) access settings: **custom expiry**, **approvable duration**, **max access duration**, and **persistent access duration**. These settings define the lifetime and conditions of temporary access for users. Admins can configure these parameters directly within the app, allowing for granular control over access privileges in line with least-privilege principles and audit requirements. By tailoring these durations in the UI, organizations can automate and standardize just-in-time access workflows without leaving the P0 platform.

### Impact on User Access and Permissions

When set on the P0 Management page, these settings directly influence how users request and receive access within the app:

* **Custom Expiry**
  * Admins can define multiple custom expiry presets (e.g., 30 minutes, 2 hours, 1 day).
  * When users request access, these presets appear as selectable options in the access request UI, allowing requestors or approvers to choose the most appropriate duration for the use case.
  * This streamlines access requests, promotes consistency, and reduces the risk of over-permissioning.
* **Approvable Duration**
  * Defines the window of time within which an access request must be approved. Requests that remain pending beyond this duration are automatically timed out.
  * Admins can configure this setting directly from the P0 Management page. The default is 14 days.
  * This prevents stale requests from lingering indefinitely, ensuring that unapproved requests are cleaned up automatically.
* **Max Access Duration**
  * Sets the upper limit for how long any single access grant can last, regardless of the preset or custom request.
  * The UI enforces this limit, preventing users or approvers from granting access that exceeds the configured maximum.
  * This ensures no temporary access persists longer than governance policies allow.
* **Persistent Access Duration**
  * Defines the standard (default) duration for recurring or standing access (such as scheduled access windows).
  * Admins can configure these durations directly from the P0 Management page, supporting dynamic permissioning and minimizing standing privileges.
  * The UI will apply this default when users or automation processes request standing access, reducing manual errors.

<figure><img src="/files/PxAYIDOdluvMhOqXFwmd" alt="P0 Management Just-in-time settings page showing expiry duration options, maximum access duration, and standing access duration configuration" width="560"><figcaption></figcaption></figure>

These controls allow admins to tailor access durations directly in the app, streamlining workflows and supporting least-privilege principles. For more details, see the [JIT Settings API](/p0-management/management-api/just-in-time-settings-api).

You can also configure these settings as code with the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs) (version `0.50.0` or later):

* Use [`p0_access_durations`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/access_durations) to set the approvable, max access, and standing access durations.
* Use [`p0_expiry_options`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/expiry_options) to define the custom expiry presets requestors can select.


# Command API

Enable external systems to programmatically initiate access requests, enabling automation and integration with internal tools, bots, and security workflows.

The Command API enables programmatic creation of access requests within the P0 platform. It is designed to support custom workflows and automation. Integrate with external systems and services to initiate access grants without using the P0 user interface.

This is particularly useful for integrating P0 into your internal tooling, bots, or security workflows that require automatic access escalation based on alerts, CI/CD pipelines, or external approvals.

{% file src="/files/0GQJ2jd7mJ0WPKlWLehf" %}

## Create an access request

> Runs a P0 command for the authenticated identity. The request runs through your organization's access policies, guardrails, and audit trail exactly as a request made in the P0 dashboard or CLI does.

```json
{"openapi":"3.0.4","info":{"title":"P0 Command API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"parameters":{"OrgId":{"name":"orgId","in":"path","required":true,"description":"The P0 organization slug, the same value that appears in console URLs at p0.app/o/{orgId}.","schema":{"type":"string"}}},"schemas":{"CommandRequest":{"type":"object","required":["argv","scriptName"],"properties":{"argv":{"type":"array","items":{"type":"string"},"description":"The command arguments, matching the `p0 request` CLI command with the leading `p0` removed. Each flag and its value are separate array elements, for example `[\"request\", \"aws\", \"role\", \"MyReadOnlyRole\", \"--account\", \"123456789012\", \"--reason\", \"...\"]`."},"scriptName":{"type":"string","description":"The client name to record for the invocation. Use `p0`."},"wait":{"type":"boolean","description":"When `true`, the response streams newline-delimited JSON status updates over the open connection until the request is approved, denied, or errors, instead of returning once the request is created. For one-shot status checks in automation, prefer polling `GET /o/{orgId}/permission-requests/{requestId}` from the Access Requests API."}}},"RequestCreated":{"type":"object","properties":{"ok":{"type":"boolean"},"id":{"type":"string","description":"The ID of the created access request. Use it with the Access Requests API to check status, approve, deny, or revoke."},"message":{"type":"string","description":"A human-readable confirmation, including a link to the request details page."},"isPreexisting":{"type":"boolean","description":"Whether an equivalent request or grant already existed."},"request":{"type":"object","description":"The created access request document."}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"A human-readable description of the failure."}}}},"responses":{"BadRequestError":{"description":"The request body is invalid, for example `argv` is missing or is not an array of strings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/o/{orgId}/command":{"post":{"summary":"Create an access request","description":"Runs a P0 command for the authenticated identity. The request runs through your organization's access policies, guardrails, and audit trail exactly as a request made in the P0 dashboard or CLI does.","parameters":[{"$ref":"#/components/parameters/OrgId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommandRequest"}}}},"responses":{"200":{"description":"The command ran. For `request` commands the body confirms the created access request and returns its ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCreated"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"408":{"description":"The command did not complete before the server's timeout."}}}}}}
```


# Access requests API

Enable programmatic approval, denial, and revocation of access, enabling seamless integration with internal tools, bots, and security workflows for automated access escalation.

The Access Request API enables programmatic approval, denial, and revocation of access requests within the P0 platform. It is designed to support custom workflows and automation. Integrate with external systems and services to process access grants without using the P0 user interface.

This is particularly useful for integrating P0 into your internal tooling, bots, or security workflows that require automatic access escalation based on alerts, CI/CD pipelines, or external approvals.

{% file src="/files/rSAVbzaUZ2nSPwiFXWkD" %}

## Approve an access request

> Approves a pending access request, after which P0 provisions the access. The optional body overrides the grant duration.

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Requests API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"parameters":{"OrgId":{"name":"orgId","in":"path","required":true,"description":"The P0 organization slug, the same value that appears in console URLs at p0.app/o/{orgId}.","schema":{"type":"string"}},"RequestId":{"name":"requestId","in":"path","required":true,"description":"The ID of the access request, as returned by the Command API when the request was created.","schema":{"type":"string"}}},"schemas":{"ActionSuccess":{"type":"object","properties":{"message":{"type":"string"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"A human-readable description of the failure."}}}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller is not allowed to perform this action on the request, for example the identity is not an approver under the matching access policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundError":{"description":"No access request exists with this ID in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/o/{orgId}/permission-requests/{requestId}/approve":{"post":{"summary":"Approve an access request","description":"Approves a pending access request, after which P0 provisions the access. The optional body overrides the grant duration.","parameters":[{"$ref":"#/components/parameters/OrgId"},{"$ref":"#/components/parameters/RequestId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"expirationLength":{"type":"string","description":"A P0 duration to grant instead of the requested one, for example `30m`, `2h`, or `1d`."},"isCustomExpiry":{"type":"boolean","description":"Set to `true` when `expirationLength` is not one of the organization's preset expiry options."}}}}}},"responses":{"200":{"description":"The request was approved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionSuccess"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"}}}}}}
```

## Deny an access request

> Denies a pending access request. Denial cannot be undone — the requestor must submit a new request.

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Requests API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"parameters":{"OrgId":{"name":"orgId","in":"path","required":true,"description":"The P0 organization slug, the same value that appears in console URLs at p0.app/o/{orgId}.","schema":{"type":"string"}},"RequestId":{"name":"requestId","in":"path","required":true,"description":"The ID of the access request, as returned by the Command API when the request was created.","schema":{"type":"string"}}},"schemas":{"ActionSuccess":{"type":"object","properties":{"message":{"type":"string"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"A human-readable description of the failure."}}}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller is not allowed to perform this action on the request, for example the identity is not an approver under the matching access policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundError":{"description":"No access request exists with this ID in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/o/{orgId}/permission-requests/{requestId}/deny":{"post":{"summary":"Deny an access request","description":"Denies a pending access request. Denial cannot be undone — the requestor must submit a new request.","parameters":[{"$ref":"#/components/parameters/OrgId"},{"$ref":"#/components/parameters/RequestId"}],"responses":{"200":{"description":"The request was denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionSuccess"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"}}}}}}
```

## Revoke an access grant

> Revokes an active grant before it expires. P0 automatically revokes access at expiry, so this is only needed to end a grant early. Revocation cannot be undone — the requestor must submit a new request to restore access.

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Requests API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"parameters":{"OrgId":{"name":"orgId","in":"path","required":true,"description":"The P0 organization slug, the same value that appears in console URLs at p0.app/o/{orgId}.","schema":{"type":"string"}},"RequestId":{"name":"requestId","in":"path","required":true,"description":"The ID of the access request, as returned by the Command API when the request was created.","schema":{"type":"string"}}},"schemas":{"ActionSuccess":{"type":"object","properties":{"message":{"type":"string"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"A human-readable description of the failure."}}}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller is not allowed to perform this action on the request, for example the identity is not an approver under the matching access policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundError":{"description":"No access request exists with this ID in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/o/{orgId}/permission-requests/{requestId}/revoke":{"post":{"summary":"Revoke an access grant","description":"Revokes an active grant before it expires. P0 automatically revokes access at expiry, so this is only needed to end a grant early. Revocation cannot be undone — the requestor must submit a new request to restore access.","parameters":[{"$ref":"#/components/parameters/OrgId"},{"$ref":"#/components/parameters/RequestId"}],"responses":{"200":{"description":"The grant was revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionSuccess"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"}}}}}}
```


# 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 the [Access Policies](/access-management/just-in-time-access/access-policies) guide.

## 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](/getting-started/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:

```bash
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](/getting-started/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](#replace-the-whole-configuration).

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`](#read-the-active-configuration).

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

> 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\`).

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"schemas":{"PolicyConfigList":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/PolicyConfigWithMeta"}}}},"PolicyConfigWithMeta":{"allOf":[{"$ref":"#/components/schemas/PolicyConfig"},{"type":"object","description":"A stored policy configuration document, with its metadata.","properties":{"id":{"type":"string","description":"The document ID. For a version document this is its auto-generated version ID; for the active-configuration pointer it is the literal `latest`."},"version":{"type":"string","description":"The active configuration's version ID. Present when you read `latest`; use it as `currentVersion` when replacing the whole configuration."},"createdDate":{"type":"string","format":"date-time","description":"When this configuration document was last written."}}}]},"PolicyConfig":{"type":"object","description":"A complete policy configuration — the full set of rules that is active at once.","required":["rules"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Policy"}}}},"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}},"responses":{"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."}}},"paths":{"/policy":{"get":{"summary":"List stored policy configurations","description":"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`).","responses":{"200":{"description":"The documents in the organization's policy store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyConfigList"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"}}}}}}
```

## Replace the policy configuration

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

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"schemas":{"PolicyConfigUpdate":{"type":"object","description":"A request to replace the active policy configuration.","required":["currentVersion","policy"],"properties":{"currentVersion":{"type":"string","description":"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."},"policy":{"$ref":"#/components/schemas/PolicyConfig"}}},"PolicyConfig":{"type":"object","description":"A complete policy configuration — the full set of rules that is active at once.","required":["rules"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Policy"}}}},"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"PolicyConfigVersionResponse":{"type":"object","properties":{"policy":{"$ref":"#/components/schemas/PolicyConfigVersion"}}},"PolicyConfigVersion":{"allOf":[{"$ref":"#/components/schemas/PolicyConfig"},{"type":"object","properties":{"version":{"type":"string","description":"The ID of the newly created configuration version."}}}]}},"responses":{"BadRequestError":{"description":"The request body failed validation — for example, a `name` that contains a slash, a `name` that does not match the URL, an unknown `service`, a filter with a non-string `pattern` or an invalid `key`, or any unknown field (policy objects reject unknown properties). The response body describes the problem."},"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"ConflictError":{"description":"The change conflicts with the current state — for example, a rule with the given name already exists, more than one rule matches the given name, or the supplied `currentVersion` no longer matches the active configuration."},"UnprocessableEntityError":{"description":"A filter `pattern` (or an agentic `subjectPattern`) is not a valid regular expression, or is unsafe because it is subject to catastrophic backtracking."}}},"paths":{"/policy":{"post":{"summary":"Replace the policy configuration","description":"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.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyConfigUpdate"}}}},"responses":{"200":{"description":"The new policy configuration, including its version ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyConfigVersionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"$ref":"#/components/responses/ConflictError"},"422":{"$ref":"#/components/responses/UnprocessableEntityError"}}}}}}
```

## Get a policy configuration

> 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\`.

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"parameters":{"policyId":{"name":"policyId","in":"path","required":true,"description":"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.","schema":{"type":"string"}}},"schemas":{"PolicyConfigResponse":{"type":"object","properties":{"policy":{"$ref":"#/components/schemas/PolicyConfigWithMeta"}}},"PolicyConfigWithMeta":{"allOf":[{"$ref":"#/components/schemas/PolicyConfig"},{"type":"object","description":"A stored policy configuration document, with its metadata.","properties":{"id":{"type":"string","description":"The document ID. For a version document this is its auto-generated version ID; for the active-configuration pointer it is the literal `latest`."},"version":{"type":"string","description":"The active configuration's version ID. Present when you read `latest`; use it as `currentVersion` when replacing the whole configuration."},"createdDate":{"type":"string","format":"date-time","description":"When this configuration document was last written."}}}]},"PolicyConfig":{"type":"object","description":"A complete policy configuration — the full set of rules that is active at once.","required":["rules"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Policy"}}}},"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}},"responses":{"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"NotFoundError":{"description":"No policy matches the given name or version ID."}}},"paths":{"/policy/{policyId}":{"get":{"summary":"Get a policy configuration","description":"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`.","parameters":[{"$ref":"#/components/parameters/policyId"}],"responses":{"200":{"description":"The requested policy configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyConfigResponse"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"}}}}}}
```

## Get a policy rule

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

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"parameters":{"name":{"name":"name","in":"path","required":true,"description":"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.","schema":{"type":"string"}}},"schemas":{"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}},"responses":{"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"NotFoundError":{"description":"No policy matches the given name or version ID."},"ConflictError":{"description":"The change conflicts with the current state — for example, a rule with the given name already exists, more than one rule matches the given name, or the supplied `currentVersion` no longer matches the active configuration."}}},"paths":{"/policy/name/{name}":{"get":{"summary":"Get a policy rule","description":"Returns a single policy rule from the active configuration by its name.","parameters":[{"$ref":"#/components/parameters/name"}],"responses":{"200":{"description":"The requested policy rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Policy"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"},"409":{"$ref":"#/components/responses/ConflictError"}}}}}}
```

## Create a policy rule

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

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"parameters":{"name":{"name":"name","in":"path","required":true,"description":"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.","schema":{"type":"string"}}},"schemas":{"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}},"responses":{"BadRequestError":{"description":"The request body failed validation — for example, a `name` that contains a slash, a `name` that does not match the URL, an unknown `service`, a filter with a non-string `pattern` or an invalid `key`, or any unknown field (policy objects reject unknown properties). The response body describes the problem."},"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"ConflictError":{"description":"The change conflicts with the current state — for example, a rule with the given name already exists, more than one rule matches the given name, or the supplied `currentVersion` no longer matches the active configuration."},"UnprocessableEntityError":{"description":"A filter `pattern` (or an agentic `subjectPattern`) is not a valid regular expression, or is unsafe because it is subject to catastrophic backtracking."}}},"paths":{"/policy/name/{name}":{"post":{"summary":"Create a policy rule","description":"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.","parameters":[{"$ref":"#/components/parameters/name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Policy"}}}},"responses":{"201":{"description":"The created policy rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Policy"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"$ref":"#/components/responses/ConflictError"},"422":{"$ref":"#/components/responses/UnprocessableEntityError"}}}}}}
```

## Update a policy rule

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

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"parameters":{"name":{"name":"name","in":"path","required":true,"description":"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.","schema":{"type":"string"}}},"schemas":{"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}},"responses":{"BadRequestError":{"description":"The request body failed validation — for example, a `name` that contains a slash, a `name` that does not match the URL, an unknown `service`, a filter with a non-string `pattern` or an invalid `key`, or any unknown field (policy objects reject unknown properties). The response body describes the problem."},"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"NotFoundError":{"description":"No policy matches the given name or version ID."},"ConflictError":{"description":"The change conflicts with the current state — for example, a rule with the given name already exists, more than one rule matches the given name, or the supplied `currentVersion` no longer matches the active configuration."},"UnprocessableEntityError":{"description":"A filter `pattern` (or an agentic `subjectPattern`) is not a valid regular expression, or is unsafe because it is subject to catastrophic backtracking."}}},"paths":{"/policy/name/{name}":{"put":{"summary":"Update a policy rule","description":"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.","parameters":[{"$ref":"#/components/parameters/name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Policy"}}}},"responses":{"200":{"description":"The updated policy rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Policy"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"},"409":{"$ref":"#/components/responses/ConflictError"},"422":{"$ref":"#/components/responses/UnprocessableEntityError"}}}}}}
```

## Delete a policy rule

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

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}","variables":{"orgId":{"default":"demo-org","description":"The organization ID (your organization slug)."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Token"}},"parameters":{"name":{"name":"name","in":"path","required":true,"description":"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.","schema":{"type":"string"}}},"responses":{"UnauthorizedError":{"description":"The token is missing or invalid, or it is valid but belongs to an identity that is not a member of your P0 organization, so it holds no P0 role. Add the identity — for example, a service account's email — as a member in Role-Based Access Control."},"ForbiddenError":{"description":"The token is valid and the identity is a member of your P0 organization, but it lacks the required permission — for example, a Manager calling a write endpoint, which requires the Owner role."},"NotFoundError":{"description":"No policy matches the given name or version ID."},"ConflictError":{"description":"The change conflicts with the current state — for example, a rule with the given name already exists, more than one rule matches the given name, or the supplied `currentVersion` no longer matches the active configuration."}}},"paths":{"/policy/name/{name}":{"delete":{"summary":"Delete a policy rule","description":"Removes a single named rule from the active configuration and creates a new configuration version.","parameters":[{"$ref":"#/components/parameters/name"}],"responses":{"204":{"description":"The rule was deleted."},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"},"409":{"$ref":"#/components/responses/ConflictError"}}}}}}
```

## Policy schema

## The Policy object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}}}}
```

## The PolicyConfig object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"PolicyConfig":{"type":"object","description":"A complete policy configuration — the full set of rules that is active at once.","required":["rules"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Policy"}}}},"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}}}}
```

## The PolicyConfigWithMeta object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"PolicyConfigWithMeta":{"allOf":[{"$ref":"#/components/schemas/PolicyConfig"},{"type":"object","description":"A stored policy configuration document, with its metadata.","properties":{"id":{"type":"string","description":"The document ID. For a version document this is its auto-generated version ID; for the active-configuration pointer it is the literal `latest`."},"version":{"type":"string","description":"The active configuration's version ID. Present when you read `latest`; use it as `currentVersion` when replacing the whole configuration."},"createdDate":{"type":"string","format":"date-time","description":"When this configuration document was last written."}}}]},"PolicyConfig":{"type":"object","description":"A complete policy configuration — the full set of rules that is active at once.","required":["rules"],"properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Policy"}}}},"Policy":{"type":"object","description":"A single access-control policy rule.","required":["requestor","resource","approval"],"properties":{"name":{"type":"string","description":"A unique, human-readable name for the rule. Cannot contain `/` or `\\`."},"disabled":{"type":"boolean","description":"If true, P0 skips this rule during evaluation. Defaults to false."},"requestor":{"$ref":"#/components/schemas/RequestorRule"},"resource":{"$ref":"#/components/schemas/ResourceRule"},"approval":{"type":"array","items":{"$ref":"#/components/schemas/ApprovalRule"}}}},"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}},"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]},"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}}}}
```

## The RequestorRule object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"RequestorRule":{"description":"Matches who is making the request.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"$ref":"#/components/schemas/AgenticRequestorRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"UserRequestorRule":{"type":"object","description":"Matches a single user by email address.","required":["type","uid"],"properties":{"type":{"type":"string","enum":["user"]},"uid":{"type":"string","description":"The user's email address."}}},"AgenticRequestorRule":{"type":"object","description":"Matches requests made by AI agents through the P0 AI Gateway, based on the acting agent and the human user on whose behalf the agent acts.","required":["type","agent","user"],"properties":{"type":{"type":"string","enum":["agentic"]},"agent":{"description":"The agent half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"type":"object","required":["type","clientId"],"properties":{"type":{"type":"string","enum":["agent-client"]},"clientId":{"type":"string"}}},{"type":"object","required":["type","providerId"],"properties":{"type":{"type":"string","enum":["provider"]},"providerId":{"type":"string"},"subjectPattern":{"type":"string","description":"An optional regular expression matched against the federated subject."}}},{"type":"object","required":["type","owner"],"properties":{"type":{"type":"string","enum":["agent-owner"]},"owner":{"type":"string"}}},{"type":"object","required":["type","groups"],"properties":{"type":{"type":"string","enum":["owner-group"]},"groups":{"$ref":"#/components/schemas/GroupRule"}}}]},"user":{"description":"The human-user half of the match.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/GroupRule"},{"$ref":"#/components/schemas/UserRequestorRule"},{"type":"object","description":"Matches an agent-only session with no human user.","required":["type"],"properties":{"type":{"type":"string","enum":["none"]}}}]}}}}}}
```

## The ResourceRule object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"ResourceRule":{"description":"Matches which resource the request targets.","oneOf":[{"$ref":"#/components/schemas/AnyRule"},{"$ref":"#/components/schemas/IntegrationResourceRule"}],"discriminator":{"propertyName":"type"}},"AnyRule":{"type":"object","description":"Matches any requestor or any resource.","required":["type"],"properties":{"type":{"type":"string","enum":["any"]}}},"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]}}}}
```

## The ApprovalRule object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"ApprovalRule":{"description":"One entry in a policy's approval path. A policy's `approval` array can hold several rules; approval from any one of them provisions the request, except that a single `deny` rule denies all matching requests.","oneOf":[{"type":"object","description":"Routes to the organization's Security Reviewers.","required":["type"],"properties":{"type":{"type":"string","enum":["p0"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"allOf":[{"$ref":"#/components/schemas/GroupRule"},{"type":"object","description":"Routes to members of a directory group.","properties":{"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},{"type":"object","description":"Auto-approves if the requestor is currently on call.","required":["type","integration"],"properties":{"type":{"type":"string","enum":["auto"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"options":{"$ref":"#/components/schemas/ApprovalOptions"}}},{"type":"object","description":"Routes to on-call users for the listed services or schedules.","required":["type","integration","services"],"properties":{"type":{"type":"string","enum":["escalation"]},"integration":{"type":"string","enum":["pagerduty","incidentio"]},"services":{"type":"array","description":"PagerDuty service IDs or Incident.io schedule IDs.","items":{"type":"string"}},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Automatically grants access (standing access).","required":["type"],"properties":{"type":{"type":"string","enum":["persistent"]},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}},{"type":"object","description":"Denies all matching requests. Takes precedence over every other rule.","required":["type"],"properties":{"type":{"type":"string","enum":["deny"]}}},{"type":"object","description":"Routes to a user named by a property of the requestor's directory profile, such as their manager.","required":["type","directory"],"properties":{"type":{"type":"string","enum":["requestor-profile"]},"directory":{"$ref":"#/components/schemas/Directory"},"profileProperty":{"type":"string"},"options":{"$ref":"#/components/schemas/UserApprovalOptions"}}}]},"UserApprovalOptions":{"type":"object","description":"Options for approval rules that route to people.","properties":{"allowOneParty":{"type":"boolean","description":"If true, requestors can approve their own matching requests. Defaults to false."},"breakGlassApprover":{"type":"boolean","description":"If true, designates this approver as a break-glass approver for SSH \"all\" access. Defaults to false."},"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}},"GroupRule":{"type":"object","description":"Matches members of one or more directory groups.","required":["type","effect","groups"],"properties":{"type":{"type":"string","enum":["group"]},"effect":{"type":"string","description":"Whether membership of the listed groups includes (`keep`) or excludes (`remove`) the requestor.","enum":["keep","remove"]},"groups":{"type":"array","items":{"$ref":"#/components/schemas/IdpGroup"}}}},"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]},"ApprovalOptions":{"type":"object","description":"Options for automated approval rules.","properties":{"requireReason":{"type":"boolean","description":"If true, requestors must supply a justification. Defaults to false."},"requireDuration":{"type":"boolean"},"requirePreapproval":{"type":"boolean"}}}}}}
```

## The IntegrationResourceRule object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"IntegrationResourceRule":{"type":"object","description":"Matches resources in a specific integration.","required":["type","service"],"properties":{"type":{"type":"string","enum":["integration"]},"service":{"type":"string","description":"The integration the rule applies to, such as `aws`, `gcloud`, `azure`, `k8s`, `snowflake`, or `ssh`."},"accessType":{"type":"string","description":"The access type within the service the rule applies to, such as `role`, `permission-set`, or `resource`. Defaults to matching any access type when omitted."},"filters":{"type":"object","description":"Narrows the rule to resources that match a filter, keyed by the resource property being filtered (for example `policy` or `role`).","additionalProperties":{"oneOf":[{"$ref":"#/components/schemas/PatternFilter"},{"$ref":"#/components/schemas/BooleanFilter"}]}}}},"PatternFilter":{"description":"Includes or excludes resources whose property matches a regular expression. Patterns are unanchored; use `^` and `$` to anchor them.","oneOf":[{"type":"object","required":["effect","key","pattern"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"key":{"type":"string","description":"The resource property to match against, such as `arn` or `name`."},"pattern":{"type":"string","description":"A JavaScript regular expression matched against the property value."}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","description":"Disables the resource type entirely.","enum":["removeAll"]}}}]},"BooleanFilter":{"description":"Includes or excludes resources by a boolean property, such as SSH `sudo`.","oneOf":[{"type":"object","required":["effect","value"],"properties":{"effect":{"type":"string","enum":["keep","remove"]},"value":{"type":"boolean"}}},{"type":"object","required":["effect"],"properties":{"effect":{"type":"string","enum":["removeAll"]}}}]}}}}
```

## The IdpGroup object

```json
{"openapi":"3.0.4","info":{"title":"P0 Access Policies API","version":"1.0.0"},"components":{"schemas":{"IdpGroup":{"type":"object","description":"A single directory group.","required":["id","label","directory"],"properties":{"id":{"type":"string","description":"The group identifier. For Google Workspace, the group email address. For Okta, the group ID from the admin console URL. For Microsoft Entra ID, the group's UUID."},"label":{"type":"string","description":"A human-readable name for the group, shown in approval notifications."},"directory":{"$ref":"#/components/schemas/Directory"}}},"Directory":{"type":"string","description":"The directory provider a group belongs to.","enum":["azure-ad","entra-id","okta","workspace"]}}}}
```

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

{% hint style="info" %}
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 the [Access Policies](/access-management/just-in-time-access/access-policies) guide.
{% endhint %}

## 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](/getting-started/authenticating-with-the-p0-api) to get a token.

### Read a single rule

```bash
curl -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/your-org/policy/name/engineering-aws-readonly
```

The response is the matching `Policy`:

```json
{
  "name": "engineering-aws-readonly",
  "requestor": {
    "type": "group",
    "effect": "keep",
    "groups": [
      { "id": "engineering@yourcompany.com", "label": "Engineering", "directory": "workspace" }
    ]
  },
  "resource": {
    "type": "integration",
    "service": "aws",
    "accessType": "permission-set",
    "filters": {
      "permission-set": { "effect": "remove", "key": "name", "pattern": "FullAccess|Admin" }
    }
  },
  "approval": [
    { "type": "p0", "options": { "requireReason": true } }
  ]
}
```

### Create a rule

Add a rule by `POST`ing 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:

```bash
curl -X POST \
  -H "Authorization: Bearer $P0_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "engineering-aws-readonly",
    "requestor": {
      "type": "group",
      "effect": "keep",
      "groups": [
        { "id": "engineering@yourcompany.com", "label": "Engineering", "directory": "workspace" }
      ]
    },
    "resource": {
      "type": "integration",
      "service": "aws",
      "accessType": "permission-set",
      "filters": {
        "permission-set": { "effect": "remove", "key": "name", "pattern": "FullAccess|Admin" }
      }
    },
    "approval": [
      { "type": "p0", "options": { "requireReason": true } }
    ]
  }' \
  https://api.p0.app/o/your-org/policy/name/engineering-aws-readonly
```

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:

```bash
curl -X PUT \
  -H "Authorization: Bearer $P0_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "engineering-aws-readonly",
    "requestor": {
      "type": "group",
      "effect": "keep",
      "groups": [
        { "id": "engineering@yourcompany.com", "label": "Engineering", "directory": "workspace" }
      ]
    },
    "resource": { "type": "integration", "service": "aws" },
    "approval": [
      {
        "type": "group",
        "effect": "keep",
        "groups": [
          { "id": "sre@yourcompany.com", "label": "SRE", "directory": "workspace" }
        ],
        "options": { "allowOneParty": true, "requireReason": true }
      }
    ]
  }' \
  https://api.p0.app/o/your-org/policy/name/engineering-aws-readonly
```

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

```bash
curl -X DELETE \
  -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/your-org/policy/name/engineering-aws-readonly
```

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:

```bash
curl -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/your-org/policy/latest
```

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

```json
{
  "policy": {
    "id": "latest",
    "version": "8f3c1a2b",
    "createdDate": "2026-08-14T17:04:12.000Z",
    "rules": [
      {
        "name": "deny-gcloud-owner",
        "requestor": { "type": "any" },
        "resource": {
          "type": "integration",
          "service": "gcloud",
          "accessType": "role",
          "filters": { "role": { "effect": "keep", "key": "id", "pattern": "roles/owner" } }
        },
        "approval": [ { "type": "deny" } ]
      }
    ]
  }
}
```

{% hint style="info" %}
`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`.
{% endhint %}

### 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:

```bash
curl -X POST \
  -H "Authorization: Bearer $P0_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "currentVersion": "8f3c1a2b",
    "policy": {
      "rules": [
        {
          "name": "deny-gcloud-owner",
          "requestor": { "type": "any" },
          "resource": {
            "type": "integration",
            "service": "gcloud",
            "accessType": "role",
            "filters": { "role": { "effect": "keep", "key": "id", "pattern": "roles/owner" } }
          },
          "approval": [ { "type": "deny" } ]
        },
        {
          "name": "engineering-any",
          "requestor": {
            "type": "group",
            "effect": "keep",
            "groups": [
              { "id": "engineering@yourcompany.com", "label": "Engineering", "directory": "workspace" }
            ]
          },
          "resource": { "type": "any" },
          "approval": [ { "type": "p0" } ]
        }
      ]
    }
  }' \
  https://api.p0.app/o/your-org/policy
```

The response contains the stored configuration and the new `version`:

```json
{
  "policy": {
    "rules": [ ... ],
    "version": "b7e90d44"
  }
}
```

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](/getting-started/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.                                                                                                                                                                                     |

## Related

* [Access Policies](/access-management/just-in-time-access/access-policies) — the full rule reference: requestor, resource, filter, and approval types, plus evaluation order.
* [Configure your first access policy](/access-management/just-in-time-access/access-policies/configure-your-first-access-policy) — build a policy in Policy Studio.
* [Agentic access policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) — the `agentic` requestor rule for AI agents.
* [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) — get a bearer token to authenticate these requests.
* [P0 API overview](/getting-started/p0-api-overview) — the base URL, authentication, and permissions shared by all P0 APIs.


# Creating an environment

Follow this guide to install P0 for data collection in your cloud provider, and to run your first posture and inventory scans.

## Install for IAM data collection

First, connect P0 to your cloud service provider by installing the **IAM assessment** integration.

{% hint style="warning" %}
You must install the **IAM assessment** integration to use **Access Inventory** or **Posture**. P0 collects the IAM data for both products through this integration. Install it on each cloud provider you want to assess before creating an environment.
{% endhint %}

{% hint style="warning" %}
If you have P0 installed for just-in-time access, you'll still need to follow this step, as data collection requires different permissions in your cloud provider.
{% endhint %}

Follow the cloud-specific guide for each provider you want to assess:

* [Install IAM assessment on Google Cloud](/environments/creating-an-environment/install-iam-assessment-google-cloud)
* [Install IAM assessment on AWS](/environments/creating-an-environment/install-iam-assessment-aws)
* [Install IAM assessment on Microsoft Azure](/environments/creating-an-environment/install-iam-assessment-azure)

Each guide walks you through navigating to your provider's integration page on the P0 app. After logging in to [p0.app](https://p0.app), select "Integrations," then your cloud provider. Choose "IAM assessment" and follow the steps to install P0.

<figure><img src="/files/NZ54NhyUiZ81NuhYUABv" alt="Google Cloud integration page in P0 showing available components including Access logging, Cloud Run invocation, Domain-restricted sharing, and IAM assessment" width="563"><figcaption></figcaption></figure>

{% hint style="info" %}
Make sure you have the ability to create custom roles and assign privileges in your cloud provider before beginning.
{% endhint %}

{% hint style="info" %}
To augment the usage data available to P0, you may also install "Access logging"
{% endhint %}

## Running your first scan

Navigate to "Dashboard", then select "Create" under "Create an environment to get started":

<figure><img src="/files/Cg4DQmTHm2oOnOZcFaqq" alt="P0 dashboard prompt showing Create an environment to get started with a Create button"><figcaption></figcaption></figure>

Choose a useful name, an assessment frequency, and the targets to assess (this will be the resources you installed in the previous step), then select "Create Environment".

<figure><img src="/files/FN0QIOBSOZdIHbuhylT4" alt="Create New Environment dialog with fields for environment name, assessment frequency, integration selection, and an option to start scheduling immediately" width="375"><figcaption></figcaption></figure>

{% hint style="info" %}
If you select "Start scheduling assessments immediately", the first scan runs as soon as the environment is created. If you would rather trigger the scan manually, you can uncheck this setting and navigate to "Settings" to run a scan once the environment is created.
{% endhint %}

Within a few moments P0 will start collecting data from your project:

<figure><img src="/files/QXKfCqNvQBdXHavmE8Fo" alt="Environment status page showing data collection progress with steps for Data Sources Prepared, Collecting Data, Constructing IAM Graph, and Evaluating Monitors" width="563"><figcaption></figcaption></figure>

Once the data-collection job completes, click "Dashboard" to see your results.

<figure><img src="/files/fFIhzxqmlrTyizWNCm43" alt="P0 dashboard showing posture findings chart, inventory identity breakdown, and just-in-time access metrics including approval time and request frequency" width="563"><figcaption></figcaption></figure>

Congratulations, you've run your first environment scan with P0!

For information on how to view and use the results, see [Access inventory](/inventory/access-inventory) and [Posture overview](/posture/posture-overview).


# Install IAM assessment on Google Cloud

Install the P0 IAM assessment integration on Google Cloud to collect IAM data. Required to use Access Inventory and Posture for your GCP projects.

Install the **IAM assessment** integration to let P0 collect and analyze the IAM configuration of your Google Cloud projects.

{% hint style="warning" %}
You must install the **IAM assessment** integration to use **Access Inventory** or **Posture**. P0 builds the identity graph and evaluates posture findings from the data this integration collects. Without it, Inventory and Posture have no data to display.
{% endhint %}

## What data P0 collects

The IAM assessment grants P0 a read-only custom role and uses it to collect the IAM configuration of your projects. P0 reads metadata and configuration only. It doesn't read the contents of your storage buckets, databases, or other application data.

P0 collects the following datasets:

| Dataset                          | What it includes                                                                                        | Google Cloud API                  |
| -------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------- |
| IAM policy bindings              | Role grants on organizations, folders, projects, and resources, including inherited (ancestor) bindings | Cloud Asset API                   |
| Resource inventory               | Resources in scope, such as Compute instances, storage buckets, service accounts, and BigQuery datasets | Cloud Asset API                   |
| Roles                            | Predefined and custom role definitions and their permissions                                            | IAM API                           |
| Service accounts                 | Service accounts and their metadata                                                                     | IAM API                           |
| Service account keys             | Key metadata, such as creation date and key type (P0 does not read private key material)                | IAM API                           |
| Service account activity         | Authentication-event counts used to detect unused identities and keys                                   | Cloud Monitoring API              |
| IAM recommendations              | Google's IAM policy insights, such as over-privileged or unused grants                                  | Recommender API                   |
| Policy analysis                  | Effective access derived from `analyzeIamPolicy`                                                        | Cloud Asset API                   |
| Compute and Cloud Run identities | Service identities attached to Compute instances and Cloud Run services and jobs                        | Compute Engine API, Cloud Run API |
| Essential contacts               | Project technical contacts                                                                              | Essential Contacts API            |

P0 collects this data when you first install the integration and refreshes it on each scan.

## Required Google Cloud APIs

Enable the following APIs on each project you assess. The custom role's permissions depend on these services, so the assessment fails to read data if an API is disabled.

| API                                      | Service name                          | Used for                                                    |
| ---------------------------------------- | ------------------------------------- | ----------------------------------------------------------- |
| Cloud Asset API                          | `cloudasset.googleapis.com`           | IAM policy bindings, resource inventory, policy analysis    |
| Cloud Resource Manager API               | `cloudresourcemanager.googleapis.com` | Project, folder, and organization metadata and IAM policies |
| Identity and Access Management (IAM) API | `iam.googleapis.com`                  | Roles, service accounts, and key metadata                   |
| Cloud Monitoring API                     | `monitoring.googleapis.com`           | Service account and key activity                            |
| Recommender API                          | `recommender.googleapis.com`          | IAM policy insights and recommendations                     |
| Compute Engine API                       | `compute.googleapis.com`              | Compute instance and service-identity data                  |
| Cloud Run Admin API                      | `run.googleapis.com`                  | Cloud Run service and job identities                        |
| Essential Contacts API                   | `essentialcontacts.googleapis.com`    | Project technical contacts                                  |

{% hint style="info" %}
The commands P0 generates during installation enable the required APIs and create the custom role for you. Enable the APIs manually only if your organization restricts API enablement or uses a separate provisioning process.
{% endhint %}

## Project-level vs organization-level configuration

You can install the IAM assessment on individual projects or across an entire organization. The data P0 collects is the same; the scope of the role binding and the permissions differ.

### Project-level install

Install on one or more specific projects. For each project, P0:

* Creates the **P0 IAM Auditor** (`p0IamAuditor`) custom role at the project level.
* Grants the role to the P0 service account on that project.
* Enables the [required APIs](#required-google-cloud-apis) on that project.

A project-level install assesses only the projects you add.

### Organization-level install

Install once at the organization to assess every project in the hierarchy. P0:

* Creates the custom role at the organization level and grants it to the P0 service account on the organization. The grant is inherited by all folders and projects, so you don't bind the role to each project individually.
* Adds the following organization- and folder-level permissions to the role, on top of the project-level permissions, so P0 can read the resource hierarchy and enumerate projects:
  * `resourcemanager.organizations.get`
  * `resourcemanager.organizations.getIamPolicy`
  * `resourcemanager.folders.get`
  * `resourcemanager.folders.list`
  * `resourcemanager.folders.getIamPolicy`
  * `resourcemanager.projects.list`

{% hint style="info" %}
Enable the [required APIs](#required-google-cloud-apis) on each project you want to assess, even with an organization-level install. The custom role binding is inherited across the hierarchy, but API enablement is per project.
{% endhint %}

The permissions the custom role grants at the project level are:

* `cloudasset.assets.analyzeIamPolicy`
* `cloudasset.assets.searchAllIamPolicies`
* `cloudasset.assets.searchAllResources`
* `compute.instances.list`
* `compute.projects.get`
* `compute.zones.list`
* `essentialcontacts.contacts.list`
* `iam.roles.get`
* `iam.roles.list`
* `iam.serviceAccountKeys.list`
* `iam.serviceAccounts.get`
* `monitoring.timeSeries.list`
* `recommender.iamPolicyInsights.list`
* `resourcemanager.projects.get`
* `resourcemanager.projects.getIamPolicy`
* `run.jobs.list`
* `run.revisions.list`

## Prerequisites

* Existing P0 account at [p0.app](https://p0.app/).
* Existing Google project(s) where you want to install P0.
* Permissions to create GCP roles and add IAM bindings to your Google project(s):
  * `iam.roleAdmin` (Role Admin)
  * `iam.securityAdmin` (Security Admin)

{% hint style="info" %}
You may need to work with your organization's administrator to obtain these permissions.
{% endhint %}

## Install the integration

1. Go to [p0.app](https://p0.app/), navigate to **Integrations**, and select **Google Cloud**.
2. If you have not connected an organization yet, enter your organization ID and click **Next**. To find your organization ID, run `gcloud organizations list` in the [Google Cloud Console Shell](https://console.cloud.google.com), or go to **IAM & Admin** > **Manage Resources**.
3. Choose the **IAM assessment** component.
4. Click **Add project**, enter the **Project identifier** of the GCP project you want to assess, and click **Next**.
5. Review the generated commands. P0 creates a custom role named **P0 IAM Auditor** (`p0IamAuditor`) with the read-only permissions required to collect IAM data, then grants it to the P0 service account.

## Provision access

Provision access using the [Google Cloud Console Shell](https://console.cloud.google.com) or Terraform. To use the Cloud Shell:

1. Go to your [Google Cloud project](https://console.cloud.google.com/) and select the project you entered in the previous step.
2. Open **Cloud Shell Editor**, then click **Open Terminal**.
3. If you use multiple Google accounts, run `gcloud config set account email@email.com` and replace the email with your account email.
4. Copy the **Shell** commands from the P0 configuration page, paste them into the terminal, and press `Return`. If an authorization window appears, click **Authorize**.
5. Return to the P0 configuration page and click **Next** to begin installation.

## Verify the installation

When the commands finish, click **Next** on the P0 configuration page. P0 validates that it can read your IAM data. When validation succeeds, the project appears as installed under the **IAM assessment** component.

## Next steps

* Create an environment and run your first scan. See [Creating an environment](/environments/creating-an-environment).
* View your results in [Access Inventory](/inventory/access-inventory) and [Posture](/posture/posture-overview).


# Install IAM assessment on AWS

Install the P0 IAM assessment integration on AWS to collect IAM data. Required to use Access Inventory and Posture for your AWS accounts.

Install the **IAM assessment** integration to let P0 collect and analyze the IAM configuration of your AWS accounts.

{% hint style="warning" %}
You must install the **IAM assessment** integration to use **Access Inventory** or **Posture**. P0 builds the identity graph and evaluates posture findings from the data this integration collects. Without it, Inventory and Posture have no data to display.
{% endhint %}

## Prerequisites

* Existing P0 account at [p0.app](https://p0.app/).
* At least one AWS account on which to install P0.
* The ability to create roles, add trust relationships, and create and assign role policies. You have this if the [IAMFullAccess managed policy](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/IAMFullAccess.html) is attached to your user.

## Install the integration

1. Navigate to **Integrations** on [p0.app](https://p0.app), then select **AWS**.
2. Choose the **IAM assessment** component.
3. Click **Add account**, enter your numeric AWS account ID, then click **Next**.
4. Review the generated commands. P0 sets up an [AWS IAM Access Analyzer](https://docs.aws.amazon.com/IAM/latest/UserGuide/what-is-access-analyzer.html) for unused access and creates a read-only IAM role that P0 assumes to collect IAM data.

## Provision access

Provision access using the AWS CLI or Terraform. To use the AWS CLI:

1. Copy the commands from the P0 configuration page.
2. Run the commands with the AWS CLI or in [AWS CloudShell](https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html), or apply the Terraform configuration instead.

## Verify the installation

Return to the P0 configuration page and click **Next** to verify the installation. When verification succeeds, P0 takes you to the integration configuration page and the account appears as installed under the **IAM assessment** component.

## Next steps

* Create an environment and run your first scan. See [Creating an environment](/environments/creating-an-environment).
* View your results in [Access Inventory](/inventory/access-inventory) and [Posture](/posture/posture-overview).


# Install IAM assessment on Microsoft Azure

Install the P0 IAM assessment integration on Microsoft Azure to collect IAM data. Required to use Access Inventory and Posture for your Azure subscriptions.

Install the **IAM assessment** integration to let P0 collect and analyze Azure role assignments, permissions, and resource access for your subscriptions. The assessment also inventories the key and password credentials of service principals that hold role assignments, so Posture can flag stale, expiring, and overprivileged service principal access.

P0 also collects usage signals: the last sign-in activity of users and service principals, and the control-plane actions recorded in the [Azure Activity Log](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log). P0 uses these signals to populate **Last Used** in Access Inventory and to flag unused identities and privileges in Posture.

{% hint style="warning" %}
You must install the **IAM assessment** integration to use **Access Inventory** or **Posture**. P0 builds the identity graph and evaluates posture findings from the data this integration collects. Without it, Inventory and Posture have no data to display.
{% endhint %}

{% hint style="info" %}
IAM assessment on Azure is available in beta.
{% endhint %}

## Prerequisites

* Existing P0 account at [p0.app](https://p0.app/).
* One Entra ID directory and at least one subscription on which to install P0.
* A completed [Azure app registration](/integrations/resource-integrations/microsoft-azure/azure-app-registration). IAM assessment uses the service identity created during app registration.
* The ability to create role assignments. You have this if the [Owner](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/privileged#owner) role is assigned to your user.
* The ability to grant admin consent for Microsoft Graph application permissions. You have this if the [Privileged Role Administrator](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#privileged-role-administrator) or [Global Administrator](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator) role is assigned to your user.

{% hint style="info" %}
P0 derives the **Last Used** time for users and service principals from Microsoft Entra sign-in activity, which requires your tenant to hold a [Microsoft Entra ID P1 or P2](https://learn.microsoft.com/en-us/entra/fundamentals/licensing) license. Without a P1 or P2 license, P0 records only the usage it can observe from the Azure Activity Log, so some identities may show **Last Used** as *Unknown*.
{% endhint %}

## Install the integration

1. Navigate to **Integrations** on [p0.app](https://p0.app), then select **Azure**.
2. If prompted, enter the ID of the Entra tenant you want to install P0 on.
3. Choose the **IAM assessment** component.
4. Enter the **subscription ID** of the subscription you want to assess, then click **Next**.
5. Review the generated commands. To assess your subscription, P0 needs the built-in **Reader** role on the subscription, so P0 can collect IAM data, and the following read-only Microsoft Graph permissions, so P0 can inventory directory identities and the app credentials of service principals that hold role assignments:

   | Permission                | Description                                           |
   | ------------------------- | ----------------------------------------------------- |
   | `Group.Read.All`          | Read all groups                                       |
   | `GroupMember.Read.All`    | Read group memberships                                |
   | `User.Read.All`           | Read all users' full profiles                         |
   | `RoleManagement.Read.All` | Read role management resources                        |
   | `Reports.Read.All`        | Read all usage reports                                |
   | `AuditLog.Read.All`       | Read all audit log data                               |
   | `Application.Read.All`    | Read all application and service principal properties |

   Granting the Microsoft Graph permissions requires admin consent.

{% hint style="info" %}
The IAM assessment integration grants the same Microsoft Graph permissions as the [Entra ID directory integration](/integrations/resource-integrations/microsoft-azure). This lets you install IAM assessment on its own, without first installing the Entra ID integration.
{% endhint %}

## Provision access

The P0 configuration page provides the provisioning commands in two formats. Choose the tab that matches how you manage Azure access:

{% tabs %}
{% tab title="Shell" %}

1. Copy the commands from the **Shell** tab on the P0 configuration page. P0 generates a command that adds and admin-consents the Microsoft Graph permissions and a command that assigns the **Reader** role.
2. Run the commands with the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) or in [Azure Cloud Shell](https://learn.microsoft.com/en-us/azure/cloud-shell/overview).

{% hint style="info" %}
The `az ad app permission admin-consent` command grants tenant-wide admin consent for the Graph permissions. Run it with an account that holds the Privileged Role Administrator or Global Administrator role.
{% endhint %}
{% endtab %}

{% tab title="Terraform" %}

1. Copy the configuration from the **Terraform** tab on the P0 configuration page. P0 generates:
   * An `azuread_application_api_access` resource that grants the Microsoft Graph permissions to the P0 app registration.
   * An `azuread_app_role_assignment` resource that grants admin consent for each permission.
   * An `azurerm_role_assignment` resource that assigns the **Reader** role on the subscription.
2. Add the configuration to your Terraform project, then run `terraform apply`.

{% hint style="info" %}
Applying the `azuread_app_role_assignment` resources grants tenant-wide admin consent for the Graph permissions. Run `terraform apply` with credentials that hold the Privileged Role Administrator or Global Administrator role.
{% endhint %}
{% endtab %}
{% endtabs %}

## Verify the installation

Return to the P0 configuration page and click **Next** to verify the installation. When verification succeeds, the subscription appears as installed under the **IAM assessment** component.

## Next steps

* Create an environment and run your first scan. See [Creating an environment](/environments/creating-an-environment).
* View your results in [Access Inventory](/inventory/access-inventory) and [Posture](/posture/posture-overview).


# Environment terminology

This page is a guide to the terminology P0 uses for environment scans.

* **Environment** - The systems that P0 will analyze; consists of a name, scan frequency, and the targets from which P0 will collect data
* **Detection** - A match for a monitor query on a single scan
* **Finding** - An evolving history of detections for a single query match, across multiple scans; findings also have a status and you may attach notes to a finding; statuses are:
  * **Open** - The finding has been detected in the latest scan
  * **Ignored** - The finding has been manually ignored by an assessment owner
  * **Resolved** - The finding was not detected in the latest scan
* **Monitor** - A query that will run automatically on every scan; it also includes a description, severity, and any suggested remediation actions
* **Scan** - A single data collection and analysis run for an environment
* **Target** - A single system to be scanned; this is the integration and specification of what to scan; e.g. AWS accounts, Azure subscriptions, Google Cloud organizations, projects, or folders, Kubernetes clusters, or Okta or Workspace domains
* **Query** - A search of a scan's data for a specific set of terms; a query is composed of a "show" part (describing what element of the IAM configuration to return) and a "where" part (describing conditions that the shown elements must match)
* **Query Result** - A single match for a query; a query may return 0 or more results


# Settings

The settings page lets you control how your environment is scanned and edit your monitors. It's organized into three tabs: **Status**, **Configuration**, and **Monitors**.

### Status

The **Status** tab shows the current state of your environment.

### Configuration

The **Configuration** tab has the following sections:

#### Scheduling

You can edit the frequency at which your assessment runs, or disable scheduled runs altogether in this section.

<figure><img src="/files/weaojqzzNhDalvbLnHuN" alt="Scheduling configuration panel with frequency input set to 1 day, and Update Scheduling and Disable Scheduled Runs buttons" width="375"><figcaption></figcaption></figure>

### Monitors

The **Monitors** tab lets you edit or archive your monitors:

<figure><img src="/files/ouUsUxCXH7e3mPbMNggl" alt="Monitors tab showing an active monitor named Critical GCS risks with HIGH severity, and Edit and Archive options" width="563"><figcaption></figcaption></figure>

Edit monitors to change their severity, description, or search query. Archive monitors to prevent them from running on future assessment jobs. Archived monitors will also be removed from your default findings view.


# Integrations

Connect P0 with AWS, Google Cloud, Azure, Kubernetes, Okta, Slack, and other systems. Configure cloud, directory, notifier, and SIEM integrations.

Integrations allow you to connect your **P0 platform** with external systems, such as cloud environments (AWS, GCP, Azure, Kubernetes), SIEM tools, notification channels (Email, Slack), and custom applications.

Many resource integrations broker access through a [P0 connector](/readme/connectors), a small runtime you deploy in your own cloud account so that no standing credentials sit on laptops or in P0's SaaS.

## Prerequisites

* Existing P0 account at [p0.app](https://p0.app/).
* Sufficient privileged access to configure the target integration to connect with P0 app.

## Installation steps

To install an integration:

1. Login to you P0 account at [p0.app](https://p0.app/).
2. Click on Integrations.
3. Follow the specific guide for the integration.

## Available Integrations

Currently, P0 security has the following integrations:

* [📞 Notifier integrations](/integrations/notifier-integrations)
  * [💬 Slack](/integrations/notifier-integrations/slack)
  * [👬 Microsoft Teams](/integrations/notifier-integrations/microsoft-teams)
  * [✉️ Email](/integrations/notifier-integrations/email)
  * [📣 Custom Notifiers](/integrations/notifier-integrations/custom-notifiers)
    * [AWS Lambda Notifier](/integrations/notifier-integrations/custom-notifiers/aws-lambda-notifier)
* [🔑 Resource integrations](/integrations/resource-integrations)
  * [🤖 Agentic Gateway](/integrations/resource-integrations/agentic-gateway)
    * [Gateway](/integrations/resource-integrations/agentic-gateway/gateway)
    * [Identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider)
    * [MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server)
      * [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws)
      * [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp)
        * [Compute Engine MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/compute)
        * [Cloud Storage MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/storage)
        * [BigQuery MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/bigquery)
        * [Cloud Monitoring MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/monitoring)
        * [IAM MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/iam)
      * [Custom MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/custom)
    * [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client)
      * [Agentic client registration API](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client/client-registration-api)
    * [JWT-SVID](/integrations/resource-integrations/agentic-gateway/spiffe-svid)
    * [Requesting access](/integrations/resource-integrations/agentic-gateway/requesting-access)
    * [Use MCP servers with Claude Code](/integrations/resource-integrations/agentic-gateway/using-mcp-servers)
  * [📦 AWS](/integrations/resource-integrations/aws)
    * [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods)
      * [IAM](/integrations/resource-integrations/aws/installation-methods/iam)
      * [Identity Center](/integrations/resource-integrations/aws/installation-methods/identity-center)
      * [Federated](/integrations/resource-integrations/aws/installation-methods/federated)
    * [Identity Center (merged)](/integrations/resource-integrations/aws/identity-center-merged)
    * [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)
      * [Permission levels](/integrations/resource-integrations/aws/requesting-access/permission-levels)
    * [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc)
    * [Function invocation](/integrations/resource-integrations/aws/function-invocation)
    * [Managed services](/integrations/resource-integrations/aws/managed-services)
      * [AWS RDS](/integrations/resource-integrations/aws/managed-services/aws-rds)
  * [🔐 Cisco Secure Access](/integrations/resource-integrations/cisco-secure-access)
    * [Installation](/integrations/resource-integrations/cisco-secure-access/installation)
    * [Requesting Cisco Secure Access](/integrations/resource-integrations/cisco-secure-access/requesting-access)
  * [Cloudflare](/integrations/resource-integrations/cloudflare)
    * [Requesting Cloudflare access](/integrations/resource-integrations/cloudflare/requesting-access)
  * [🛠️ Custom Resource](/integrations/resource-integrations/custom-resource)
    * [Installing a custom resource integration](/integrations/resource-integrations/custom-resource/installing-a-custom-resource-integration)
  * [📁 File Transfer](/integrations/resource-integrations/file-transfer)
    * [Transfer a file to an instance](/integrations/resource-integrations/file-transfer/transferring-files)
  * [GitHub](/integrations/resource-integrations/github)
    * [Requesting GitHub access](/integrations/resource-integrations/github/requesting-access)
  * [☁️ Google Cloud](/integrations/resource-integrations/google-cloud)
    * [GCP Workload Identity Federation](/integrations/resource-integrations/google-cloud/gcp-wif)
    * [Security perimeter](/integrations/resource-integrations/google-cloud/security-perimeter)
    * [Requesting Google Cloud access](/integrations/resource-integrations/google-cloud/requesting-access)
    * [Permissions reference](/integrations/resource-integrations/google-cloud/permissions-reference)
      * [Cloud Storage](/integrations/resource-integrations/google-cloud/permissions-reference/cloud-storage)
      * [Compute Engine](/integrations/resource-integrations/google-cloud/permissions-reference/compute-engine)
    * [Cloud Run invocation](/integrations/resource-integrations/google-cloud/cloud-run-invocation)
    * [Google Secret Manager](/integrations/resource-integrations/google-cloud/secret-manager)
  * [Grafana Cloud](/integrations/resource-integrations/grafana-cloud)
    * [Requesting access](/integrations/resource-integrations/grafana-cloud/requesting-access)
  * [☸️ Kubernetes](/integrations/resource-integrations/kubernetes)
    * [Terraform installation](/integrations/resource-integrations/kubernetes/terraform-installation)
    * [Requesting Kubernetes access](/integrations/resource-integrations/kubernetes/requesting-access)
    * [Advanced requests](/integrations/resource-integrations/kubernetes/advanced-requests)
  * [🔷 Microsoft Azure](/integrations/resource-integrations/microsoft-azure)
    * [Azure App Registration](/integrations/resource-integrations/microsoft-azure/azure-app-registration)
    * [IAM Management](/integrations/resource-integrations/microsoft-azure/iam-management)
    * [Configure bastion host integration](/integrations/resource-integrations/microsoft-azure/configure-bastion-host-integration)
      * [Azure bastion host](/integrations/resource-integrations/microsoft-azure/configure-bastion-host-integration/azure-bastion-host)
      * [Custom jump host](/integrations/resource-integrations/microsoft-azure/configure-bastion-host-integration/custom-jump-host)
        * [Jump host sizing and tuning](/integrations/resource-integrations/microsoft-azure/configure-bastion-host-integration/custom-jump-host/jump-host-sizing-and-tuning)
      * [Create a custom role](/integrations/resource-integrations/microsoft-azure/configure-bastion-host-integration/create-a-custom-role)
    * [Install SSH access](/integrations/resource-integrations/microsoft-azure/install-ssh-access)
    * [Jump host management](/integrations/resource-integrations/microsoft-azure/jump-host-management)
      * [The jump host connector application](/integrations/resource-integrations/microsoft-azure/jump-host-management/jump-host-connector-application)
      * [The jump host connector image](/integrations/resource-integrations/microsoft-azure/jump-host-management/jump-host-connector-image)
    * [Requesting Microsoft Azure access](/integrations/resource-integrations/microsoft-azure/requesting-access)
  * [🐬 MySQL](/integrations/resource-integrations/mysql)
    * [Installation](/integrations/resource-integrations/mysql/installation)
    * [Requesting MySQL access](/integrations/resource-integrations/mysql/requesting-access)
  * [🔮 Oracle Cloud](/integrations/resource-integrations/oracle-cloud)
    * [Requesting Oracle Cloud access](/integrations/resource-integrations/oracle-cloud/requesting-access)
  * [🐘 PostgreSQL](/integrations/resource-integrations/postgresql-new)
    * [Installing an RDS Database](/integrations/resource-integrations/postgresql-new/installing-an-rds-database)
    * [Installing a Cloud SQL database](/integrations/resource-integrations/postgresql-new/installing-a-cloudsql-database)
    * [Requesting PostgreSQL Access](/integrations/resource-integrations/postgresql-new/requesting-postgresql-access)
  * [Salesforce](/integrations/resource-integrations/salesforce)
    * [Requesting Salesforce access](/integrations/resource-integrations/salesforce/requesting-access)
  * [❄️ Snowflake](/integrations/resource-integrations/snowflake)
  * [🖥️ SSH](/integrations/resource-integrations/ssh)
    * [Self-hosted](/integrations/resource-integrations/ssh/self-hosted)
  * [🌀 Tailscale](/integrations/resource-integrations/tailscale)
    * [Requesting Tailscale access](/integrations/resource-integrations/tailscale/requesting-access)
* [👥 Directory integrations](/integrations/directory-integrations)
  * [Microsoft Entra ID](/integrations/directory-integrations/microsoft-entra-id)
    * [Requesting Microsoft Entra ID access](/integrations/directory-integrations/microsoft-entra-id/requesting-access)
    * [Viewing Security perimeter logs](/integrations/directory-integrations/microsoft-entra-id/viewing-security-perimeter-logs)
  * [Microsoft Entra ID (Legacy)](/integrations/directory-integrations/microsoft-entra-id-legacy)
    * [Requesting Microsoft Entra ID access (legacy)](/integrations/directory-integrations/microsoft-entra-id-legacy/requesting-access)
  * [Google Workspace](/integrations/directory-integrations/google-workspace)
  * [Okta](/integrations/directory-integrations/okta)
* [✔️ Approval integrations](/integrations/approval-integrations)
  * [🔔 PagerDuty](/integrations/approval-integrations/pagerduty)
  * [🚨 Incident.io](/integrations/approval-integrations/incidentio)
* [⚡ SIEM Integrations](/integrations/siem-integrations)
  * [Audit log format](/integrations/siem-integrations/audit-log-format)
  * [Datadog setup](/integrations/siem-integrations/datadog-setup)
  * [Splunk HEC setup](/integrations/siem-integrations/splunk-hec-setup)
* [📝 Tracker integrations](/integrations/tracker-integrations)
  * [🎫 Jira](/integrations/tracker-integrations/jira)


# Notifier integrations

Notifiers are where access requests are broadcasted and approvers may allow or deny requests.


# Slack

{% hint style="info" %}
Installing P0 on Slack takes about 2 minutes.
{% endhint %}

### Before you begin

Make sure you are either "Workspace Admin" or "Workspace Owner" for your workspace, or ask a user with one of these roles to install the P0 Slack integration.

### Adding Slack

1. On [p0.app](https://p0.app), navigate to "Integrations", then select Slack:

<figure><img src="/files/7cz0YrumcJsBs27SLAx3" alt="" width="563"><figcaption></figcaption></figure>

2. Click "Install integration". You'll be redirected to Slack's OAuth install page:

<figure><img src="/files/Esvii9rVqhAGuiYTVfqp" alt="" width="408"><figcaption></figcaption></figure>

3. Choose the workspace where you want P0 installed, and select a channel where you want P0 to post.

{% hint style="warning" %}
The Slack channel you specify must be a public channel.
{% endhint %}

After you finish adding the Slack bot, your integration should look like this (just with your own information of course):

<figure><img src="/files/NVUcQV2q954ziLclbwox" alt="" width="563"><figcaption></figcaption></figure>

And that's it. You're ready to use p0 to grant least-privileged, just-in-time access to members of your organization!

### Slack Settings

{% hint style="info" %}
P0 can create and manage a <mark style="color:blue;">@P0Approvers</mark> Slack group to automatically notify approvers when access requests are made.

If you want P0 to manage this group, you will need to configure Slack (in your Slack's admin settings page) so that anyone except guests can create and modify user groups.
{% endhint %}

<figure><img src="/files/fDAIX05r1ycUmYCHxKWB" alt="" width="563"><figcaption></figcaption></figure>


# Microsoft Teams

{% hint style="info" %}
Installing P0 on Microsoft Teams takes about 5 minutes.
{% endhint %}

### Before you begin

Make sure you are either "Privileged Role Administrator" or "Global Administrator" for your Microsoft Entra ID directory, or ask a user with one of these roles to install the P0 Microsoft Teams integration.

### Adding Microsoft Teams

1. On [p0.app](https://p0.app), navigate to "Integrations", then select **Microsoft Teams**.

<figure><img src="/files/JA1cp0KzZV1ORoQksHNU" alt=""><figcaption></figcaption></figure>

2. Complete the prerequisite steps outlined before clicking the "Install integration" button
   * Make the app available to users in your organization ([Teams docs](https://learn.microsoft.com/en-us/microsoftteams/teams-custom-app-policies-and-settings#upload-a-custom-app-using-teams-admin-center))
     * Access app management in the [Admin Center](https://admin.teams.microsoft.com/policies/manage-apps)
     * Navigate to Manage apps and search for P0 Security
     * Edit availability to all users of your organization

<figure><img src="/files/LyHdmhdIXv3LxBAZBj2y" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="warning" %}
While typically it takes a few minutes, sometimes it may take hours before the P0 Security app becomes fully available in your Teams instance.\
If you encounter errors during the installation process, try again later.
{% endhint %}

3. Click the "Install integration" button. You will be redirected to login to Entra ID. Authorize the P0 Security Bot by clicking "Accept".

<figure><img src="/files/JHuD96i7oTpAlU8Tyhdl" alt="" width="338"><figcaption></figcaption></figure>

4. Choose a Team and a public Channel where the P0 Security Bot will send approval messages. Click "Connect" to complete the installation.

{% hint style="info" %}
The Microsoft Teams integration supports standard channels only
{% endhint %}

<figure><img src="/files/k4irkBzUiLr6G3B3kSx6" alt="" width="563"><figcaption></figcaption></figure>

You should see the following screen when the integration is successful:

<figure><img src="/files/I4pBaAvsRfj6wCeBs3Ao" alt="" width="563"><figcaption></figcaption></figure>

You can pick a new channel from the drop-down and click "Switch channel" any time to route approval messages to a different channel. To use a different team, uninstall first by clicking the trash icon and re-install in a different team.

And that's it. You're ready to use p0 to grant least-privileged, just-in-time access to members of your organization!


# Email

The **Email Notifier** for P0 Security keeps your team informed of critical access-related events in real-time. It ensures that both access **requestors (principals)** and **approvers** are immediately notified of important lifecycle changes in access permissions.

The Email Notifier automatically sends email alerts for key access events, including:

* **New Access Request**: Notifies approvers when a user submits a request for access.
* **Approval Granted**: Notifies the requestor and approvers when their access is approved.
* **Access Revoked**: Notifies both the requestor and approvers when access is explicitly revoked.
* **Access Expired**: Sends alerts when an access grant naturally expires.
* **Pre-approval Granted**: Notifies the requestor and approver when pre-approved access has been granted.
* **Pre-approval Expiration Reminder**: Notifies approver 2 weeks before their pre-approval expires.

#### Who Gets Notified

| Event                            | Notified Parties      |
| -------------------------------- | --------------------- |
| Access Request Created           | Approvers             |
| Access Approved                  | Approvers & Requestor |
| Access Revoked                   | Requestor             |
| Access Expired                   | Requestor             |
| Pre-approval Created             | Approver & Requestor  |
| Pre-approval Expiration Reminder | Requestor             |

## Installing your Email Notifier

1. Go to p0.app in your browser, navigate to **Integrations**, and select **Email** in the **Notifiers** section.

<figure><img src="/files/Y41endxJymB0jQLDyD8R" alt="" width="563"><figcaption></figcaption></figure>

2. Within the integration, click the **"Install integration"** button.

<figure><img src="/files/TTPeKpd0CW3uzx4rHqbp" alt="" width="563"><figcaption></figcaption></figure>

2. You're ready to go! No additional configuration is needed.

<figure><img src="/files/yWutS8oU4iffonzBxfys" alt="" width="563"><figcaption></figcaption></figure>


# Custom notifiers

Integrate P0 with any internal notification system you own, or with systems that do not have a built-in notification service yet.

Implement your own API endpoints that P0 can send notifications to.

## Set up Custom Notifier Integration

### Configuration Parameters

These parameters are configured by you during setup.

The examples use a custom notifier that sends notifications to an internal application for custom processing. Each custom notifier integration supports different trigger types. To reduce notification noise, you can choose only the specific triggers you want to be notified about.

<table><thead><tr><th width="179.36224365234375">Parameter</th><th>Description</th><th>Example Value</th></tr></thead><tbody><tr><td>Notifier ID</td><td>The identifier of the system you are integrating*</td><td>my-notifier-service</td></tr><tr><td>Notifier Name</td><td>The name of the custom notifier integration</td><td>Internal Customer Admin App</td></tr><tr><td>Webhook URL</td><td>Your https URL that P0 uses to call your endpoints.</td><td>https://p0-api.example.com/notifications</td></tr></tbody></table>

<sup>\* Identifiers in P0 do not allow whitespace and by convention use CamelCasing. Not visible to users.</sup>

## OpenAPI Specification

This specification describes the API endpoint you must implement to create a custom notifier integration in P0.

{% file src="/files/e6fy7OrMxHEPkNt81enM" %}

## Receive notifications about requests and preapprovals

> This endpoint receives access-related notifications from P0, including user access requests and preapproval notifications. Implement this endpoint to handle custom notifiers that are not natively supported by P0. Once implemented, register the endpoint using a custom notifier in the P0 console to start receiving events.<br>

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"tags":[{"name":"notifications"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"P0 generates a keypair during integration setup. Each http request sent by P0 to your endpoints contains a JWT signed with the private key in the Authorization header. Your endpoints must use the public key retrieved during integration setup, to decode the JWT   and verify its claims. The claims are: sub (Subject) iss (Issuer), aud (Audience), iat (Issued At), exp (Expiry)"}},"schemas":{"NotificationEvent":{"oneOf":[{"$ref":"#/components/schemas/PreapprovalCreatedEvent"},{"$ref":"#/components/schemas/PreapprovalExpiredEvent"}],"discriminator":{"propertyName":"eventType"}},"PreapprovalCreatedEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-created"]},"data":{"$ref":"#/components/schemas/Preapproval"}}},"Preapproval":{"type":"object","required":["id","type","access","startsAt","endsAt","principal","permission"],"properties":{"id":{"type":"string","description":"Random identifier for the preapproval generated by P0"},"type":{"type":"string","description":"The integration type the preapproval is for."},"access":{"type":"string","description":"The access type the preapproval is for."},"permission":{"$ref":"#/components/schemas/Permission"},"startsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval becomes active."},"endsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval expires."},"createdAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval was created."},"principal":{"type":"string","description":"The principal this preapproval grants access to"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"reason":{"type":"string","description":"The justification for the creation of this preapproval."}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}},"PreapprovalExpiredEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-expired"]},"data":{"$ref":"#/components/schemas/Preapproval"}}},"Error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["public","internal"]},"message":{"type":"string"},"errorId":{"type":"string"}}}},"responses":{"EmptySuccessResponse":{"description":"Success"},"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/":{"post":{"tags":["notifications"],"summary":"Receive notifications about requests and preapprovals","description":"This endpoint receives access-related notifications from P0, including user access requests and preapproval notifications. Implement this endpoint to handle custom notifiers that are not natively supported by P0. Once implemented, register the endpoint using a custom notifier in the P0 console to start receiving events.\n","operationId":"handleAccessEvent","requestBody":{"description":"Grant a user access to a resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationEvent"}}},"required":true},"responses":{"200":{"$ref":"#/components/responses/EmptySuccessResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}}}}}}
```

## The NotificationEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"NotificationEvent":{"oneOf":[{"$ref":"#/components/schemas/PreapprovalCreatedEvent"},{"$ref":"#/components/schemas/PreapprovalExpiredEvent"}],"discriminator":{"propertyName":"eventType"}},"PreapprovalCreatedEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-created"]},"data":{"$ref":"#/components/schemas/Preapproval"}}},"Preapproval":{"type":"object","required":["id","type","access","startsAt","endsAt","principal","permission"],"properties":{"id":{"type":"string","description":"Random identifier for the preapproval generated by P0"},"type":{"type":"string","description":"The integration type the preapproval is for."},"access":{"type":"string","description":"The access type the preapproval is for."},"permission":{"$ref":"#/components/schemas/Permission"},"startsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval becomes active."},"endsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval expires."},"createdAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval was created."},"principal":{"type":"string","description":"The principal this preapproval grants access to"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"reason":{"type":"string","description":"The justification for the creation of this preapproval."}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}},"PreapprovalExpiredEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-expired"]},"data":{"$ref":"#/components/schemas/Preapproval"}}}}}}
```

## The PreapprovalCreatedEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"PreapprovalCreatedEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-created"]},"data":{"$ref":"#/components/schemas/Preapproval"}}},"Preapproval":{"type":"object","required":["id","type","access","startsAt","endsAt","principal","permission"],"properties":{"id":{"type":"string","description":"Random identifier for the preapproval generated by P0"},"type":{"type":"string","description":"The integration type the preapproval is for."},"access":{"type":"string","description":"The access type the preapproval is for."},"permission":{"$ref":"#/components/schemas/Permission"},"startsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval becomes active."},"endsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval expires."},"createdAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval was created."},"principal":{"type":"string","description":"The principal this preapproval grants access to"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"reason":{"type":"string","description":"The justification for the creation of this preapproval."}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}}}}}
```

## The PreapprovalExpiredEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"PreapprovalExpiredEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["preapproval-expired"]},"data":{"$ref":"#/components/schemas/Preapproval"}}},"Preapproval":{"type":"object","required":["id","type","access","startsAt","endsAt","principal","permission"],"properties":{"id":{"type":"string","description":"Random identifier for the preapproval generated by P0"},"type":{"type":"string","description":"The integration type the preapproval is for."},"access":{"type":"string","description":"The access type the preapproval is for."},"permission":{"$ref":"#/components/schemas/Permission"},"startsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval becomes active."},"endsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval expires."},"createdAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval was created."},"principal":{"type":"string","description":"The principal this preapproval grants access to"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"reason":{"type":"string","description":"The justification for the creation of this preapproval."}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}}}}}
```

## The PermissionRequest object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"PermissionRequest":{"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The integration type that this request was created for"},"access":{"type":"string","description":"The access type requested"},"status":{"type":"string","enum":["APPROVED","CLEANED_UP","DENIED","DONE","DRAFT","ERRORED","EXPIRED","NEW","EXPIRY_SUBMITTED","PENDING_APPROVAL","REVOKE_SUBMITTED","REVOKED","STAGED"]},"principal":{"type":"string","description":"The principal access will be granted to"},"requestor":{"type":"string","description":"The user requesting access. This payload might be different if requesting on behalf of another user."},"reason":{"type":"string","description":"The requestor's justification for this access request."},"requestedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds of when this request was created."},"permission":{"$ref":"#/components/schemas/Permission"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"error":{"$ref":"#/components/schemas/Error"}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}},"Error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["public","internal"]},"message":{"type":"string"},"errorId":{"type":"string"}}}}}}
```

## The Preapproval object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"Preapproval":{"type":"object","required":["id","type","access","startsAt","endsAt","principal","permission"],"properties":{"id":{"type":"string","description":"Random identifier for the preapproval generated by P0"},"type":{"type":"string","description":"The integration type the preapproval is for."},"access":{"type":"string","description":"The access type the preapproval is for."},"permission":{"$ref":"#/components/schemas/Permission"},"startsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval becomes active."},"endsAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval expires."},"createdAt":{"type":"number","description":"The epoch timestamp in milliseconds for when this preapproval was created."},"principal":{"type":"string","description":"The principal this preapproval grants access to"},"approver":{"$ref":"#/components/schemas/ApprovalDetails"},"reason":{"type":"string","description":"The justification for the creation of this preapproval."}}},"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}},"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}}}}}
```

## The Permission object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"Permission":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"A short label or name for the requested permission."},"description":{"type":"string","description":"A full friendly description of the requested permission."}}}}}}
```

## The ApprovalDetails object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"ApprovalDetails":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"A unique identifier of the entity that approved the request, this may be an email."},"name":{"type":"string","description":"A user friendly name of the entity that approved the request"},"email":{"type":"string","description":"The email of the entity that approved the request."},"approvedTimestamp":{"type":"number","description":"The epoch timestamp in milliseconds when the entity approved the request."},"approvalSource":{"type":"string","enum":["evidence","pagerduty","persistent","slack","webapp"],"description":"The originating source that the entity used to approve a request."}}}}}}
```

## The Error object

```json
{"openapi":"3.0.4","info":{"title":"Custom Notifier API","version":"0.0.1"},"components":{"schemas":{"Error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["public","internal"]},"message":{"type":"string"},"errorId":{"type":"string"}}}}}}
```


# AWS Lambda notifier

The **P0 AWS Lambda Notifier** lets you route notification events directly to your own AWS Lambda function. Built using the [Custom Notifier API](/integrations/notifier-integrations/custom-notifiers), it enables you to trigger custom workflows, integrate with internal systems, or run arbitrary logic in response to notification events from your existing AWS infrastructure.

Use it to:

* Trigger automation pipelines
* Forward events to internal alerting systems
* Apply custom filtering or enrichment
* Connect with services beyond email or Slack

## Before you begin

This guide walks you through setting up your **AWS Lambda Notifier.** Before diving into the steps, make sure you have installed an AWS function caller component in P0

[How to install the AWS function caller component](https://github.com/p0-security/p0-docs/blob/main/integrations/resource-integrations/aws/function-caller.md)

## Installing your AWS Lambda Notifier

1. Go to p0.app in your browser, navigate to **Integrations**, and select **AWS Lambda** in the **Notifiers** section.

<figure><img src="/files/CaXfkx4ugvT5H9nkhzRJ" alt="" width="563"><figcaption></figcaption></figure>

2. Within the integration, click the **"Add Notifier"** button.

<figure><img src="/files/28y4luKfLzlJWM1EUSlq" alt="" width="563"><figcaption></figcaption></figure>

3. Give your AWS Lambda notifier name. This is a user-friendly label that helps you recognize the purpose of the notifier at a glance.

<figure><img src="/files/zU1Clc8wqI3wUegodp86" alt="" width="563"><figcaption></figcaption></figure>

4. Choose the Lambda function you registered previously via the function-caller.

<figure><img src="/files/fHL80FV4ew49Bav6j3vs" alt="" width="563"><figcaption></figcaption></figure>

5. Complete the setup by clicking **"Finish"**. Your notifier is now active!

<figure><img src="/files/URN20Al9bSHL4rNpLBVR" alt="" width="563"><figcaption></figcaption></figure>

## Related Links

[Review the Custom Notifier OpenAPI specification](/integrations/notifier-integrations/custom-notifiers)


# Resource integrations

Configure just-in-time access for AWS, Google Cloud, Azure, Kubernetes, SSH, and more. Install P0 Security resource integrations to manage privileged cloud access.

Resource integrations allow you to request and grant access to resources in your systems.


# Agentic gateway

Configure the P0 AI Gateway and the upstream MCP servers it fronts, so P0-managed AI agents can access them under runtime authorization policy.

The Agentic Gateway integration puts runtime authorization in front of the MCP servers in your environment, so agents don't operate on loose delegation or standing privilege and every action stays attributable. The [P0 AI Gateway](/readme/agentic-control-plane) sits in the data path between your agents and your MCP servers, verifies the identity of the originator and the agent on every tool call, and evaluates each action against the authorization policy defined in the [P0 AuthZ Control Plane™ for Agents](/readme/agentic-control-plane) before it reaches its target.

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

This integration has four components:

| Component                                                                                      | Use                                                                                                                                        |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [**Gateway**](/integrations/resource-integrations/agentic-gateway/gateway)                     | Registers your self-hosted P0 AI Gateway deployment with P0 so it can enforce policy and report activity.                                  |
| [**Identity provider**](/integrations/resource-integrations/agentic-gateway/identity-provider) | Enrolls an external identity provider so its JWT-authenticated agents are trusted at the gateway.                                          |
| [**MCP server**](/integrations/resource-integrations/agentic-gateway/mcp-server)               | Configures an upstream MCP server behind the gateway. Once configured, it becomes available to P0-managed agents across your organization. |
| [**A2A bridge**](/integrations/resource-integrations/agentic-gateway/a2a-bridge)               | Places an upstream agent host behind the gateway so agent-to-agent (A2A) calls pass through the gateway under policy.                      |

## Prerequisites

* An existing P0 account at [p0.app](https://p0.app/).
* A deployed P0 AI Gateway. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway). This is a prerequisite for the [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component.

## How it works

1. [**Register the gateway**](/integrations/resource-integrations/agentic-gateway/gateway). After deploying the gateway into your environment, register it with P0 so the AuthZ Control Plane can supply policy and collect audit activity.
2. [**Enroll an identity provider**](/integrations/resource-integrations/agentic-gateway/identity-provider). Tell the gateway which token issuer to trust so its JWT-authenticated agents are accepted.
3. [**Configure upstream MCP servers**](/integrations/resource-integrations/agentic-gateway/mcp-server). Declare each MCP server you want to expose behind the gateway. P0 supports two kinds:
   * **Predefined**: P0-authored server definitions
   * **Custom**: servers you define yourself
4. [**Add A2A bridges**](/integrations/resource-integrations/agentic-gateway/a2a-bridge) (optional). Place an upstream agent host behind the gateway so agent-to-agent calls pass through the gateway under policy.

## Next steps

* Connect your agents. Each developer points their own agent at the configured server through the gateway. For Claude Code, see [Use MCP servers with Claude Code](/integrations/resource-integrations/agentic-gateway/using-mcp-servers).

Once an agent connects, the gateway authenticates every tool call, authorizes it against policy, and logs it. See [Requesting access](/integrations/resource-integrations/agentic-gateway/requesting-access) for how agents request and use just-in-time access, and [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) for governing those requests.


# Gateway

Register your self-hosted P0 AI Gateway with P0 so it can enforce policy on agent tool calls and receive its MCP server definitions.

The **Gateway** component registers a deployed [P0 AI Gateway](/readme/agentic-control-plane) with P0. Once registered, P0 recognizes the gateway's identity, pushes it the definitions of the [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) it should host, and enforces policy at runtime. The gateway ships access logs directly to the system of your choice. You register one Gateway component per gateway deployment.

## Prerequisites

* An existing P0 account at [p0.app](https://p0.app/).
* A deployed P0 AI Gateway. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway). You need the gateway's public URL and its OAuth server endpoint from that deployment.

## Register the gateway

### Through the console

1. Navigate to **Integrations** on [p0.app](https://p0.app) and select **Agentic gateway**, then choose the **Gateway** component.

<figure><img src="/files/pkU6FQYexOfbjEM7YBV3" alt="Agentic gateway integration page listing the Gateway and MCP server components, not installed"><figcaption></figcaption></figure>

2. Click **Add gateway**.

<figure><img src="/files/KJx3RGpNtWSfLYg68odJ" alt="Gateway component page with an empty list of installed gateways and an Add gateway button"><figcaption></figcaption></figure>

3. Enter the gateway details, then click **Next**:

<figure><img src="/files/OfV0jbMgUMelYtcNnRQC" alt="Form for installing a new gateway with fields for gateway identifier and agentic gateway URL"><figcaption></figcaption></figure>

* **Gateway identifier**: a name for this gateway within P0.
* **Agentic gateway URL**: The public URL where your gateway runs. MCP clients connect to MCP servers through this gateway URL.

4. Enter the **OAuth server endpoint** then click **Finish**:

<figure><img src="/files/otvIucpmE8iaCGwkY8ZI" alt="Second step of gateway configuration showing the read-only gateway URL and the OAuth server endpoint field"><figcaption></figcaption></figure>

* **OAuth server endpoint**: the public URL of your gateway's OAuth server, typically the same as the **Agentic gateway URL**. It must be publicly accessible and serve `.well-known/jwks.json`. P0 uses this endpoint as the gateway's token issuer to verify the requests it receives, so it must exactly match the issuer your deployed OAuth server presents.

5. The gateway now appears with the state **Installed**.

<figure><img src="/files/pUFUd6ZkJqJPoYkt1scJ" alt="Installed gateways list showing the new gateway with state Installed"><figcaption></figcaption></figure>

## How it works

After registration, the gateway periodically calls P0 to sync its configuration. P0 returns the set of [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) configured for this gateway, and the gateway reconciles them, adding newly configured servers and removing ones that are no longer configured. No servers are available through the gateway until you configure them.

## Next steps

* Configure the [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) this gateway should front.
* [Connect an MCP client to the P0 AI Gateway](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client).


# Identity provider

Enroll an identity provider with the P0 AI Gateway so its JWT-authenticated agents are trusted to access the gateway.

The **Identity provider** component enrolls an external identity provider (IdP) with the [P0 AI Gateway](/readme/agentic-control-plane). Once enrolled, agents that present a JWT issued by that provider are trusted at the gateway, provided the token's claims match the patterns you configure. Use this component to federate the gateway with the issuer that mints your agents' tokens, so those agents can reach the [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) behind the gateway.

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

Enrolling an identity provider establishes **issuer trust**: it tells the gateway which token issuers to accept and which audience and subject claims those tokens must carry. When a matching token arrives, P0 resolves it to an individual agent client, either by registering the client automatically or by matching one you pre-registered, depending on the provider's **Dynamic registration** setting. Enrolling a provider does not, on its own, grant access to any resource. Every accepted request is still authorized against the [policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) you define in P0.

## Prerequisites

* An existing P0 account at [p0.app](https://p0.app/).
* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, and the gateway deployed in your environment. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway).
* The details of the issuer whose tokens you want to trust: its issuer URL (the `iss` claim) and the audience (`aud`) claim its agent tokens carry, plus the subject (`sub`) or email (`email`) claim values to narrow matching to specific agents.

## Enroll an identity provider

1. Navigate to **Integrations** on [p0.app](https://p0.app) and select **Agentic gateway**, then choose the **Identity provider** component.
2. Click **Add identity provider**.
3. Enter the identity provider details, then finish the configuration:
   * **Issuer**: the issuer URL of the identity provider, matching the `iss` claim in the tokens it issues. Enter it as a full URL, such as `https://your-idp.example.com/`. P0 matches this value against the token's `iss` claim exactly, so it must match the issuer your provider presents character for character.
   * **Audience pattern**: a pattern that a token's audience (the `aud` claim) must match to be accepted. A token is accepted when at least one of its audience values matches this pattern.
   * **Subject pattern**: an optional pattern that a token's subject (the `sub` claim) must match to be accepted. Leave it empty to accept any subject.
   * **Email pattern**: an optional pattern that a token's email (the `email` claim) must match to be accepted. When set, the email must also be verified. Leave it empty to accept any email.
   * **Dynamic registration**: whether P0 automatically registers agents whose tokens match this provider. Enable it to let P0 register each matching agent as a client the first time it validates the agent's token. Leave it disabled to require that each agent client be pre-registered before P0 accepts its tokens.
4. The identity provider now appears in the component's list of enrolled providers.

{% hint style="info" %}
**Audience pattern**, **Subject pattern**, and **Email pattern** are regular expressions, not literal strings. To match an exact value, anchor the expression, for example `^my-agent$`. To accept any value, either leave the optional **Subject pattern** or **Email pattern** empty or use `.*`.
{% endhint %}

You can enroll more than one identity provider. A token is trusted if it matches **any** enrolled provider.

## Fields

| Field                    | Claim   | Matching                      | Description                                                                                                                                                            |
| ------------------------ | ------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Issuer**               | `iss`   | Exact match                   | The issuer URL of the identity provider. Must be a valid URL and match the token's `iss` claim exactly.                                                                |
| **Audience pattern**     | `aud`   | Regular expression            | A token is accepted when at least one of its `aud` values matches this pattern.                                                                                        |
| **Subject pattern**      | `sub`   | Regular expression (optional) | When set, the token's `sub` claim must match this pattern. Leave empty to accept any subject.                                                                          |
| **Email pattern**        | `email` | Regular expression (optional) | When set, the token's `email` claim must match this pattern and must be verified. Leave empty to accept any email.                                                     |
| **Dynamic registration** | —       | Toggle                        | When enabled, P0 registers a matching agent as a client automatically on first validation. When disabled, the agent client must be pre-registered or validation fails. |

## How it works

When an agent presents a JWT to the gateway, the gateway asks P0 to validate it against the enrolled identity providers. P0 first rejects the token outright if it carries no audience or has a missing, malformed, or expired `exp` claim. It then looks for an enrolled provider that satisfies all of the following:

* The provider's **Issuer** equals the token's `iss` claim.
* At least one of the token's `aud` values matches the provider's **Audience pattern**.
* If the provider sets a **Subject pattern**, the token's `sub` claim matches it.
* If the provider sets an **Email pattern**, the token's `email` claim matches it and is verified.

If no enrolled provider matches, the token is rejected. Matching a provider establishes that the token comes from a trusted issuer and satisfies the configured claim constraints (audience, and any subject/email patterns). It is a trust decision about the issuer, not an authorization decision about a specific resource.

Once a token matches a provider, P0 resolves it to an individual agent client. It maps the client subject from the first available of the federating token's:

* `external_sub` claim, if set
* `email` claim, if set and if the `email_verified` claim is `true`
* `sub` claim
* If the client is already registered, P0 accepts the token, unless that client has been disabled.
* If the client is not yet registered, the provider's **Dynamic registration** setting decides the outcome. With dynamic registration enabled, P0 registers the client automatically and accepts the token. With it disabled, P0 rejects the token until you [register the agent identity](#register-an-agent-identity).

After the token is accepted, the gateway authorizes each tool call against your P0 policies and logs it.

## Register an agent identity

When a provider has **Dynamic registration** disabled, P0 rejects an agent's tokens until you register that agent's identity in advance. Register each agent identity once, before it connects for the first time. Providers that have dynamic registration enabled register their agents automatically, so this step is not required for them.

Registering an agent identity does not create a secret. The agent still authenticates with the JWT its identity provider issues; registration only tells P0 to trust the resolved client.

1. Navigate to **Agentic Access** on [p0.app](https://p0.app) and open the **MCP clients** tab.
2. Click **Register MCP client**.
3. For **Client type**, select **Federated**.
4. Select the **Identity provider** to register the agent against. Only installed identity providers appear in the list.
5. In the **Agent identity** field, enter the identity the way the provider presents it in its tokens, in this order:

   * Use the `external_sub` claim if the provider sets one. Some providers set this claim to group several of their subjects into a single agent.
   * If the provider does not set it, use the verified email. For a GCP service account, use its email, such as `agent@my-project.iam.gserviceaccount.com`.
   * If there is no verified email either, use the token subject (the `sub` claim). For GitHub Actions, use a subject such as `repo:my-org/my-repo:ref:refs/heads/main`.

   The order matters. P0 turns the identity you enter into a subject the same way it turns an incoming token into a subject. If a provider sets `external_sub` and you register the agent's email instead, the client is stored under a subject that token validation never looks up, and the agent can never authenticate.
6. Click **Register agent identity**.

P0 issues a subject for the client and shows it as the **Issued subject**. The agent can now connect through the gateway using a token from this provider.

{% hint style="info" %}
The identity you register must match the claim the provider actually issues. If a registered agent cannot connect, compare its **Issued subject** with the caller shown on the **Activity** tab of the Agentic Access page: the identity must match the claim its provider presents.
{% endhint %}

You can register each identity once per provider. If you register an identity that already exists for the provider, P0 returns an error and leaves the existing client unchanged. A new registration does not reactivate a client that was disabled, and registering the same workload name against two providers creates two distinct clients.

## Next steps

* Configure the [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) you want to expose behind the gateway.
* Define the [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) that govern what trusted agents can do.


# MCP server

Configure upstream MCP servers behind the P0 AI Gateway to make them available to P0-managed agents under policy.

The **MCP server** component configures an upstream MCP server behind the [P0 AI Gateway](/readme/agentic-control-plane). Once an MCP server is configured, it is available to agents through the gateway. Every tool call is authenticated and checked against policy before reaching the upstream server.

## Prerequisites

* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, and the gateway deployed in your environment. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway).

## Types of MCP server

When you add a server, you set its **Definition**: a server predefined by P0, or a custom server you define yourself.

| Server                                                                              | Type       | Description                                                                                                                                                                                      |
| ----------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [**AWS**](/integrations/resource-integrations/agentic-gateway/mcp-server/aws)       | Predefined | Exposes the AWS CLI to agents. Requires the [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc) component.                                                                              |
| [**GCP**](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp)       | Predefined | Exposes Google Cloud APIs to agents, with one server per Google Cloud service. Requires a [GCP Workload Identity Federation](/integrations/resource-integrations/google-cloud/gcp-wif) provider. |
| [**Custom**](/integrations/resource-integrations/agentic-gateway/mcp-server/custom) | Custom     | A server you define yourself, for any MCP server P0 does not ship a predefined definition for.                                                                                                   |

## Add an MCP server

Every MCP server starts with the same steps. The final configuration screen differs by server. Continue to the [AWS](/integrations/resource-integrations/agentic-gateway/mcp-server/aws), [GCP](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp), or [Custom](/integrations/resource-integrations/agentic-gateway/mcp-server/custom) page once you reach it.

1. Navigate to **Integrations** on [p0.app](https://p0.app) and select **Agentic gateway**, then choose the **MCP server** component.

<figure><img src="/files/pkU6FQYexOfbjEM7YBV3" alt="Agentic gateway integration page listing the Gateway and MCP server components, not installed"><figcaption></figcaption></figure>

2. Click **Add server**.

<figure><img src="/files/1lDp6Mn8SDC8m94CH7du" alt="MCP server component page with an empty list of installed servers and an Add server button"><figcaption></figcaption></figure>

3. Enter the server details, then click **Next**:

<figure><img src="/files/zQaPomyHP0PvYQtmbcwP" alt="Form for installing a new server with fields for server identifier and gateway"><figcaption></figcaption></figure>

* **Server identifier**: a name for this server within P0.
* **Gateway**: the [registered gateway](/integrations/resource-integrations/agentic-gateway/gateway) that hosts this server.

4. Set the **Credential source**, which selects how the gateway authenticates to the upstream:
   * [**AWS IAM federation**](/integrations/resource-integrations/aws/aws-oidc)
   * [**GCP WIF federation**](/integrations/resource-integrations/google-cloud/gcp-wif)
   * **OAuth**
5. Set the **Definition**, which selects what the server actually exposes:
   * **P0**: a predefined server. Continue to its page, [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws) or [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp).
   * **Custom**: a server you define yourself. See [Custom MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/custom).

### Configure with Terraform

Use the P0 Terraform provider to configure an MCP server outside the console. The [`p0_agentic_server`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_server) resource takes the hosting gateway's `id`, the server's own `id`, a `definition` (`p0` or `custom`), and a `credential` source (`aws`, `gcp`, or `oauth`) — the same choices as the console fields described earlier.

## Related docs

* [Connect an MCP client to the P0 AI Gateway](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client): Learn how to register a client and connect an agent to an MCP server through the gateway.
* [Use MCP servers with Claude Code](/integrations/resource-integrations/agentic-gateway/using-mcp-servers): Connect Claude Code to MCP servers managed by P0.


# AWS MCP server

Install the predefined AWS MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the AWS CLI.

The **AWS** MCP server is a predefined server that runs the official [AWS API MCP server](https://github.com/awslabs/mcp/blob/main/src/aws-api-mcp-server/README.md) (`awslabs.aws-api-mcp-server`) behind the [P0 AI Gateway](/readme/agentic-control-plane), wired up with P0's access controls. Agents can run AWS API calls (via `call_aws`, with `suggest_aws_commands` for help) without ever holding AWS credentials: the gateway obtains short-lived, policy-scoped access for each session using Workload Identity Federation, backed by the [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc) component.

## Prerequisites

* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, with the gateway deployed in your environment.
* The [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc) component installed on the AWS account you want agents to reach. This provides the federated identity the AWS MCP server uses as its credential provider, and it requires the base [AWS integration](/integrations/resource-integrations/aws) with IAM management on the same account.

## Configure the AWS MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:

<figure><img src="/files/zQaPomyHP0PvYQtmbcwP" alt="AWS MCP server configuration showing fields server identifier and gateway"><figcaption></figcaption></figure>

<figure><img src="/files/8bfVVjWtgHJBRpN8Fa6U" alt="AWS MCP server configuration showing AWS IAM federation as the credential source and the P0 AWS CLI predefined definition"><figcaption></figcaption></figure>

* **Credential source**: choose **AWS IAM federation**, then select the **Federation provider**: the [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc) identity you installed. The gateway uses this identity's audience when it federates to AWS, and your account's IAM trust policy authorizes it.
* **Definition**: choose **P0**, then select **AWS CLI** as the pre-defined server identifier.

{% hint style="info" %}
A Federation provider maps one-to-one to a single AWS account. To give agents access to multiple AWS accounts, configure a separate AWS MCP server for each account, using that account's [AWS OIDC](/integrations/resource-integrations/aws/aws-oidc) identity as its Federation provider.
{% endhint %}

2. The server now appears with the state **Installed**.

<figure><img src="/files/57hseyMYEPBItCSZERgj" alt="Installed servers list showing the new AWS server with state Installed"><figcaption></figcaption></figure>

Once saved, P0 pushes the server definition to the gateway on its next sync, and the AWS MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived AWS credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the AWS MCP server, and what they may do in AWS.


# GCP MCP server

Install the predefined GCP MCP servers behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to Google Cloud APIs.

The **GCP** MCP servers are predefined servers that expose Google Cloud APIs to P0-managed agents behind the [P0 AI Gateway](/readme/agentic-control-plane), each wired up with P0's access controls. P0 ships one server per Google Cloud service, so you install only the services your agents need.

Agents call these services without ever holding Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation, backed by a [GCP Workload Identity Federation](/integrations/resource-integrations/google-cloud/gcp-wif) provider. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

{% hint style="info" %}
The GCP MCP servers are a preview feature. Contact P0 to enable them for your organization.
{% endhint %}

## Available servers

| Server                                                                                                       | Exposes                                                               |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| [Compute Engine MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/compute)      | Compute Engine resources such as VM instances, disks, and networks.   |
| [Cloud Storage MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/storage)       | Cloud Storage buckets and objects.                                    |
| [BigQuery MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/bigquery)           | BigQuery datasets, tables, and jobs.                                  |
| [Cloud Monitoring MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/monitoring) | Cloud Monitoring metrics, alerting policies, and dashboards.          |
| [IAM MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/iam)                     | Identity and Access Management service accounts, roles, and policies. |

## Prerequisites

Every GCP MCP server shares the same prerequisites:

* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, with the gateway deployed in your environment.
* A [GCP Workload Identity Federation](/integrations/resource-integrations/google-cloud/gcp-wif) provider installed on the Google Cloud project you want agents to reach. This provides the federated identity each GCP MCP server uses as its credential provider. It requires the base [Google Cloud integration](/integrations/resource-integrations/google-cloud) with IAM management installed on the same project.

## Configure a GCP MCP server

Every GCP MCP server follows the shared [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) flow. On the configuration screen you choose **GCP WIF federation** as the credential source and select the predefined definition for the service you want.

{% hint style="info" %}
A Federation provider maps one-to-one to a single Google Cloud project. To give agents access to more than one project, configure a separate MCP server for each project, using that project's GCP Workload Identity Federation provider as its Federation provider.
{% endhint %}

The service-specific configuration and the roles agents can request differ by server. Continue to the page for the server you are installing:

* [Compute Engine MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/compute)
* [Cloud Storage MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/storage)
* [BigQuery MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/bigquery)
* [Cloud Monitoring MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/monitoring)
* [IAM MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp/iam)

## Next steps

* Define the MCP roles and policies that determine which agents and users may call each GCP MCP server, and what they may do in Google Cloud.


# Compute Engine MCP server

Install the predefined Compute Engine MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the Google Cloud Compute Engine API.

The **Compute Engine** MCP server is a predefined [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) that exposes the Google Cloud Compute Engine API to agents, so they can inspect and manage Compute Engine resources such as VM instances, disks, and networks. It runs P0's Google Cloud MCP image behind the [P0 AI Gateway](/readme/agentic-control-plane) and bridges to Google's hosted Compute Engine MCP endpoint.

Agents can request Compute Engine access two ways, and they never hold Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation.

* **Resource-scoped** access to a single VM instance, with a role such as `compute.instanceAdmin.v1`. This is preferred, since it grants the least access.
* **Project-wide** access across every resource in the project, with a role such as `compute.viewer`. Use this only when the calling tools must span multiple resources.

{% hint style="info" %}
The Compute Engine MCP server is a preview feature. Contact P0 to enable it for your organization.
{% endhint %}

Before you start, complete the [GCP MCP server prerequisites](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp#prerequisites).

## Configure the Compute Engine MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:
   * **Credential source**: choose **GCP WIF federation**, then select the **Federation provider**: the GCP Workload Identity Federation identity you installed. The gateway uses this identity's audience when it federates to Google Cloud, and the provider's trust configuration authorizes it.
   * **Definition**: choose **P0**, then select **Google Cloud Compute** as the predefined server identifier.
2. The server now appears with the state **Installed**.

Once saved, P0 pushes the server definition to the gateway on its next sync, and the Compute Engine MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the Compute Engine MCP server, and what they may do in Compute Engine.


# Cloud Storage MCP server

Install the predefined Cloud Storage MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the Google Cloud Storage API.

The **Cloud Storage** MCP server is a predefined [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) that exposes the Google Cloud Storage API to agents, so they can work with buckets and objects. It runs P0's Google Cloud MCP image behind the [P0 AI Gateway](/readme/agentic-control-plane) and bridges to Google's hosted Cloud Storage MCP endpoint.

Agents can request Cloud Storage access two ways, and they never hold Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation.

* **Resource-scoped** access to a single bucket, with a role such as `storage.objectViewer` for read-only access or `storage.objectUser` for read/write access. Prefer this, since it grants the least access.
* **Project-wide** access across every resource in the project, with a role such as `storage.objectViewer`. Use this only when the calling tools must span multiple resources.

{% hint style="info" %}
The Cloud Storage MCP server is a preview feature. Contact P0 to enable it for your organization.
{% endhint %}

Before you start, complete the [GCP MCP server prerequisites](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp#prerequisites).

## Configure the Cloud Storage MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:
   * **Credential source**: choose **GCP WIF federation**, then select the **Federation provider**: the GCP Workload Identity Federation identity you installed. The gateway uses this identity's audience when it federates to Google Cloud, and the provider's trust configuration authorizes it.
   * **Definition**: choose **P0**, then select **Google Cloud Storage** as the predefined server identifier.
2. The server now appears with the state **Installed**.

Once saved, P0 pushes the server definition to the gateway on its next sync, and the Cloud Storage MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the Cloud Storage MCP server, and what they may do in Cloud Storage.


# BigQuery MCP server

Install the predefined BigQuery MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the Google Cloud BigQuery API.

The **BigQuery** MCP server is a predefined [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) that exposes the Google Cloud BigQuery API to agents, so they can work with datasets, tables, and query jobs. It runs P0's Google Cloud MCP image behind the [P0 AI Gateway](/readme/agentic-control-plane) and bridges to Google's hosted BigQuery MCP endpoint.

Agents can request BigQuery access two ways, and they never hold Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation.

* **Resource-scoped** access to a single dataset or table, with a role such as `bigquery.dataViewer`. Prefer this, since it grants the least access.
* **Project-wide** access across every resource in the project, with a role such as `bigquery.dataEditor`. Use this only when the calling tools must span multiple resources.

{% hint style="info" %}
The BigQuery MCP server is a preview feature. Contact P0 to enable it for your organization.
{% endhint %}

Before you start, complete the [GCP MCP server prerequisites](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp#prerequisites).

## Configure the BigQuery MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:
   * **Credential source**: choose **GCP WIF federation**, then select the **Federation provider**: the GCP Workload Identity Federation identity you installed. The gateway uses this identity's audience when it federates to Google Cloud, and the provider's trust configuration authorizes it.
   * **Definition**: choose **P0**, then select **Google Cloud BigQuery** as the predefined server identifier.
2. The server now appears with the state **Installed**.

Once saved, P0 pushes the server definition to the gateway on its next sync, and the BigQuery MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the BigQuery MCP server, and what they may do in BigQuery.


# Cloud Monitoring MCP server

Install the predefined Cloud Monitoring MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the Google Cloud Monitoring API.

The **Cloud Monitoring** MCP server is a predefined [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) that exposes the Google Cloud Monitoring API to agents, so they can query metrics and time series and inspect alerting policies and dashboards. It runs P0's Google Cloud MCP image behind the [P0 AI Gateway](/readme/agentic-control-plane) and bridges to Google's hosted Cloud Monitoring MCP endpoint.

Agents request access by Cloud Monitoring IAM role (for example, `monitoring.viewer`), scoped to a project and a business justification. They never hold Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation.

{% hint style="info" %}
The Cloud Monitoring MCP server is a preview feature. Contact P0 to enable it for your organization.
{% endhint %}

Before you start, complete the [GCP MCP server prerequisites](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp#prerequisites).

## Configure the Cloud Monitoring MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:
   * **Credential source**: choose **GCP WIF federation**, then select the **Federation provider**: the GCP Workload Identity Federation identity you installed. The gateway uses this identity's audience when it federates to Google Cloud, and the provider's trust configuration authorizes it.
   * **Definition**: choose **P0**, then select **Google Cloud Monitoring** as the predefined server identifier.
2. The server now appears with the state **Installed**.

Once saved, P0 pushes the server definition to the gateway on its next sync, and the Cloud Monitoring MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the Cloud Monitoring MCP server, and what they may do in Cloud Monitoring.


# IAM MCP server

Install the predefined IAM MCP server behind the P0 AI Gateway, giving agents policy-scoped, credential-free access to the Google Cloud IAM API.

The **IAM** MCP server is a predefined [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) that exposes the Google Cloud Identity and Access Management (IAM) API to agents. Its tools both read and change IAM: agents can inspect service accounts, keys, and roles, and can also create, modify, and delete them. Because Google does not host an MCP server for IAM, this is a P0-built server that implements its tools directly against the IAM API behind the [P0 AI Gateway](/readme/agentic-control-plane).

Agents request access by IAM role (for example, `iam.serviceAccountViewer`), scoped to a project and a business justification. They never hold Google Cloud credentials: the gateway obtains a short-lived, policy-scoped token for each session using Workload Identity Federation.

{% hint style="info" %}
The IAM MCP server is a preview feature. Contact P0 to enable it for your organization.
{% endhint %}

Before you start, complete the [GCP MCP server prerequisites](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp#prerequisites).

## What the tools can change

Many of this server's tools change IAM, not just read it:

* **Service accounts.** Create, update, delete, undelete, disable, and enable them.
* **Service account keys.** Upload, delete, disable, and enable them.
* **Custom roles.** Create, update, delete, and undelete them.
* **Service account IAM policy.** Set the policy that controls who may impersonate an account.

Two of these deserve particular care. Uploading a service account key creates a long-lived credential, and setting a service account's IAM policy changes who is allowed to act as that account.

What an agent can actually do is bounded by the IAM role it holds for the session, not by the list of tools. Every agent sees the same tools. Against a read-only role such as `iam.serviceAccountViewer`, the tools that change IAM are still listed, but each call fails. Against a role such as `iam.serviceAccountAdmin`, they succeed. So the role you approve in your [agentic access policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) is what limits an agent, and a policy written on the assumption that this server is read-only will grant more than it appears to.

## Configure the IAM MCP server

1. Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set the following fields, then click **Finish**:
   * **Credential source**: choose **GCP WIF federation**, then select the **Federation provider**: the GCP Workload Identity Federation identity you installed. The gateway uses this identity's audience when it federates to Google Cloud, and the provider's trust configuration authorizes it.
   * **Definition**: choose **P0**, then select **Google Cloud IAM** as the predefined server identifier.
2. The server now appears with the state **Installed**.

Once saved, P0 pushes the server definition to the gateway on its next sync, and the IAM MCP server becomes available to agents through the gateway URL. Access is granted per session and governed by your P0 policy; no long-lived Google Cloud credentials are issued.

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the IAM MCP server, and what they may do in Google Cloud IAM.


# Custom MCP server

Define and configure a custom MCP server behind the P0 AI Gateway when P0 does not ship a predefined server for it.

A **custom** MCP server is one you define yourself, for any MCP server P0 does not ship a [predefined](/integrations/resource-integrations/agentic-gateway/mcp-server) definition for. The gateway either proxies to a server hosted outside it or runs a Docker container to host the server's tools. Once configured, it behaves like any other server behind the [P0 AI Gateway](/readme/agentic-control-plane): agents reach it through the gateway URL, and every tool call is authenticated and checked against policy.

## Prerequisites

* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, with the gateway deployed in your environment.

## Configure the custom MCP server

Follow the shared steps in [Add an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server#add-an-mcp-server) to name the server and choose its gateway. On the configuration screen, set **Definition** to **Custom**, then choose a **Server type**.

<figure><img src="/files/rENk3fFyGiomqBhYCHZK" alt="Custom server definition form with Server type set to External, showing Server URL, Label, Logo URL, and Agent prompt fields"><figcaption></figcaption></figure>

### Server type

The **Server type** determines how the gateway reaches the server.

**External** proxies requests to an MCP server hosted outside the gateway, reachable at a fixed URL:

| Field          | Description                              | Required |
| -------------- | ---------------------------------------- | -------- |
| **Server URL** | URL of the externally hosted MCP server. | Yes      |
| **Label**      | Human-friendly label for this server.    | Yes      |

**Container** runs a Docker container behind the gateway to host the server's tools:

| Field                 | Description                                                                                          | Required |
| --------------------- | ---------------------------------------------------------------------------------------------------- | -------- |
| **Docker image**      | Image that hosts the MCP server.                                                                     | Yes      |
| **Docker entrypoint** | Container run entrypoint. Supports LiquidJS templating to inject request parameters and credentials. | Yes      |

### Server details

These fields apply to both server types:

| Field            | Description                                | Required |
| ---------------- | ------------------------------------------ | -------- |
| **Logo URL**     | Address of a logo image for the server.    | No       |
| **Agent prompt** | Text that describes this server to agents. | Yes      |

## Next steps

* Define the MCP roles and policies that determine which agents and users may call the custom MCP server, and what they may do.


# A2A bridge

Configure an A2A bridge behind the P0 AI Gateway so P0-managed agents reach upstream agents under runtime authorization policy.

The **A2A bridge** component places an upstream agent host behind the [P0 AI Gateway](/readme/agentic-control-plane) so that agent-to-agent (A2A) calls pass through the gateway. Where the [MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server) component fronts the MCP servers your agents call, the A2A bridge extends the same governance to A2A traffic.

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

## Prerequisites

* An existing P0 account at [p0.app](https://p0.app/).
* A registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component, and the gateway deployed in your environment. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway).
* The base URL where your upstream agents are hosted.

## Add an A2A bridge

1. Navigate to **Integrations** on [p0.app](https://p0.app) and select **Agentic gateway**, then choose the **A2A bridge** component.
2. Click **Add A2A bridge**.
3. Enter the bridge details, then finish the configuration:
   * **A2A bridge identifier**: a name for this bridge within P0. Use letters, digits, and hyphens only — the identifier becomes part of the gateway address that agents call.
   * **Gateway**: the [registered gateway](/integrations/resource-integrations/agentic-gateway/gateway) that fronts this bridge.
   * **Agent host**: the base URL at which your upstream agents are hosted. The gateway routes A2A calls to this URL.
4. The A2A bridge now appears in the component's list of configured bridges.

## How it works

Once a bridge is configured, P0 reconciles it to the gateway alongside your MCP servers. When a P0-managed agent makes an A2A call through the gateway, the gateway verifies the caller's identity, authorizes the call against the [policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) you define in P0, and logs it before forwarding the call to the **Agent host**.

The gateway supplies its own credential to the upstream agent host: a natively minted gateway session token. Unlike an MCP server, an A2A bridge has no AWS, GCP, or OAuth credential source to configure.

## Next steps

* Define the [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) that govern what agents can do through the bridge.
* Configure the [MCP servers](/integrations/resource-integrations/agentic-gateway/mcp-server) you want to expose behind the same gateway.


# Connect an MCP client

Register an MCP client and connect it to an MCP server configured behind the P0 AI Gateway.

This guide covers the path from a configured P0 AI Gateway to a working tool call. Configure an MCP server behind the gateway, register a client so your agent can authenticate, and connect to the server from Python.

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

## 1. Overview

### What this enables

Once you finish this guide, an agent you write can call the tools of an upstream MCP server through the gateway. The gateway sits in the data path. It authenticates the caller on every tool call, checks the call against the policies you set in P0, forwards it to the upstream server, and logs it. Your agent never holds upstream credentials, and no tool call reaches the upstream server unauthenticated.

### Prerequisites

* A P0 account at [p0.app](https://p0.app/).
* A deployed P0 AI Gateway. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway).
* A configured [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component in P0. The gateway must appear with the state **Installed** before an MCP server can point at it.
* Python 3.10 or later, for the client example.

## 2. Configure the MCP server

An MCP server configured behind the gateway becomes available to P0-managed agents across your organization.

### Configure in the console

1. Navigate to **Integrations** on [p0.app](https://p0.app), select **Agentic gateway**, then choose the **MCP server** component.
2. Click **Add server**.
3. Enter the server details, then click **Next**:
   * **Server identifier**: a name for this server within P0. This value also becomes the server's ID in the gateway URL, so note it. Step 4 refers to it as `SERVER_ID`.
   * **Gateway**: the [registered gateway](/integrations/resource-integrations/agentic-gateway/gateway) that hosts this server.
4. Set the **Credential source**, which selects how the gateway authenticates to the upstream:
   * [**AWS IAM federation**](/integrations/resource-integrations/aws/aws-oidc)
   * [**GCP WIF federation**](/integrations/resource-integrations/google-cloud/gcp-wif)
   * **OAuth**
5. Set the **Definition**, which selects what the server actually exposes:
   * **P0**, a predefined definition. Continue to [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws) or [GCP MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/gcp) for the fields specific to each.
   * **Custom**, a server you define yourself. See [Custom MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/custom).

For the full walkthrough of this component, including screenshots of each screen, see [MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server).

### Configure with Terraform

Use the P0 Terraform provider to configure an MCP server outside the console. The [`p0_agentic_server`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_server) resource takes the hosting gateway's `id`, the server's own `id`, a `definition` (`p0` or `custom`), and a `credential` source (`aws`, `gcp`, or `oauth`), the same choices as the console fields above.

* Registering the gateway itself is a separate step, covered in [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) for the console walkthrough.
* You can use Terraform to register and configure the Gateway component in P0 using the P0 Terraform provider [`p0_agentic_gateway`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_gateway)

### Inspect the configured servers

Read back the configured servers, which is the fastest way to confirm what the gateway serves:

```http
GET /o/{tenant}/agentic/servers
GET /o/{tenant}/agentic/servers/{serverId}
```

* `{tenant}`: your P0 tenant slug, the same value that appears in console URLs at `p0.app/o/{tenant}/...`.

These read calls accept a user token or an [API key](/p0-management/generating-an-api-key), passed as a bearer token in the `Authorization` header. See [Authenticating with the P0 API](/getting-started/authenticating-with-the-p0-api) for how to get either. This example uses an API key:

```bash
curl -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/$P0_TENANT/agentic/servers
```

Registering a client is different: it requires a user token and rejects an API key, covered in [Register the client](#3-register-the-client).

### What P0 creates after configuration

* The server becomes reachable at `{gatewayUrl}/mcps/{serverId}`, and this URL is where your client connects. `GET /o/{tenant}/agentic/servers` returns the exact `url` for each server in its JSON body.
* The gateway periodically syncs its configuration from P0 and reconciles the set of servers it hosts, adding newly configured servers and removing ones no longer configured. When a new server is installed, the gateway performs an ad-hoc sync so the server becomes available immediately.
* The server's tool catalog is synced to P0, which allows P0 to understand the tools exposed by the MCP server.

## 3. Register the client

A client is the identity your agent presents to the gateway. Registering one yields a client ID and a client secret.

This is the user-delegated path: the credentials identify your *application*, and a person still signs in through P0, so every tool call carries both the user's identity and the agent's. Use it for agents that act on behalf of a person. For an unattended workload with no human in the loop, use [JWT-SVID](/integrations/resource-integrations/agentic-gateway/spiffe-svid).

### Register in the console

1. Navigate to the Agentic page on [p0.app](https://p0.app) and open the MCP clients view.

<figure><img src="/files/XhF2jr6tOGZou5fmTVLI" alt="MCP clients view in the Agentic page, showing the button to register a new client"><figcaption></figcaption></figure>

2. Register a new client, supplying its platform and redirect URI.
   * For Claude Code, the redirect URI depends on how you configure the MCP client. If you use `claude mcp add`, Claude Code uses a dynamically assigned local callback port unless you configure a fixed port.
   * For a custom agent application, provide the callback URL exposed by your application's OAuth implementation.

<figure><img src="/files/TO678wGQTsrP0svwOpMR" alt="Client registration form with fields for platform and redirect URI"><figcaption></figcaption></figure>

3. Copy the **client ID** and **client secret**.

{% hint style="danger" %}
The client secret is shown once, at registration. Store it securely before leaving the page. If you lose it, register a new client.
{% endhint %}

### Register through the API

To register a client outside the console, `POST` to the clients endpoint. Set `type` to `client_credential_post` for the confidential client this guide uses. The response contains the created client, including its `secret`, which P0 returns only once.

Registration requires a user token: a bearer token from a session signed in as a P0 user. An API key doesn't identify a user, so P0 rejects it. The [P0 CLI token](/getting-started/authenticating-with-the-p0-api#p0-cli-token) method in the authentication guide prints one from your login session.

```bash
curl -X POST \
  -H "Authorization: Bearer $P0_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "client_credential_post",
    "platform": "custom",
    "hostname": "my-agent.internal",
    "redirectUri": "http://localhost:8080/callback",
    "version": "1.0.0"
  }' \
  https://api.p0.app/o/$P0_TENANT/agentic/clients
```

For the request and response fields, the federated-identity variant, authentication for each endpoint, and the endpoints to list and update registered clients, see the [Agentic client registration API](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client/client-registration-api) reference.

## 4. Connect from Python

This example uses the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk).

```bash
pip install mcp
```

### Where each value goes

| Value                        | Where it comes from                                                                                                         | Used as                     |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `GATEWAY_URL`                | The **Agentic gateway URL** you set on the [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component | Base of the server URL      |
| `SERVER_ID`                  | The **Server identifier** from step 2                                                                                       | Path segment under `/mcps/` |
| `CLIENT_ID`, `CLIENT_SECRET` | Step 3                                                                                                                      | Client authentication       |
| `REDIRECT_URI`               | Step 3, and must match the registered value exactly                                                                         | Where sign-in returns       |

Your client connects to `{GATEWAY_URL}/mcps/{SERVER_ID}`. The access token must use that same URL as its audience. Copy the exact URL from the `url` field that `GET /o/{tenant}/agentic/servers` returns for your server.

### Where the authorization code arrives

`REDIRECT_URI` is where P0 sends the browser once the user signs in, with the authorization code on the query string. For an agent that runs locally, nothing is listening at that address until your process opens the port. This is the same loopback pattern Claude Code uses, and the port and path must match what you registered in step 3 exactly.

Your code is responsible for one thing: receiving that authorization code and handing it to the SDK. `OAuthClientProvider` does the rest, exchanging the code for an access token and attaching the token to every request, so your code never handles the token itself.

### The client

{% hint style="warning" %}
This example shows how to configure an MCP client using a P0-registered client ID and client secret. Replace the example variables with your client details.

The `CallbackServer` below is the loopback listener for a locally run agent. If your agent runs behind a web server instead, register that server's callback route in step 3 instead of a `localhost` URI. Have the route pass the authorization code to `callback_handler` instead of running this listener.
{% endhint %}

{% hint style="info" %}
The snippet below targets MCP Python SDK **v2**, whose import paths differ from v1. In v2 the transport helper is `streamable_http_client`, not `streamablehttp_client`, and `callback_handler` returns an `AuthorizationCodeResult` rather than v1's `(code, state)` tuple. Check your installed version with `pip show mcp`.
{% endhint %}

```python
import asyncio
import queue
import threading
import urllib.parse
import webbrowser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

from mcp import ClientSession
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken

GATEWAY_URL = "https://gateway.example.com"
SERVER_ID = "gcp-compute"
CLIENT_ID = "<client id from step 3>"
CLIENT_SECRET = "<client secret from step 3>"
REDIRECT_URI = "http://localhost:8080/callback"

SERVER_URL = f"{GATEWAY_URL}/mcps/{SERVER_ID}"


class P0ClientStorage(TokenStorage):
    """Supplies the client P0 issued in step 3.

    Returning client info from get_client_info stops the SDK from registering a
    client of its own, so the gateway sees the client you registered with P0.
    Tokens are held in memory here; persist them to reuse a session.
    """

    def __init__(self) -> None:
        self._tokens: OAuthToken | None = None

    async def get_client_info(self) -> OAuthClientInformationFull | None:
        return OAuthClientInformationFull(
            client_id=CLIENT_ID,
            client_secret=CLIENT_SECRET,
            redirect_uris=[REDIRECT_URI],
            token_endpoint_auth_method="client_secret_post",
            grant_types=["authorization_code", "refresh_token"],
            response_types=["code"],
        )

    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
        return None

    async def get_tokens(self) -> OAuthToken | None:
        return self._tokens

    async def set_tokens(self, tokens: OAuthToken) -> None:
        self._tokens = tokens


class CallbackServer:
    """Listens on REDIRECT_URI for the authorization response from P0.

    P0 redirects the browser here after sign-in with the authorization code on
    the query string. The code is handed to OAuthClientProvider, which exchanges
    it for an access token; this class never sees the token.
    """

    def __init__(self, redirect_uri: str) -> None:
        url = urllib.parse.urlparse(redirect_uri)
        self._path = url.path
        self._results: queue.Queue[AuthorizationCodeResult] = queue.Queue()
        outer = self

        class Handler(BaseHTTPRequestHandler):
            def log_message(self, *args: object) -> None:
                pass

            def do_GET(self) -> None:
                path, _, query = self.path.partition("?")
                if path != outer._path:
                    self.send_response(404)
                    self.send_header("Content-Length", "0")
                    self.end_headers()
                    return

                params = urllib.parse.parse_qs(query)
                body = b"Authorization complete. You can close this tab."
                self.send_response(200)
                self.send_header("Content-Type", "text/plain")
                self.send_header("Content-Length", str(len(body)))
                self.end_headers()
                self.wfile.write(body)

                outer._results.put(
                    AuthorizationCodeResult(
                        code=params.get("code", [""])[0],
                        state=params.get("state", [None])[0],
                        iss=params.get("iss", [None])[0],
                    )
                )

        self._server = ThreadingHTTPServer(
            (url.hostname or "localhost", url.port or 80), Handler
        )

    def __enter__(self) -> "CallbackServer":
        threading.Thread(target=self._server.serve_forever, daemon=True).start()
        return self

    def __exit__(self, *exc: object) -> None:
        self._server.shutdown()
        self._server.server_close()

    async def wait(self) -> AuthorizationCodeResult:
        """Blocks until P0 redirects the browser to REDIRECT_URI."""
        return await asyncio.to_thread(self._results.get)


async def main() -> None:
    with CallbackServer(REDIRECT_URI) as callback_server:

        async def redirect_handler(authorization_url: str) -> None:
            """Sends the user to P0 to sign in."""
            print("open this URL to sign in:", authorization_url)
            webbrowser.open(authorization_url)

        auth = OAuthClientProvider(
            server_url=SERVER_URL,
            client_metadata=OAuthClientMetadata(
                client_name="my-agent",
                redirect_uris=[REDIRECT_URI],
                grant_types=["authorization_code", "refresh_token"],
                response_types=["code"],
                token_endpoint_auth_method="client_secret_post",
            ),
            storage=P0ClientStorage(),
            redirect_handler=redirect_handler,
            callback_handler=callback_server.wait,
        )

        async with create_mcp_http_client(auth=auth) as http_client:
            async with streamable_http_client(SERVER_URL, http_client=http_client) as (
                read,
                write,
            ):
                async with ClientSession(read, write) as session:
                    await session.initialize()

                    tools = await session.list_tools()
                    print("tools:", [tool.name for tool in tools.tools])


asyncio.run(main())
```

## 5. Verify the connection

1. **Confirm the session opened.** `session.initialize()` returns without raising.
2. **Confirm that the gateway serves the server.** Call `list_tools()` and confirm the server's tools come back. For a P0 predefined server you also see the access tools `access`, `check`, `list`, and `relinquish`. An empty tool list means the gateway has no definition for this server. See Troubleshooting below.

   ```python
   tools = await session.list_tools()
   print("tools:", [tool.name for tool in tools.tools])
   ```
3. **Confirm P0 saw it.** The `list_tools` call appears in the gateway's activity, which you can read with `GET /o/{tenant}/agentic/activity`. The gateway also emits an `auth.verified` audit event recording the subject and audience of the token it accepted.

{% hint style="info" %}
Calling a tool, rather than listing tools, additionally requires an [Agentic Access Policy](/access-management/just-in-time-access/access-policies/agentic-access-policies) that permits the call. Listing tools needs no policy.
{% endhint %}

## 6. Troubleshooting

| Symptom                                 | Cause                                                                | Fix                                                                                                                                                                                                      |
| --------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `404` on the MCP endpoint               | Wrong gateway URL, or `SERVER_ID` does not match a configured server | Copy the `url` field for your server from `GET /o/{tenant}/agentic/servers`. The gateway resolves the server before it authenticates, so an unknown server answers `404` and never returns a `401`.      |
| Tool list is empty, no error            | The gateway has no definition for this server                        | Installing a server triggers an immediate sync, so this is rarely a timing problem. Confirm the server appears in `GET /o/{tenant}/agentic/servers` and that `SERVER_ID` matches its identifier exactly. |
| `401` with `WWW-Authenticate: Bearer`   | No token, or the token failed verification                           | Confirm your client obtained a token, and that its audience is `{gatewayUrl}/mcps/{serverId}`.                                                                                                           |
| `invalid_client` at the token endpoint  | Client ID or secret wrong, or client disabled                        | Check the client in `GET /o/{tenant}/agentic/clients`. A client's `status` must be `active`.                                                                                                             |
| Sign-in returns a redirect URI mismatch | The `redirectUri` sent does not exactly match the registered one     | They must match exactly, including port and path.                                                                                                                                                        |
| A `2xx` response that is not JSON       | You are talking to an SSO proxy or an app front end, not the gateway | Point at the gateway host, not the P0 console or P0 API. A proxy answers `200` with an HTML login page.                                                                                                  |
| Token rejected with an audience error   | The audience is the bare gateway host instead of the per-server URL  | The token audience must be `{gatewayUrl}/mcps/{serverId}`. See the audience section in [JWT-SVID](/integrations/resource-integrations/agentic-gateway/spiffe-svid).                                      |

## Related

* [Agentic client registration API](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client/client-registration-api): the API reference for registering, listing, and updating clients.
* [JWT-SVID](/integrations/resource-integrations/agentic-gateway/spiffe-svid): connect an unattended workload instead of a user-delegated client.
* [MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server): the full component walkthrough, with screenshots.
* [Gateway](/integrations/resource-integrations/agentic-gateway/gateway): register a gateway deployment with P0.
* [Use MCP servers with Claude Code](/integrations/resource-integrations/agentic-gateway/using-mcp-servers): connect Claude Code instead of a client you wrote.
* [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies): govern what agents may do.


# Agentic client registration API

Register, list, and update the MCP clients that authenticate to a server behind the P0 AI Gateway.

Register the MCP clients that authenticate to a server behind the [P0 AI Gateway](/integrations/resource-integrations/agentic-gateway/gateway), list the clients you have already registered, and update a client's display name or status. A client is the identity your agent presents to the gateway.

Use these endpoints to script client registration, to audit the clients already registered, and to rename or disable a client after registration. For the end-to-end walkthrough that includes the console path and a Python client example, see [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client).

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

## Authentication

Pass a bearer token on every call. `{orgId}` is your P0 tenant slug, the same value that appears in console URLs at `p0.app/o/{orgId}/...`.

The endpoints authenticate differently:

* **`POST /clients` (register)** requires a user token: a bearer token from a session signed in as a P0 user, because P0 derives the client owner from the caller's identity. P0 rejects an API key with `400`. Register through the console, or get a user token from the [P0 CLI](/getting-started/authenticating-with-the-p0-api#p0-cli-token).
* **`GET /clients` (list)** accepts a user token or an [API key](/p0-management/generating-an-api-key).
* **`PATCH /clients/{clientId}` (update)** accepts a user token or an [API key](/p0-management/generating-an-api-key), because an API key always acts as owner.

```bash
# List clients with an API key.
curl -H "Authorization: Bearer $P0_API_TOKEN" \
  https://api.p0.app/o/$P0_TENANT/agentic/clients
```

Registering a client requires the `agentic.client.create` permission, held by the viewer, manager, and owner roles. Viewer is the base role, so any user in your sign-in domain can register. Listing requires `agentic.client.read`, held by the iamViewer, iamOwner, manager, and owner roles, so a user who can register cannot necessarily list. Updating requires `agentic.client.update`, held by the manager and owner roles.

## Client types

The `type` field selects which kind of client you register:

* `client_credential_post`: a confidential client that authenticates with a client secret P0 issues. This is the user-delegated path used by [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client). The secret is returned only once, in the registration response.
* `jwt_bearer`: a federated agent identity that authenticates with a token from an installed [identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider). P0 does not issue a secret. Register these only when dynamic registration is disabled for the provider. See [Register an agent identity](/integrations/resource-integrations/agentic-gateway/identity-provider#register-an-agent-identity).

{% hint style="danger" %}
A confidential client's secret is shown once, in the registration response. Store it securely before discarding the response. If you lose it, register a new client.
{% endhint %}

## Update a client

`PATCH /clients/{clientId}` changes a client's display name or status. This works for both confidential and federated clients. Send at least one of the two fields below and nothing else; an empty request returns `400`, and so does an unrecognized field. The response is the updated client, with secrets removed.

* **`displayName`**: a human-friendly name for the client, no longer than 100 characters. P0 trims leading and trailing whitespace, but measures the limit against the name as sent, before trimming. Send `null` to clear the saved name. A blank or whitespace-only name returns `400`, and so does one over the limit.
* **`status`**: `active` or `disabled`. Set it to `disabled` to stop the client from authenticating and refreshing, or back to `active` to restore it.

URL-encode `{clientId}`, because federated identity IDs contain slashes. If no client matches, P0 returns `404`.

```bash
# Rename a client and disable it. jq URL-encodes the client ID.
curl -X PATCH \
  -H "Authorization: Bearer $P0_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Build agent", "status": "disabled"}' \
  https://api.p0.app/o/$P0_TENANT/agentic/clients/$(jq -rn --arg v "$CLIENT_ID" '$v|@uri')
```

## API reference

{% file src="/files/yPjHMJcLyfD21jFYMPsT" %}

## Register an MCP client

> Registers a client that an agent presents to the gateway. Requires the \`agentic.client.create\` permission. For a confidential client, the response contains a \`secret\` that is returned only once. Select the variant with the \`type\` field.

```json
{"openapi":"3.0.4","info":{"title":"P0 Agentic Client Registration API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}/agentic","variables":{"orgId":{"default":"demo-org","description":"The P0 tenant slug, the same value that appears in console URLs at p0.app/o/{orgId}."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"schemas":{"CreateClientRequest":{"oneOf":[{"$ref":"#/components/schemas/ConfidentialClientCreateRequest"},{"$ref":"#/components/schemas/FederatedClientCreateRequest"}],"discriminator":{"propertyName":"type","mapping":{"client_credential_post":"#/components/schemas/ConfidentialClientCreateRequest","jwt_bearer":"#/components/schemas/FederatedClientCreateRequest"}}},"ConfidentialClientCreateRequest":{"type":"object","description":"Register a confidential client that authenticates with a client secret P0 issues.","required":["type","hostname","platform","redirectUri","version"],"properties":{"type":{"type":"string","enum":["client_credential_post"],"description":"Selects the confidential-client variant."},"platform":{"type":"string","enum":["claude-code","claude-agents","custom"],"description":"The platform the client runs on. Use `custom` for an agent you wrote yourself."},"redirectUri":{"type":"string","description":"Where P0 sends the user back after sign-in. Must match exactly what the client sends, including port and path."},"hostname":{"type":"string","description":"The host the client runs on. For display only."},"version":{"type":"string","description":"The client's version string. For display only."}}},"FederatedClientCreateRequest":{"type":"object","description":"Pre-register an agent identity against an installed identity provider. Use this variant when dynamic registration is disabled for the provider. P0 does not issue a secret for these identities.","required":["type","providerId","identity"],"properties":{"type":{"type":"string","enum":["jwt_bearer"],"description":"Selects the federated-identity variant."},"providerId":{"type":"string","description":"The ID of the installed identity-provider component that approves the identity."},"identity":{"type":"string","description":"The identity in the agent's token. Use the `external_sub` claim if the identity provider sets one. If not, use the verified email, and then the `sub` claim."}}},"CreateClientResponse":{"type":"object","properties":{"client":{"oneOf":[{"$ref":"#/components/schemas/ConfidentialClientWithSecret"},{"$ref":"#/components/schemas/FederatedClient"}]}}},"ConfidentialClientWithSecret":{"allOf":[{"$ref":"#/components/schemas/ConfidentialClient"},{"type":"object","properties":{"secret":{"type":"string","description":"The client secret. Returned only in the registration response and never retrievable later."}}}]},"ConfidentialClient":{"type":"object","description":"A registered confidential client.","required":["id","createdBy","hostname","platform","redirectUri","type","version"],"properties":{"id":{"type":"string","description":"The client ID. Present in the redacted list and in the create response."},"type":{"type":"string","enum":["client_credential_post"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the client was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"platform":{"type":"string","enum":["claude-code","claude-agents","custom"]},"redirectUri":{"type":"string"},"hostname":{"type":"string"},"version":{"type":"string"},"status":{"type":"string","enum":["active","disabled"],"description":"Whether the client may currently authenticate."}}},"CreatedBy":{"type":"object","description":"The user who registered the client, or the gateway that registered it through dynamic registration.","properties":{"user":{"type":"string","description":"Email of the P0 user who registered the client."},"ip":{"type":"string","description":"IP address the registration request came from, when known."},"gateway":{"type":"string","description":"Gateway that registered the client through dynamic registration."}}},"FederatedClient":{"type":"object","description":"A registered federated (JWT bearer) client identity.","required":["id","createdBy","issuer","providerId","type"],"properties":{"id":{"type":"string","description":"The client subject the gateway matches on."},"type":{"type":"string","enum":["jwt_bearer"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the identity was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"providerId":{"type":"string","description":"The identity-provider component that validated the identity."},"issuer":{"type":"string","description":"The trust authority issuer URL."},"status":{"type":"string","enum":["active","disabled"]}}},"Error":{"type":"object","description":"The body returned with every failed request.","properties":{"error":{"type":"string","description":"A human-readable description of what went wrong."},"errorState":{"type":"object","description":"Structured detail about the failure, present on validation and state errors. Absent on errors that carry no structured state."}}}},"responses":{"BadRequestError":{"description":"The request is invalid, for example a missing `type` discriminator, a missing required field, a `platform` outside the allowed values, a blank `identity`, an update with no fields or an unrecognized field, or a `displayName` that is blank or longer than 100 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller lacks the required permission: `agentic.client.create` to register, `agentic.client.read` to list, or `agentic.client.update` to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ConflictError":{"description":"An identity with the same subject is already registered for the provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotInstalledError":{"description":"The `providerId` does not match an installed identity-provider component. Applies to federated (`jwt_bearer`) registration only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/clients":{"post":{"summary":"Register an MCP client","description":"Registers a client that an agent presents to the gateway. Requires the `agentic.client.create` permission. For a confidential client, the response contains a `secret` that is returned only once. Select the variant with the `type` field.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientRequest"}}}},"responses":{"200":{"description":"The registered client. Confidential clients include the one-time `secret`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"$ref":"#/components/responses/ConflictError"},"422":{"$ref":"#/components/responses/NotInstalledError"}}}}}}
```

## List registered MCP clients

> Returns every registered client, ordered newest first. Requires the \`agentic.client.read\` permission. Secrets are never returned.

```json
{"openapi":"3.0.4","info":{"title":"P0 Agentic Client Registration API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}/agentic","variables":{"orgId":{"default":"demo-org","description":"The P0 tenant slug, the same value that appears in console URLs at p0.app/o/{orgId}."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"schemas":{"ListClientsResponse":{"type":"object","properties":{"clients":{"type":"array","description":"The registered clients, ordered newest first. Secrets are never returned.","items":{"$ref":"#/components/schemas/ClientListItem"}}}},"ClientListItem":{"oneOf":[{"$ref":"#/components/schemas/ConfidentialClient"},{"$ref":"#/components/schemas/FederatedClient"}]},"ConfidentialClient":{"type":"object","description":"A registered confidential client.","required":["id","createdBy","hostname","platform","redirectUri","type","version"],"properties":{"id":{"type":"string","description":"The client ID. Present in the redacted list and in the create response."},"type":{"type":"string","enum":["client_credential_post"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the client was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"platform":{"type":"string","enum":["claude-code","claude-agents","custom"]},"redirectUri":{"type":"string"},"hostname":{"type":"string"},"version":{"type":"string"},"status":{"type":"string","enum":["active","disabled"],"description":"Whether the client may currently authenticate."}}},"CreatedBy":{"type":"object","description":"The user who registered the client, or the gateway that registered it through dynamic registration.","properties":{"user":{"type":"string","description":"Email of the P0 user who registered the client."},"ip":{"type":"string","description":"IP address the registration request came from, when known."},"gateway":{"type":"string","description":"Gateway that registered the client through dynamic registration."}}},"FederatedClient":{"type":"object","description":"A registered federated (JWT bearer) client identity.","required":["id","createdBy","issuer","providerId","type"],"properties":{"id":{"type":"string","description":"The client subject the gateway matches on."},"type":{"type":"string","enum":["jwt_bearer"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the identity was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"providerId":{"type":"string","description":"The identity-provider component that validated the identity."},"issuer":{"type":"string","description":"The trust authority issuer URL."},"status":{"type":"string","enum":["active","disabled"]}}},"Error":{"type":"object","description":"The body returned with every failed request.","properties":{"error":{"type":"string","description":"A human-readable description of what went wrong."},"errorState":{"type":"object","description":"Structured detail about the failure, present on validation and state errors. Absent on errors that carry no structured state."}}}},"responses":{"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller lacks the required permission: `agentic.client.create` to register, `agentic.client.read` to list, or `agentic.client.update` to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/clients":{"get":{"summary":"List registered MCP clients","description":"Returns every registered client, ordered newest first. Requires the `agentic.client.read` permission. Secrets are never returned.","responses":{"200":{"description":"The registered clients.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListClientsResponse"}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"}}}}}}
```

## Update an MCP client

> Updates a registered client's display name or status. Requires the \`agentic.client.update\` permission. Include at least one field; an empty request is rejected. Returns the updated client. Secrets are never returned.

```json
{"openapi":"3.0.4","info":{"title":"P0 Agentic Client Registration API","version":"1.0.0"},"servers":[{"url":"https://api.p0.app/o/{orgId}/agentic","variables":{"orgId":{"default":"demo-org","description":"The P0 tenant slug, the same value that appears in console URLs at p0.app/o/{orgId}."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key"}},"schemas":{"UpdateClientRequest":{"type":"object","description":"Update a registered client's display name or status. Include at least one field; an empty object is rejected. These are the only fields accepted; any other field is rejected.","minProperties":1,"additionalProperties":false,"properties":{"displayName":{"type":"string","nullable":true,"minLength":1,"maxLength":100,"pattern":"\\S","description":"A human-friendly client name, no longer than 100 characters. P0 trims leading and trailing whitespace, but measures the limit against the value as sent, before trimming. Send `null` to clear the saved value. A blank or whitespace-only value is rejected."},"status":{"type":"string","enum":["active","disabled"],"description":"Whether the client may authenticate and refresh. Set to `disabled` to stop the client from authenticating."}}},"UpdateClientResponse":{"type":"object","required":["client"],"properties":{"client":{"$ref":"#/components/schemas/ClientListItem"}}},"ClientListItem":{"oneOf":[{"$ref":"#/components/schemas/ConfidentialClient"},{"$ref":"#/components/schemas/FederatedClient"}]},"ConfidentialClient":{"type":"object","description":"A registered confidential client.","required":["id","createdBy","hostname","platform","redirectUri","type","version"],"properties":{"id":{"type":"string","description":"The client ID. Present in the redacted list and in the create response."},"type":{"type":"string","enum":["client_credential_post"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the client was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"platform":{"type":"string","enum":["claude-code","claude-agents","custom"]},"redirectUri":{"type":"string"},"hostname":{"type":"string"},"version":{"type":"string"},"status":{"type":"string","enum":["active","disabled"],"description":"Whether the client may currently authenticate."}}},"CreatedBy":{"type":"object","description":"The user who registered the client, or the gateway that registered it through dynamic registration.","properties":{"user":{"type":"string","description":"Email of the P0 user who registered the client."},"ip":{"type":"string","description":"IP address the registration request came from, when known."},"gateway":{"type":"string","description":"Gateway that registered the client through dynamic registration."}}},"FederatedClient":{"type":"object","description":"A registered federated (JWT bearer) client identity.","required":["id","createdBy","issuer","providerId","type"],"properties":{"id":{"type":"string","description":"The client subject the gateway matches on."},"type":{"type":"string","enum":["jwt_bearer"]},"displayName":{"type":"string","description":"A human-friendly client name. Present only when one has been set."},"createdAt":{"type":"integer","format":"int64","description":"Epoch milliseconds when the identity was registered."},"createdBy":{"$ref":"#/components/schemas/CreatedBy"},"providerId":{"type":"string","description":"The identity-provider component that validated the identity."},"issuer":{"type":"string","description":"The trust authority issuer URL."},"status":{"type":"string","enum":["active","disabled"]}}},"Error":{"type":"object","description":"The body returned with every failed request.","properties":{"error":{"type":"string","description":"A human-readable description of what went wrong."},"errorState":{"type":"object","description":"Structured detail about the failure, present on validation and state errors. Absent on errors that carry no structured state."}}}},"responses":{"BadRequestError":{"description":"The request is invalid, for example a missing `type` discriminator, a missing required field, a `platform` outside the allowed values, a blank `identity`, an update with no fields or an unrecognized field, or a `displayName` that is blank or longer than 100 characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnauthorizedError":{"description":"The caller could not be authenticated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ForbiddenError":{"description":"The caller lacks the required permission: `agentic.client.create` to register, `agentic.client.read` to list, or `agentic.client.update` to update.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFoundError":{"description":"No client with the given `clientId` is registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/clients/{clientId}":{"patch":{"summary":"Update an MCP client","description":"Updates a registered client's display name or status. Requires the `agentic.client.update` permission. Include at least one field; an empty request is rejected. Returns the updated client. Secrets are never returned.","parameters":[{"name":"clientId","in":"path","required":true,"description":"The client ID. URL-encode it, because federated identity IDs contain slashes.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClientRequest"}}}},"responses":{"200":{"description":"The updated client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClientResponse"}}}},"400":{"$ref":"#/components/responses/BadRequestError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"$ref":"#/components/responses/NotFoundError"}}}}}}
```

## Related

* [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client): the full walkthrough, including the console path and a Python client example.
* [Identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider): register federated agent identities.
* [JWT-SVID](/integrations/resource-integrations/agentic-gateway/spiffe-svid): connect an unattended workload instead of a user-delegated client.


# JWT-SVID

Enroll a SPIFFE identity provider with P0, and authenticate an unattended workload to the P0 AI Gateway with an existing JWT-SVID from Python.

This guide covers authenticating an unattended agent to the [P0 AI Gateway](/readme/agentic-control-plane) with a [SPIFFE JWT-SVID](https://spiffe.io/docs/latest/spiffe-specs/jwt-svid/): enrolling the identity provider that issues your JWT-SVIDs, setting the audience values correctly, and presenting a JWT-SVID from Python.

{% hint style="info" %}
The Agentic Gateway integration is available as an opt-in capability. Contact P0 to enable it for your organization.
{% endhint %}

## 1. Overview

### When to use JWT-SVID authentication

Use JWT-SVID authentication when your agent runs as a **workload rather than on behalf of a person**: a service, a batch job, a pod in your cluster. There is no browser and no one to sign in, so the agent authenticates as itself using the identity your SPIFFE infrastructure already gives it.

If a person is present and the agent acts for them, use the client ID and secret path in [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client) instead.

### What this guide assumes

This guide assumes **your workload can already obtain a JWT-SVID**. Issuing, minting, rotating, and mounting SVIDs is the job of your SPIFFE infrastructure, and is out of scope here. What follows covers only what P0 requires.

An SVID is a SPIFFE-compatible format for presenting a **SPIFFE ID**, the identifier for a workload. SPIFFE defines several SVID formats, each with its own conventions. In a JWT-SVID the SPIFFE ID is carried as the `sub` claim.

{% hint style="warning" %}
P0 requires the **JWT-SVID** format. SPIFFE also defines X509-SVID and WIT-SVID, and neither can be presented here. If your platform issues X509-SVIDs by default, or exchanges them through an internal service, you must obtain the JWT-SVID form for use with the gateway.
{% endhint %}

### What must already exist

* A deployed P0 AI Gateway and a registered [Gateway](/integrations/resource-integrations/agentic-gateway/gateway) component.
* At least one [MCP server configured](/integrations/resource-integrations/agentic-gateway/mcp-server) behind that gateway. Note its server identifier.
* A JWT-SVID available to your workload, and the **issuer URL** of whatever issues it. The issuer must serve an OpenID Connect discovery document whose issuer value matches your configured issuer exactly, and which advertises a `jwks_uri` where the signing keys are published. For an issuer with no path, that document is at `{issuer}/.well-known/openid-configuration`; for an issuer that carries a path, the `.well-known` segment goes between the origin and the path, as `{origin}/.well-known/openid-configuration{issuer-path}` (per RFC 8414). The `jwks_uri` may be on a different host. **The OAuth server** fetches both, so the issuer must be reachable from it over HTTPS at a publicly resolvable address. Private ranges and redirects are both rejected.
* An issuer that signs with **RS256 or ES256**. These are the only algorithms a gateway accepts by default.
* Python 3.10 or later.

## 2. How the authentication flow works

Four steps, of which your code performs two:

1. **Your workload obtains a JWT-SVID.** Handled by your SPIFFE infrastructure, typically written to a file that a sidecar or CSI driver keeps rotated. Your code reads it.
2. **Your workload presents the JWT-SVID to P0.** It posts the JWT-SVID to the gateway's OAuth server token endpoint as a JWT bearer assertion, naming the MCP server it wants to reach.
3. **P0 validates it.** The OAuth server verifies the JWT-SVID's signature against the issuer's published keys, checks the JWT-SVID's audience, and asks P0 whether the issuer is enrolled and whether the token's audience and subject satisfy that enrollment's patterns.
4. **The gateway's authorization server issues an access token.** On success the OAuth server returns a short-lived access token scoped to that one MCP server. Your client presents that token, not the JWT-SVID, on every MCP request.

The important consequence of step 4: **the JWT-SVID is exchanged once for an access token, and the access token is what the gateway sees.** These are two different tokens, and the `Authorization: Bearer` header on your MCP requests carries the access token.

## 3. Enroll the SPIFFE identity provider in P0

Enrolling an identity provider tells P0 that JWTs from a given issuer may authenticate agents to the gateway. Follow [Identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider) to enroll one, then set its fields for a JWT-SVID as follows.

| Field                | What it must match for a JWT-SVID                                                                                                          |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Issuer**           | The issuer URL of your SPIFFE infrastructure, matching the JWT-SVID's `iss` claim. Compared for exact string equality, never as a pattern. |
| **Audience pattern** | The JWT-SVID's `aud` claim, which must be the gateway hostname. See [step 4](#4-configure-the-audience).                                   |
| **Subject pattern**  | The JWT-SVID's `sub` claim, which is the workload's SPIFFE ID, for example `spiffe://example.org/ns/prod/sa/my-agent`.                     |

{% hint style="danger" %}
**Audience pattern and subject pattern are regular expressions, and they are not anchored for you.** They are compiled as regular expressions and tested against the claim, so a pattern matches anywhere in the value unless you anchor it.

`spiffe://example.org` as a subject pattern also matches `spiffe://example.org.attacker.example/agent`, because the pattern is unanchored and `.` matches any character. Anchor both ends and escape dots:

* Too permissive: `spiffe://example.org`
* Correct: `^spiffe://example\.org/.+$`

Glob syntax does not work. A bare `*` is not a wildcard here and is not a valid regular expression on its own.
{% endhint %}

### What this configuration is, and is not

{% hint style="info" %}
The identity provider configuration is a **coarse authentication filter, not a set of per-agent rules.** It establishes that an issuer is trusted and that a token's audience and subject fall within accepted patterns. It does not say which agent may do what.
{% endhint %}

Binding a verified identity to a specific agent happens on the client registration, not here. See [Register an agent identity](/integrations/resource-integrations/agentic-gateway/identity-provider#register-an-agent-identity). Authorization over tool calls lives in [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies). Do not attempt to restrict individual agents in the identity provider's patterns.

Whether the provider automatically registers a matching agent depends on the provider's **Dynamic registration** setting. With it disabled, you must register each agent identity before P0 accepts that agent's JWT-SVID.

The check is also **tenant-wide**. A token passes if *any* enrolled identity provider matches it.

{% hint style="info" %}
To manage Agentic Gateway configuration as code, use the P0 Terraform provider's [`p0_agentic_gateway`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_gateway) and [`p0_agentic_gateway_staged`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_gateway_staged) resources.
{% endhint %}

## 4. Configure the audience

**There are two different audience values in this flow, and they're not the same.** Setting both to the same value is the most common reason a correctly issued JWT-SVID is rejected.

| Audience                                          | Value                                                           | Where it is set                                                                                                                                          |
| ------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **The JWT-SVID's own `aud` claim**                | The gateway hostname, for example `https://gateway.example.com` | Set by whatever issues the JWT-SVID. Must match the OAuth server's configured assertion audience, and must satisfy the **Audience pattern** from step 3. |
| **The `audience` parameter on the token request** | The per-server URL, `{gatewayUrl}/mcps/{serverId}`              | Set by your code, in the token request in step 5.                                                                                                        |

{% hint style="danger" %}
The JWT-SVID's audience is the **gateway hostname**. The token request's audience is the **specific MCP server URL**, which is the gateway hostname suffixed with the `/mcps/{serverId}` path.

Using the bare hostname as the token request audience yields an access token the gateway's data plane rejects. Using the per-server URL as the JWT-SVID audience causes validation to fail before a token is ever issued.
{% endhint %}

So for a gateway at `https://gateway.example.com` serving a server with identifier `gcp-compute`:

* The JWT-SVID must be issued with `aud` of `https://gateway.example.com`.
* The **Audience pattern** in step 3 must match that, for example `^https://gateway\.example\.com$`.
* The token request must send `audience=https://gateway.example.com/mcps/gcp-compute`.

## 5. Connect from Python

```bash
pip install mcp
```

`httpx2` is installed alongside the MCP SDK, so the token request needs no additional dependency. Note that v2 of the SDK depends on `httpx2`, not `httpx` — the two are separate packages and their types are not interchangeable.

{% hint style="info" %}
The following snippet targets MCP Python SDK **v2**, whose import paths differ from v1. In v2 the transport helper is `streamable_http_client`, not `streamablehttp_client`. Check your installed version with `pip show mcp`.
{% endhint %}

### Reading the JWT-SVID

Read the JWT-SVID from wherever your SPIFFE infrastructure puts it, commonly a file kept rotated by a sidecar or CSI driver. Read it fresh on each exchange rather than caching it at startup, so a rotated JWT-SVID is picked up.

### The client

```python
import asyncio
import os
import pathlib

import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client

GATEWAY_URL = "https://gateway.example.com"
SERVER_ID = "gcp-compute"
SVID_PATH = os.environ.get("SPIFFE_JWT_SVID_PATH", "/run/spiffe-jwt/svid.jwt")

JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer"

# The MCP server URL, which is also the audience the access token must carry.
SERVER_URL = f"{GATEWAY_URL}/mcps/{SERVER_ID}"


def read_svid() -> str:
    """Reads the JWT-SVID your SPIFFE infrastructure wrote."""
    svid = pathlib.Path(SVID_PATH).read_text().strip()
    if not svid:
        raise RuntimeError(f"the JWT-SVID at {SVID_PATH} is empty")
    return svid


async def fetch_access_token() -> str:
    """Exchanges the JWT-SVID for an access token scoped to one MCP server.

    The audience here is the per-server URL, not the bare gateway hostname.
    The grant rejects any client authentication parameter, so no client_id or
    client_secret is sent.
    """
    async with httpx2.AsyncClient() as http:
        response = await http.post(
            f"{GATEWAY_URL}/token",
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            data={
                "grant_type": JWT_BEARER_GRANT,
                "assertion": read_svid(),
                "audience": SERVER_URL,
            },
        )

    if response.status_code != 200:
        raise RuntimeError(
            f"token exchange failed: {response.status_code} {response.text}"
        )
    if "json" not in response.headers.get("content-type", ""):
        raise RuntimeError(
            f"{GATEWAY_URL}/token did not return JSON. Is this the OAuth "
            f"server, rather than an SSO proxy or the console front end?"
        )

    token = response.json().get("access_token")
    if not token:
        raise RuntimeError("the OAuth server returned no access_token")
    return token


async def main() -> None:
    access_token = await fetch_access_token()

    async with create_mcp_http_client(
        headers={"Authorization": f"Bearer {access_token}"}
    ) as http_client:
        async with streamable_http_client(SERVER_URL, http_client=http_client) as (
            read,
            write,
        ):
            async with ClientSession(read, write) as session:
                await session.initialize()

                tools = await session.list_tools()
                print("tools:", [tool.name for tool in tools.tools])


asyncio.run(main())
```

{% hint style="warning" %}
The gateway and its OAuth server must be reachable at the same URL. Splitting them across separate hosts is not supported yet, so the token endpoint and the MCP server URL share the `GATEWAY_URL` host above.
{% endhint %}

Access tokens are short-lived, and this snippet obtains one and holds it, which is enough for a single short session. A long-running agent needs more: set the `Authorization` header per request rather than once on the client, so that when the gateway answers `401` you can exchange a fresh JWT-SVID and retry with the new access token. Reading the SVID file on each exchange, as `read_svid()` does, is what makes that possible.

## 6. Verify the connection

1. **Confirm the exchange.** `fetch_access_token()` returns without raising. A failure here is a JWT-SVID or identity provider problem, not an MCP problem.
2. **Inspect the access token's subject.** Decode the returned token. Its `sub` is a subject P0 derives, of the form `federated/{providerId}/subject/{jwtSvidSub}` — **not** your workload's SPIFFE ID. Confirm the `providerId` names the identity provider you enrolled in step 3. The SVID's own `sub` appears in the last `{jwtSvidSub}` segment.
3. **Confirm the session.** `list_tools()` returns the server's tools.
4. **Confirm P0 saw it.** The `list_tools` call appears in the gateway's activity at `GET /o/{tenant}/agentic/activity`.

## 7. Troubleshooting

The token endpoint answers a failed exchange in one of three ways. Identify which before working through the table:

| Response                      | Meaning                                                                                                       |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `400 invalid_request`         | The request is malformed. The `error_description` names the problem.                                          |
| `400 invalid_grant`           | The JWT-SVID failed validation. Deliberately opaque: every check returns this same response.                  |
| `503 temporarily_unavailable` | The gateway could not reach P0. Nothing is wrong with your JWT-SVID — check the gateway's connectivity to P0. |

### Finding out which check failed

`invalid_grant` never says what went wrong, but the gateway runs in your own environment, so its logs do. In the OAuth server's logs, find the audit event `auth.jwt_bearer_grant.assertion.outcome` with `outcome=denied`. Its `reason` attribute names the exact check that failed, for example `assertion_expired`, `assertion_lifetime_exceeded`, `issuer_not_registered`, or `signature_or_registered_key_invalid`.

`signature_or_registered_key_invalid` is the broadest of these: it covers a bad signature, a wrong `aud`, an expired token, a disallowed algorithm, and every issuer-discovery failure. When you see it, the adjacent warning `federated assertion signature verification failed` carries the underlying error, which distinguishes them.

Work through the following table in order.

| Symptom                                                                                  | Cause                                                                                  | Fix                                                                                                                                                                                                                                                                                                                                                          |
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The token is a certificate, or has no `spiffe://` subject                                | The SVID is in X509-SVID form, not JWT-SVID form                                       | Obtain the JWT-SVID. P0 cannot accept an X509-SVID.                                                                                                                                                                                                                                                                                                          |
| `invalid_grant`, and the JWT-SVID's `aud` is the per-server URL                          | Wrong JWT-SVID audience                                                                | The JWT-SVID's `aud` must be the **gateway hostname**. See [step 4](#4-configure-the-audience).                                                                                                                                                                                                                                                              |
| Exchange succeeds, then the gateway answers `401 invalid_token` on the first MCP request | Wrong token request audience                                                           | Nothing validates the `audience` parameter at the token endpoint, so a wrong value still returns `200` and a well-formed token. It fails only when the gateway checks the token's `aud`. The value must be exactly `{gatewayUrl}/mcps/{serverId}`, with no trailing slash. Omitting `audience` entirely produces a token that can never reach an MCP server. |
| `invalid_grant`, audience and issuer both look right                                     | **Subject pattern** does not match the JWT-SVID's SPIFFE ID                            | Test the pattern against the actual `sub`. Remember it is a regular expression: anchor it and escape dots. An over-anchored or mistyped pattern fails silently.                                                                                                                                                                                              |
| `invalid_grant`, and the issuer looks right                                              | The issuer is not enrolled, or does not match exactly                                  | **Issuer** is compared for exact string equality against `iss`. A trailing slash or an `http` versus `https` mismatch fails.                                                                                                                                                                                                                                 |
| `invalid_grant` on a freshly issued JWT-SVID                                             | The JWT-SVID has expired, or its lifetime exceeds what the gateway accepts             | Check `exp`. The gateway rejects assertions whose remaining lifetime is too long, so a long-lived JWT-SVID fails even before expiry. Gateways deployed with the Helm chart cap this at **300 seconds**, which exactly matches SPIRE's default `jwt_svid_ttl` and leaves no margin. Either shorten the issuer's TTL or raise the gateway's limit above it.    |
| `invalid_grant`, and everything else checks out                                          | The issuer signs with an algorithm the gateway does not accept                         | Only **RS256** and **ES256** are accepted by default. SPIRE configured for ES384, ES512, or EdDSA fails here with no distinguishing error. Check your issuer's key type.                                                                                                                                                                                     |
| `503 temporarily_unavailable`                                                            | The gateway cannot reach P0                                                            | Not a JWT-SVID problem. Check the gateway's network path to P0 and its P0 connection settings.                                                                                                                                                                                                                                                               |
| `invalid_request` mentioning client authentication                                       | The token request included `client_id`, `client_secret`, or a client assertion         | The JWT bearer grant rejects these outright. Send only `grant_type`, `assertion`, and `audience`.                                                                                                                                                                                                                                                            |
| A `2xx` response that is not JSON                                                        | Pointing at an SSO proxy or the console front end, not the OAuth server                | Use the gateway's OAuth server host. A proxy answers `200` with an HTML login page.                                                                                                                                                                                                                                                                          |
| `404` on an API call                                                                     | Used `mcp` as the integration key, or prefixed the read endpoints with `integrations/` | The integration key is `agentic`. Server and client reads live at `/o/{tenant}/agentic/...`; component configuration lives at `/o/{tenant}/integrations/agentic/config/...`.                                                                                                                                                                                 |

## Related

* [Connect an MCP client](/integrations/resource-integrations/agentic-gateway/connect-an-mcp-client): the user-delegated alternative to this guide. It also covers configuring the MCP server, which both paths need.
* [Identity provider](/integrations/resource-integrations/agentic-gateway/identity-provider): enroll the issuer that mints your JWT-SVIDs.
* [Gateway](/integrations/resource-integrations/agentic-gateway/gateway): register a gateway deployment, including its OAuth server endpoint.
* [Agentic Access Policies](/access-management/just-in-time-access/access-policies/agentic-access-policies): govern what an authenticated agent may do.
* [SPIFFE specification](https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md): the JWT-SVID standard.


# Requesting access

How an AI agent requests, checks, uses, and relinquishes just-in-time access to MCP servers behind the P0 AI Gateway.

Once an agent [connects to a server behind the gateway](/p0-cli/p0-commands-and-usage/p0-claude-mcp-add), it doesn't hold standing access to the underlying resource. Instead, the gateway wraps the server's tools with **access meta-tools**, and the agent requests just-in-time access through the same P0 workflow that governs human requests, including your [access policies](/access-management/just-in-time-access/access-policies), approvals, and audit trail.

## The access meta-tools

Alongside the server's own tools, the gateway exposes four meta-tools to the agent:

| Tool         | What it does                                                                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list`       | Lists possible values for access-request fields (for example, available AWS accounts or resources), so the agent can fill in the request correctly. The agent only calls it when it doesn't already know the value. |
| `access`     | Submits an access request to P0 on the user's behalf. Returns a request ID and a link to the request.                                                                                                               |
| `check`      | Polls whether a submitted request has been granted.                                                                                                                                                                 |
| `relinquish` | Revokes an active grant and tears down the session.                                                                                                                                                                 |

The server's own tools are always visible to the agent, but calling them requires the request ID of a granted access request. Without one, the call fails and directs the agent to use `access` and `check` first. This means a prompt-injected or misbehaving agent can't bypass the request flow: the tools don't work without a live grant.

## The request loop

A typical session looks like this:

1. **The agent discovers what to request.** If it doesn't already know the right values (for example, which project or account hosts a resource), it calls `list` to enumerate valid options for each request field.
2. **The agent requests access.** It calls `access` with the arguments describing the resource, role, and reason. The gateway submits a P0 access request as the authenticated user, and returns the request ID together with a link to the request in P0.
3. **P0 evaluates the request.** Your [agentic access policies](/access-management/just-in-time-access/access-policies/agentic-access-policies) decide what happens next: the request can be auto-approved, denied, or routed to approvers in Slack or the P0 web app, exactly like a human-initiated request.
4. **The agent polls for the outcome.** It calls `check` with the request ID. While the request is pending approval, `check` tells the agent to keep polling (the gateway instructs it to back off exponentially, for up to 30 minutes). When the request is granted, `check` confirms the agent can start calling the server's tools; if it's denied or errors, the agent is told to stop.
5. **The agent uses the tools.** Each tool call carries the request ID. The gateway verifies the grant is still live, obtains the short-lived credential for the session, and runs the tool in an isolated, per-session environment. The credential is never exposed to the agent.
6. **The agent relinquishes access.** When the work is done (for example, when the user tells the agent they're finished), the agent calls `relinquish`. The gateway revokes the P0 grant and tears down the session.

## Session isolation and expiry

* **Per-session isolation.** Each grant runs in its own isolated environment, keyed to the user, the agent session, and the request. Sessions are never shared across users or grants.
* **Continuous verification.** The gateway re-verifies the grant with P0 during use. If access is revoked in P0 (by the user, an admin, or expiry), the agent's tool calls stop working as soon as the access revocation propagates in the target system (typically under 30 seconds).
* **Idle teardown.** The gateway tears down session environments after 10 minutes of inactivity; the agent's next granted call transparently starts a fresh one.

## Related documentation

* [Agentic access policies](/access-management/just-in-time-access/access-policies/agentic-access-policies): route, auto-approve, or deny agent requests
* [Just-in-time access overview](/access-management/just-in-time-access): the approval workflow agents participate in
* [p0 claude mcp add](/p0-cli/p0-commands-and-usage/p0-claude-mcp-add): connect Claude Code to a gated server


# Use MCP servers with Claude Code

Connect your Claude Code client to the MCP servers behind the P0 AI Gateway, so your agent's tool calls are authenticated and governed by policy.

After an admin [configures an MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server) behind the [P0 AI Gateway](/readme/agentic-control-plane), each developer connects their own [Claude Code](https://docs.claude.com/en/docs/claude-code) client to it with the P0 CLI. Connecting registers a per-user OAuth client and signs you in through P0, so your agent reaches the server through the gateway and every tool call is authenticated and checked against policy.

{% hint style="info" %}
Connecting is per user and per machine. Everyone who wants to use a server must run these steps on their own machine. Configuring a server behind the gateway is an admin task. It doesn't connect anyone's client.
{% endhint %}

## Prerequisites

* The [P0 CLI installed](/p0-cli/installing-p0-cli) and logged in (`p0 login`).
* [Claude Code](https://docs.claude.com/en/docs/claude-code) installed, with the `claude` binary on your `PATH`.
* At least one [MCP server configured](/integrations/resource-integrations/agentic-gateway/mcp-server) behind the gateway.

## List the available servers

List the MCP servers configured behind the gateway and available to you, and note the key of each one you want:

```bash
p0 claude mcp list
```

The output shows each server's key and gateway URL. See [`p0 claude mcp list`](/p0-cli/p0-commands-and-usage/p0-claude-mcp-list) for details.

## Add each server

Connect Claude Code to a server by its key. Run the command once per server you want to use:

```bash
p0 claude mcp add aws
p0 claude mcp add gcs
```

See [`p0 claude mcp add`](/p0-cli/p0-commands-and-usage/p0-claude-mcp-add) for all flags and options.

### Choose a scope

The `--scope` flag maps to Claude Code's own configuration scopes, which control **which of your projects** can see the server. When you omit `--scope`, Claude Code applies its default, `local`.

| Scope             | Where the server is available                                                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `local` (default) | Only in the current project directory, and only to you.                                                                                          |
| `user`            | Across all your projects on this machine, and only to you.                                                                                       |
| `project`         | To everyone who works in this project, through a `.mcp.json` committed to the repository. Each user still authenticates through P0 individually. |

To make a server available in every project on your machine, add it with `--scope user`:

```bash
p0 claude mcp add aws --scope user
```

## Connect and authenticate

1. Open Claude Code.
2. Run `/mcp`.
3. Connect and authenticate to the server. You sign in through P0.

From then on, your agent's tool calls are enforced and audited by the gateway.

## Verify

Run `/mcp` in Claude Code and confirm the server shows as connected. Your agent can now call the server's tools; the gateway authenticates and checks each call against policy before it reaches the upstream server.

## Switch to a different P0 user

The P0 CLI stores all your local state under `~/.p0`: your identity and config, the credential cache, and the OAuth client it registered for MCP (`~/.p0/claude/mcp-client.json`). To operate as a different P0 user, delete this directory, then sign in and reconnect:

```bash
rm -rf ~/.p0
p0 login
p0 claude mcp add <server> --scope user
```

{% hint style="info" %}
`p0 logout` clears your credentials but keeps the cached MCP OAuth client, so the next `p0 claude mcp add` reuses the previous client. Delete `~/.p0` to force the CLI to register a fresh client for the new user.
{% endhint %}

## Related

* [`p0 claude mcp list`](/p0-cli/p0-commands-and-usage/p0-claude-mcp-list): list the servers available to you.
* [`p0 claude mcp add`](/p0-cli/p0-commands-and-usage/p0-claude-mcp-add): connect Claude Code to a server.
* [MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server): configure a server behind the gateway (admin).


# AWS

Install P0 IAM management on AWS in about 10 minutes. Configure just-in-time access, identity governance, and privilege management for your AWS environment.

The AWS integration lets P0 grant just-in-time, least-privileged access to your AWS accounts. It has two components:

* **IAM management** provisions and revokes access for your users. Installing it takes about 10 minutes.
* **Resource inventory** extends IAM management with fine-grained, resource-level access.

### Setting up AWS IAM management

To install the IAM management component and choose how P0 provisions your users, see [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods). You choose one of these login types during setup:

* [IAM](/integrations/resource-integrations/aws/installation-methods/iam) — provision users defined in the account's IAM service.
* [Identity Center](/integrations/resource-integrations/aws/installation-methods/identity-center) — provision users through AWS Identity Center.
* [Federated](/integrations/resource-integrations/aws/installation-methods/federated) — provision users through an Okta SAML federation.

To provision access through a single shared Identity Center permission set instead, use the [Identity Center (merged)](/integrations/resource-integrations/aws/identity-center-merged) integration (beta).

### Setting up AWS resource inventory

Installing P0 resource inventory on AWS takes about 10 minutes.

The resource inventory component extends the IAM management integration and allows requesting [fine-grained resource-level](/integrations/resource-integrations/aws/requesting-access#fine-grained-resource-level-access) access in AWS.

{% hint style="info" %}
An installed AWS IAM management integration is required
{% endhint %}

1. Navigate to "Integrations" on [p0.app](https://p0.app), then select "Amazon Web Services". Choose the "Resource inventory" component:

<figure><img src="/files/oquAhMb0mBYOJ09LP6wn" alt="" width="563"><figcaption></figcaption></figure>

2. Click "Add account"

<figure><img src="/files/wOjhtUBgqT8olp6ze1bc" alt="" width="563"><figcaption></figcaption></figure>

3. Choose one of the AWS accounts already installed for IAM management:

<figure><img src="/files/7UXfgMD0eYoBfbrHoPEW" alt="" width="563"><figcaption></figcaption></figure>

4. Run the AWS CLI commands to configure Resource Explorer

<figure><img src="/files/mloqJw1rQrEjneavX6In" alt="" width="563"><figcaption></figcaption></figure>

5. Click "Next" to validate your setup. You will land on the resource inventory configuration page. Clicking "Next" again takes you back to the Resource inventory overview page.

<figure><img src="/files/XCWuYwPfvV5G0nKepE6P" alt="" width="563"><figcaption></figcaption></figure>

And that's it. You're all set to start granting just-in-time, least-privileged access to AWS with P0.


# Setting up AWS IAM management

Install the P0 AWS IAM management integration and choose how P0 provisions and identifies your users — as IAM users, through AWS Identity Center, or through a federated identity provider.

Installing P0 IAM management on AWS takes about 10 minutes. You install the IAM management component on an account, then choose how P0 provisions and identifies your users — the integration's **login type**.

## Before you begin

* Choose at least one account on which to install P0.
* Make sure you can create roles, add trust relationships, and create and assign role polices. You can do this if you have the [IAMFullAccess managed policy](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/IAMFullAccess.html) attached to your user.

## Install the IAM management component

{% hint style="info" %}
For fine-grained Kubernetes access in EKS use the [P0 Kubernetes integration](/integrations/resource-integrations/kubernetes).
{% endhint %}

1. Navigate to "Integrations" on [p0.app](https://p0.app), then select "Amazon Web Services". Choose the "IAM management" component:

<figure><img src="/files/oquAhMb0mBYOJ09LP6wn" alt="" width="563"><figcaption></figcaption></figure>

2. Click the "Add account" button to begin the installation

<figure><img src="/files/z4gQZ03LnBvoPUrcLSjg" alt="" width="563"><figcaption></figcaption></figure>

3. Enter an AWS numeric account ID, then click "Next".

<figure><img src="/files/z5wK5Io1jQJKkDVbrUjT" alt="" width="563"><figcaption></figcaption></figure>

4. The next page displays commands you can run using the AWS CLI to provision P0. You can also run these commands using AWS Cloud Shell.

<figure><img src="/files/GEXzt4K8bsrybnZOTW1p" alt="" width="563"><figcaption></figcaption></figure>

5. Copy and run these commands or use the Terraform configuration to deploy the changes. Click "Next" to verify the installation. If verification is successful you will be taken to the integration configuration page.

<figure><img src="/files/W7bGF1OVzFzIA6xAO4a4" alt="" width="563"><figcaption></figcaption></figure>

## Troubleshooting

### "The web identity token provided could not be validated"

When you click "Next" to verify the installation, the verification fails with an error like this:

```
Error configuring Amazon Web Services
Failed to verify permissions for account 171433610395: The web identity token provided could not be validated. See the AssumeRoleWithWebIdentity documentation for requirements. (account=171433610395 region=undefined)
```

This happens when the account already has an IAM identity provider for `accounts.google.com` whose audience list doesn't include P0's audience. P0 assumes the `P0RoleIamManager` role by calling `sts:AssumeRoleWithWebIdentity` with a Google-signed token. When a Google identity provider exists, AWS validates the token's audience against that provider's configured audiences, and the assume-role call fails if P0's audience is missing.

To fix this, either remove the existing Google identity provider so AWS validates the token against Google's built-in federation, or add P0's audience to the provider. If other workloads depend on the existing provider, add the audience instead of removing it.

To add P0's audience to the existing Google identity provider:

1. In the AWS console, open **IAM** > **Roles** and open the `P0RoleIamManager` role.
2. Select the **Trust relationships** tab and copy the value of the `accounts.google.com:aud` condition. This is P0's audience.

<figure><img src="/files/qhhk82RqbIfBVTaS7M22" alt="Trust relationships tab of the P0RoleIamManager role, with the accounts.google.com:aud condition value highlighted"><figcaption></figcaption></figure>

3. Open **IAM** > **Identity providers** and select your `accounts.google.com` provider.
4. On the **Audiences** tab, select **Actions** > **Add audience**, then paste the value you copied and save.

<figure><img src="/files/1DbQ0nxpT5pCBC5ckh29" alt="Audiences tab of the accounts.google.com identity provider with the Actions menu open and Add audience selected"><figcaption></figcaption></figure>

5. Return to the P0 installation and click "Next" to verify again.

### "Unexpected content in P0 identity policy"

Verification fails, or the weekly install-issues email reports an error like this:

```
Failed to verify permissions for account <account-id>: Unexpected content in P0 identity policy P0RoleIamManagerPolicy.
Either fix these policy differences or rerun this account's setup commands.

Differences:
{
  "Statement": [
    "<no diff>",
    {
      "Action": [
        "<no diff>",
        {
          "-": "ec2:DescribeInstances"
        },
        "<no diff>"
      ]
    }
  ]
}
```

This means the `P0RoleIamManagerPolicy` inline policy deployed in your account no longer matches the policy P0 expects. P0 adds permissions to this policy as it gains capabilities, so an account installed some time ago drifts from the current policy. Editing the policy by hand causes the same result.

Read the `Differences` block as expected-versus-deployed:

| Marker                | Meaning                                                            |
| --------------------- | ------------------------------------------------------------------ |
| `{ "-": "<action>" }` | P0 expects this action, but your deployed policy doesn't grant it. |
| `{ "+": "<action>" }` | Your deployed policy grants this action, but P0 doesn't expect it. |
| `"<no diff>"`         | The entry matches; it's collapsed to keep the output short.        |

To fix the drift, redeploy the current policy for that account:

1. In P0, open **Integrations → Amazon Web Services** and select the **IAM management** component.
2. Select the account the error names to display its install commands.
3. Reapply the policy:
   * **AWS CLI:** run only the `aws iam put-role-policy` command. It replaces the inline policy in place. Don't run the whole block — the commands are chained with `&&`, and `create-role` fails with `EntityAlreadyExists` on a role that exists, which stops `put-role-policy` from running.
   * **Terraform:** run `terraform apply`. The configuration reconciles the policy for you.
4. Click **Next** to verify.

Until you redeploy, P0 keeps the account installed but the alert repeats weekly, and any capability that depends on a missing permission fails.

## Choose a login type

On the configuration page, you define how P0 provisions and identifies your users in AWS. This choice is the integration's login type:

| Login type                                                                                      | How P0 identifies and provisions users                                                   | Best for                                                           |
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [IAM](/integrations/resource-integrations/aws/installation-methods/iam)                         | Matches users to IAM users in the account, by user name or by a tag.                     | Accounts whose users are defined directly in IAM.                  |
| [Identity Center](/integrations/resource-integrations/aws/installation-methods/identity-center) | Matches users to AWS Identity Center identities and grants a per-request permission set. | Organizations that provision access through AWS Identity Center.   |
| [Federated](/integrations/resource-integrations/aws/installation-methods/federated)             | Matches users through an Okta SAML federation and grants IAM roles.                      | Accounts that sign in through an Okta federated identity provider. |

Federated is a legacy sign-in method — AWS recommends Identity Center for new deployments.

The access types each login type supports differ:

| Access type      | IAM | Identity Center | Federated |
| ---------------- | :-: | :-------------: | :-------: |
| `group`          |  ✅  |                 |           |
| `permission-set` |     |        ✅        |           |
| `policy`         |  ✅  |        ✅        |           |
| `resource`       |  ✅  |        ✅        |     ✅     |
| `role`           |     |                 |     ✅     |

{% hint style="info" %}
To avoid Identity Center permission-set sprawl, you can provision access through a single shared permission set with the [Identity Center (merged)](/integrations/resource-integrations/aws/identity-center-merged) method. That method is in beta and requires a separately installed AWS Identity Center (merged) integration.
{% endhint %}

## Next steps

* [Set up AWS resource inventory](/integrations/resource-integrations/aws#setting-up-aws-resource-inventory) to request fine-grained, resource-level access.
* [Request AWS access](/integrations/resource-integrations/aws/requesting-access).


# IAM

Configure the P0 AWS IAM management integration to provision users defined in the account's IAM service, matching them by user name or by an email tag.

Choose the **IAM** login type when your users are defined directly in the account's IAM service. P0 matches each requestor to an IAM user and grants access to that user. This is the default login type.

## Prerequisites

* An [AWS IAM management integration](/integrations/resource-integrations/aws/installation-methods) installed on the target account.

## Configure IAM provisioning

On the AWS IAM management configuration page, select **As AWS IAM users**, then choose how P0 identifies your users:

* **User name is user email**: Select this option if each user's IAM user name equals their email address. P0 matches users by user name.
* **User email in a tag**: Select this option if user names don't equal email addresses. P0 reads the email from a tag you specify.

### Match users by an email tag

If your IAM user names don't equal email addresses, add a tag to each user you want to allow access through P0, then give P0 the tag name. For example, with a tag named `Email`:

<figure><img src="/files/OVE6o1Qxn6PaAhd30jm1" alt="AWS IAM user with an Email tag whose value is the user&#x27;s email address" width="375"><figcaption></figcaption></figure>

<figure><img src="/files/KhgU3e8n9oIejaIQZVAq" alt="P0 AWS configuration set to provision users as AWS IAM users with an email tag" width="459"><figcaption></figcaption></figure>

## Related

* [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)
* [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods)


# Identity Center

Configure the P0 AWS IAM management integration to provision users through AWS Identity Center, matching them by user name or by their Identity Center email.

Choose the **Identity Center** login type when you provision access through AWS Identity Center — for example, when you provision users via SSO. P0 matches each requestor to an Identity Center identity and grants a per-request permission set.

## Prerequisites

* An [AWS IAM management integration](/integrations/resource-integrations/aws/installation-methods) installed on the management account where the Identity Center instance resides. You must install P0 on that account.

## Configure Identity Center provisioning

On the AWS IAM management configuration page, select **Via AWS Identity Center**, then use the **How are users identified in AWS Identity Center?** dropdown to tell P0 which Identity Center attribute holds the user's email address:

* **Username is user's email** (default): Select this option if each user's Identity Center user name equals their email address. P0 matches users by user name.
* **User's IDC email is user's email**: Select this option if user names don't equal email addresses and the email appears in the Identity Center email attribute instead. P0 matches users by email address.

To finish the configuration, select the management account in which the Identity Center instance resides, choose how to identify users, and click **Next**.

<figure><img src="/files/OGhwC1ArOSI6FpfmIKhb" alt="P0 AWS configuration set to provision users via AWS Identity Center" width="422"><figcaption></figcaption></figure>

## Related

* [Identity Center — merged permission set](/integrations/resource-integrations/aws/identity-center-merged)
* [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)
* [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods)


# Federated

Configure the P0 AWS IAM management integration to provision users through an Okta SAML federation, granting IAM roles assigned to your AWS Account Federation app.

Choose the **Federated** login type when you use an IAM identity provider to sign in users to your AWS account. P0 matches each requestor through the federation and grants IAM roles. This is a legacy sign-in method — AWS recommends using [Identity Center](/integrations/resource-integrations/aws/installation-methods/identity-center).

{% hint style="info" %}
Only Okta SAML federation is supported, via an AWS Account Federation.

An installed [Okta directory integration](/integrations/directory-integrations/okta) is required. Your AWS Account Federation Okta app must be in the same Okta organization as the one installed as the directory integration.
{% endhint %}

## Prerequisites

* An [AWS IAM management integration](/integrations/resource-integrations/aws/installation-methods) installed on the target account.
* An installed [Okta directory integration](/integrations/directory-integrations/okta) in the same Okta organization as your AWS Account Federation app.

## Configure federated provisioning

On the AWS IAM management configuration page, select **Via a federated identity provider**. Saving the configuration by clicking **Next** automatically applies the following changes to your AWS Account Federation app:

* Adds a custom attribute `managedByP0` to your Okta app's user profile. This lets P0 clean up dynamically assigned users from your AWS SSO Okta app.
* Enables the [`Join all roles`](https://help.okta.com/en-us/content/topics/deploymentguides/aws/aws-configure-aws-app.htm) flag. This lets users assume AWS roles that P0 assigns directly to their Okta user.

<figure><img src="/files/H2KmylvLc4GtEbVzzwTX" alt="P0 AWS configuration set to provision users via a federated identity provider" width="499"><figcaption></figcaption></figure>

{% hint style="warning" %}
When you edit or make changes to your role pool, always refresh your application data. Find this action by navigating to your Okta environment as a super admin.

<img src="/files/I721z9NSHFqmslCNR5nx" alt="Okta admin action to refresh application data" data-size="original">

See Okta's [Refresh application data](https://support.okta.com/help/s/article/Refresh-Application-Data-Functionality-and-Usage?language=en_US) guidance for details.
{% endhint %}

## Related

* [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)
* [p0 aws role assume](/p0-cli/p0-commands-and-usage/p0-aws-role-assume)
* [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods)


# Identity Center (merged)

Install the AWS Identity Center (merged) integration and configure the merged login type so P0 provisions access through a single shared permission set per user, avoiding Identity Center permission-se

{% hint style="info" %}
This login type is in beta.
{% endhint %}

Choose the **Identity Center (merged permission set)** login type to provision access through a single shared Identity Center permission set per user, rather than creating a new permission set for each access request. Each request attaches its own customer-managed policy to the shared permission set, which avoids Identity Center permission-set sprawl.

This method requires a separately installed AWS Identity Center (merged) integration on the account that hosts the Identity Center instance. Install it first, then select the merged login type on your AWS IAM management integration.

## Prerequisites

* An [AWS IAM management integration](/integrations/resource-integrations/aws/installation-methods) installed on the account that hosts the Identity Center instance.
* The AWS Identity Center (merged) integration installed on the same account. See [Install the AWS Identity Center (merged) integration](#install-the-aws-identity-center-merged-integration).

## Install the AWS Identity Center (merged) integration

Install the AWS Identity Center (merged) integration on the AWS account that hosts your Identity Center instance, typically your management or delegated administrator account. With this integration, P0 manages a single shared permission set per user and attaches a per-request customer-managed policy to it, instead of creating a separate permission set for every request.

1. Navigate to **Integrations** on [p0.app](https://p0.app), then select the **AWS MIDC** integration.
2. Enter the AWS account ID of the account that hosts the Identity Center instance.
3. Enter the AWS region where Identity Center is installed (for example, `us-east-1`). This region must match the region where your Identity Center instance resides.
4. Select the AWS partition: `aws` for commercial regions or `aws-us-gov` for GovCloud.
5. Run the displayed AWS CLI commands to provision P0's access. You can also run these commands using AWS Cloud Shell. These commands create the `P0RoleMergedIdc` role, which grants P0 permission to manage Identity Center permission sets and account assignments.
6. Click **Next** to verify the installation.
7. After verification, choose how P0 identifies users in Identity Center:
   * **Username is user's email** (default): P0 matches users by their Identity Center user name.
   * **User's IDC email is user's email**: P0 matches users by the email attribute on their Identity Center profile.

### Install with Terraform

You can install the AWS Identity Center (merged) integration with the [P0 Terraform provider](https://registry.terraform.io/providers/p0-security/p0/latest/docs) instead of the P0 app. Use the [`p0_aws_midc_staged`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/aws_midc_staged) resource to generate the role's trust and inline policies, create the `P0RoleMergedIdc` role from them, then use the [`p0_aws_midc`](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/aws_midc) resource to complete the install.

```terraform
resource "p0_aws_midc_staged" "staged_account" {
  id         = "123456789012" # The account that hosts the Identity Center instance
  idc_region = "us-east-1"     # The region where Identity Center is installed
}

resource "aws_iam_role" "p0_midc_manager" {
  name               = p0_aws_midc_staged.staged_account.role.name
  assume_role_policy = p0_aws_midc_staged.staged_account.role.trust_policy
}

resource "aws_iam_role_policy" "p0_midc_manager" {
  name   = p0_aws_midc_staged.staged_account.role.inline_policy_name
  role   = aws_iam_role.p0_midc_manager.name
  policy = p0_aws_midc_staged.staged_account.role.inline_policy
}

resource "p0_aws_midc" "installed_account" {
  id         = p0_aws_midc_staged.staged_account.id
  idc_region = p0_aws_midc_staged.staged_account.idc_region
  partition  = p0_aws_midc_staged.staged_account.partition
  depends_on = [aws_iam_role_policy.p0_midc_manager]
}
```

Run `terraform init` and `terraform apply`.

## Configure the merged login type

After you install the AWS Identity Center (merged) integration, return to your AWS IAM management configuration, select **Via AWS Identity Center (merged permission set)**, choose the account that hosts the Identity Center instance, then click **Next**.

If you install with Terraform, configure the `merged-idc` login type on the AWS account's `aws_iam_write` resource to provision access through the merged permission set.

## Related

* [Identity Center](/integrations/resource-integrations/aws/installation-methods/identity-center)
* [Requesting AWS access](/integrations/resource-integrations/aws/requesting-access)
* [Setting up AWS IAM management](/integrations/resource-integrations/aws/installation-methods)


# Requesting AWS access

How to request just-in-time access to AWS IAM policies, groups, and roles through P0 via Slack, Teams, Webex, the P0 CLI, or the P0 dashboard.

## Requesting from Slack or through p0.app

Open up the p0 modal using `/p0 request` in Slack or with [the Request Access feature ](https://docs.p0.dev/integrations/resource-integrations/aws/pages/kn3Mg03ByLowqw155BWv#via-the-p0.app)in p0.app and select "Amazon Web Services" as the resource.

<figure><img src="/files/OsnHwVcVLGjYbYaRMm23" alt="" width="290"><figcaption></figcaption></figure>

You'll see an "Access type" field with 2 options, "Attach user policy", and "Add user to group".

<figure><img src="/files/oYukdoo59cUXQLdS5VjF" alt="" width="289"><figcaption></figcaption></figure>

* **"Attach user policy":** request a user policy to be attached to your AWS user. The policy can either be a customer-managed or AWS-managed policy.
* **"Add user to group":** request your AWS user to be added to a user group.

<figure><img src="/files/jb5nmEAqT1lnnorHJJWG" alt="" width="375"><figcaption></figcaption></figure>

P0 auto-completes as you start typing out the policy or group. Once you select the policy / group you need, you can optionally add a reason for p0 to supply to the approver(s), then submit the request. If an existing policy / group isn't shown in the auto-complete results, it may be [filtered out by access policies](/access-management/just-in-time-access/access-policies#resource).

### Fine-grained resource-level access

If you installed the [Resource inventory](/integrations/resource-integrations/aws#setting-up-aws-resource-inventory) integration you will be able to choose the "Resource in AWS" access type. You can specify the exact resource and a policy. P0 will generate a new policy that contains the actions from the selected policy filtered to the selected resource.

For example, request access to a specific S3 bucket called `p0-sensitive-data`:

<figure><img src="/files/efCHUAx9vTvmHR3vTWfp" alt="" width="432"><figcaption></figcaption></figure>

When requesting access to an AWS resource, each service offers multiple permission levels ranging from read-only to full access. For some services, P0 provides curated policies that grant least-privilege access when no suitable AWS-managed policy exists. For details on the specific permissions granted for each policy, see [AWS permission levels](/integrations/resource-integrations/aws/requesting-access/permission-levels).

### What happens next

Once you make the request, you should get a Slack message from the p0 bot showing your request. There will also be a message to the approvers in the Slack channel designated by your org admin, requesting access.

1. If your request is approved, when you get a message that it has been approved, that means you should already have access provisioned, as that happens all at the same time.
2. **If you are on-call (on a PagerDuty schedule), and your org admin has enabled PagerDuty routing, your access may be automatically approved for 1 hour.**
3. After your request is approved, there will be a “relinquish” button for you to let go of your permissions early if you finish what you wanted to do before the expiration date (so you can let go of unneeded permissions).
4. If you wait for the access to expire, you will get a message that it has expired once it does.
5. If your request is denied, you'll get a message letting you know.


# Permission levels

Permission levels available when requesting just-in-time access to AWS resources through P0, including P0 curated policies and scoped AWS-managed policies.

When you request access to an AWS resource through P0, you select a permission level. Some permissions map directly to AWS-managed policies, while others are curated policies that P0 generates to provide least-privilege access.

**AWS-managed policies** reference a standard AWS IAM policy. When requesting resource-level access, P0 scopes the policy actions to the specific resource (or sub-resource if applicable) you request.

**P0 curated policies** are custom policies that P0 generates when no AWS-managed policy provides the right level of access. The following sections document the specific actions for each curated policy.

## S3

| Permission                | Type                 | Description                     |
| ------------------------- | -------------------- | ------------------------------- |
| `AmazonS3ReadOnlyAccess`  | AWS-managed (scoped) | Read and list objects           |
| `AmazonS3ReadWriteAccess` | P0 curated           | Read, write, and delete objects |
| `AmazonS3FullAccess`      | AWS-managed (scoped) | All S3 actions                  |

`AmazonS3ReadOnlyAccess` and `AmazonS3FullAccess` use the corresponding AWS-managed policy, but P0 scopes the actions to the bucket and prefix you specify in your request.

All three permission levels support object-level scoping. When you specify an object prefix (for example, `data/reports`), P0 restricts access to only that path within the bucket.

### AmazonS3ReadWriteAccess

P0 generates this policy because no AWS-managed policy provides read/write access without also granting bucket management permissions like changing ACLs or retention policies.

| Action                        | Purpose                                                                  |
| ----------------------------- | ------------------------------------------------------------------------ |
| `s3:GetObject`                | Download objects                                                         |
| `s3:PutObject`                | Upload objects                                                           |
| `s3:DeleteObject`             | Delete objects                                                           |
| `s3:AbortMultipartUpload`     | Cancel incomplete uploads                                                |
| `s3:ListMultipartUploadParts` | List parts of incomplete uploads                                         |
| `s3:ListBucket`               | List objects (scoped to your prefix when requesting object-level access) |

## EC2

| Permission                | Type        | Description                       |
| ------------------------- | ----------- | --------------------------------- |
| `AmazonEC2ReadOnlyAccess` | AWS-managed | Read-only access to EC2 resources |
| `AmazonEC2FullAccess`     | AWS-managed | Full access to EC2 resources      |

## EKS

| Permission           | Type       | Actions               |
| -------------------- | ---------- | --------------------- |
| `EksDescribeCluster` | P0 curated | `eks:DescribeCluster` |

## RDS

| Permission             | Type       | Actions          |
| ---------------------- | ---------- | ---------------- |
| `AmazonRDSConnectUser` | P0 curated | `rds-db:connect` |

## SageMaker

| Permission                | Type        | Description                             |
| ------------------------- | ----------- | --------------------------------------- |
| `AmazonSageMakerReadOnly` | AWS-managed | Read-only access to SageMaker resources |
| `SageMakerAdmin`          | P0 curated  | Broad SageMaker administration          |

### SageMakerAdmin

P0 generates this policy as a curated alternative to `AmazonSageMakerFullAccess`. The policy grants the following categories of access:

| Category           | Actions                                                             | Scope                                                    |
| ------------------ | ------------------------------------------------------------------- | -------------------------------------------------------- |
| SageMaker          | All SageMaker and SageMaker Geospatial actions                      | All resources                                            |
| Storage            | All S3 and S3 Express actions                                       | All resources                                            |
| Container registry | All ECR actions                                                     | All resources                                            |
| Code services      | All CodeCommit and CodeBuild actions                                | All resources                                            |
| Data processing    | All Glue actions                                                    | All resources                                            |
| Notifications      | All SNS actions                                                     | All resources                                            |
| Monitoring         | Read-only CloudWatch, CloudWatch Logs, and CloudFormation           | All resources                                            |
| Compute            | `ec2:Describe*`, create/delete network interfaces and VPC endpoints | All resources                                            |
| IAM                | `iam:PassRole`                                                      | SageMaker-prefixed roles only                            |
| IAM                | `iam:CreateServiceLinkedRole`                                       | SageMaker and RoboMaker service roles only               |
| Lambda             | `lambda:InvokeFunction`                                             | Functions matching `*SageMaker*` or `*LabelingFunction*` |
| Step Functions     | Describe, start, stop, and update executions                        | State machines matching `*sagemaker*`                    |
| Secrets Manager    | Create, describe, and read secrets                                  | Secrets prefixed with `AmazonSageMaker-`                 |

> **Important**: This policy grants broad access including `iam:PassRole` and full S3 permissions. Review your access policies to restrict which users can request this permission level.

## SSM

| Permission                    | Type        | Description                                |
| ----------------------------- | ----------- | ------------------------------------------ |
| `AmazonSSMFullAccess`         | AWS-managed | Full access to Systems Manager             |
| `SessionManagerConnectAccess` | P0 curated  | Start a session on a specific EC2 instance |

### SessionManagerConnectAccess

P0 generates this policy to allow SSM session access scoped to a specific EC2 instance with time-limited sessions.

| Action                 | Purpose                                   | Scope                                        |
| ---------------------- | ----------------------------------------- | -------------------------------------------- |
| `ssm:StartSession`     | Start a session on the requested instance | Specific EC2 instance, with automatic expiry |
| `ssm:TerminateSession` | End a session                             | Your own sessions only                       |
| `ssm:ResumeSession`    | Reconnect to a session                    | Your own sessions only                       |


# AWS OIDC

Install the AWS OIDC federation component so P0 can grant and revoke AWS access for OIDC-authenticated agents running through the P0 AI Gateway.

The **AWS OIDC** integration lets P0 grant and revoke AWS access for OIDC-authenticated agents. It registers an OIDC identity provider in your AWS account and provisions the IAM roles that P0 assumes on the agent's behalf, so an agent reaching AWS through the [P0 AI Gateway](/readme/agentic-control-plane) gets short-lived, policy-scoped access with no long-lived credentials.

This component exists specifically to enable **agentic access to AWS through the gateway**. The OIDC provider you register here is the gateway's own identity: the gateway signs a web-identity token, and AWS trusts it via the provider and role pool this component creates.

{% hint style="info" %}
This component supports only agentic access via the P0 AI Gateway. It's a prerequisite for the [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws).
{% endhint %}

## Prerequisites

* The base [AWS integration](/integrations/resource-integrations/aws) with **IAM management** installed on the same AWS account. AWS OIDC builds on that integration's IAM-write access, and the account picker only lists accounts where it's already installed.
* A deployed P0 AI Gateway. The **OIDC provider URL** and **Audience** you enter in the following fields come from your gateway deployment. See [Deploying the P0 AI Gateway](/getting-started/deploying-the-p0-mcp-gateway).
* Permission to apply Terraform (or run the equivalent `aws iam` commands) in the target AWS account, to create an OIDC provider and IAM roles.

## Install the OIDC federation component

1. Navigate to **Integrations** on [p0.app](https://p0.app), select **AWS OIDC**, and choose the **OIDC federation** component.

<figure><img src="/files/gr53G4USrDS1fyhTb2T4" alt="AWS OIDC integration page showing the OIDC federation component, not installed"><figcaption></figcaption></figure>

2. Click **Add identity provider**.

<figure><img src="/files/Rws69Ce9ls7zcaHnmO6d" alt="OIDC federation page with an empty list of installed identity providers and an Add identity provider button"><figcaption></figcaption></figure>

3. Fill in the identity provider details, then click **Next**:

<figure><img src="/files/K1TSZ68yQMtoU7wuhbJu" alt="Form for installing a new identity provider with fields for identifier, AWS account ID, OIDC provider URL, and audience"><figcaption></figcaption></figure>

* **Identity provider identifier**: a name for this identity provider within P0 (for example, the name of the gateway environment it serves).
* **AWS account ID**: the account to install into. The dropdown lists the accounts where the base AWS integration's IAM management is installed.
* **OIDC provider URL**: the issuer (`iss`) URL of your OIDC provider. For the P0 AI Gateway, this is the gateway's OIDC issuer URL.
* **Audience**: the `aud` claim the identity presents to AWS. For the P0 AI Gateway, this is the gateway's configured token audience.

{% hint style="info" %}
The **OIDC provider URL** and **Audience** are separate values. The OIDC provider URL is the issuer (`iss`) of the tokens your gateway mints; the Audience is the `aud` claim those tokens carry. P0 derives the trust-policy condition key from the provider URL (host and path) and matches the `aud` condition against the Audience you enter.
{% endhint %}

4. P0 generates a Terraform configuration. Copy it into your Terraform project, `apply` it, then click **Next**.

<figure><img src="/files/xloAC4WIrzZ8xaYEp2NL" alt="Terraform tab showing generated configuration that registers an AWS IAM OpenID Connect provider"><figcaption></figcaption></figure>

The generated configuration registers the OIDC provider and provisions the IAM roles P0 uses to grant access.

5. Review the read-only summary and click **Finish**.

<figure><img src="/files/n3pybIdu5OtbaHRnFsdg" alt="Read-only confirmation of the AWS account ID, OIDC provider URL, and audience with a Finish button"><figcaption></figcaption></figure>

6. The identity provider now appears with the state **Installed**.

<figure><img src="/files/TGlPFAxjBZY4UjVyxzAo" alt="Installed identity providers list showing the new provider with state Installed"><figcaption></figcaption></figure>

## How it works

When an agent requests AWS access through the gateway, P0 attaches temporary, policy-scoped permissions to one of the pre-provisioned IAM roles and conditions them on the agent's identity (`sub`). The audience (`aud`) isn't checked at this step; it's enforced by the trust policy on the pre-provisioned roles, created by the Terraform you apply when installing this component. The gateway signs a web-identity token and calls `sts:AssumeRoleWithWebIdentity` to assume the role. P0 never issues long-lived AWS credentials to the agent, and removes the grant when the session ends or the agent relinquishes it.

## Next steps

* Configure the [AWS MCP server](/integrations/resource-integrations/agentic-gateway/mcp-server/aws) to expose AWS to agents behind the gateway, using this OIDC identity as its credential provider.


# Function invocation

The **function invocation** component is a secure, reusable building block in P0 that enables the platform to invoke AWS Lambda functions on your behalf. It acts as the bridge between P0 and your cloud environment, allowing notifications and events to trigger real-time execution of your custom logic inside AWS.

This component is important because it:

* **Establishes a trusted connection** between P0 and your AWS account
* **Ensures secure invocation** of your Lambda functions using least-privilege permissions
* **Allows you to reuse the same Lambda** across multiple integrations or event types without repeating setup steps
* **Provides flexibility**, enabling you to integrate deeply with your internal systems, automation flows, or alerting infrastructure

Setting up Function invocation is the first step toward enabling powerful serverless automation with your P0 events.

## Before you begin

This guide walks you through setting up an **AWS function invocation integration component**, but before diving into the steps, make sure you have an AWS Lambda function.

## Install the AWS function invocation component

First, you'll need to define an installer component in P0 that knows how to call your AWS Lambda function. This component will give P0 permission to invoke this specific AWS Lambda function

1. Go to p0.app in your browser, navigate to Integrations, and select AWS.
2. Select the function invocation component.

<figure><img src="/files/C4zbPkVjgPVeWnSrEBn2" alt="" width="563"><figcaption></figcaption></figure>

3. Enter the full **ARN** of your AWS Lambda function. This is required so P0 knows which function to invoke.

<figure><img src="/files/CA1gnzEqXLIP5ydz9IhY" alt="" width="563"><figcaption></figcaption></figure>

4. Follow the provided instructions to provision access using **AWS CloudShell** or **Terraform**. This step grants P0 permission to call your AWS Lambda securely.

<figure><img src="/files/kUDdUlhfqjYArAxLrqSh" alt="" width="563"><figcaption></figcaption></figure>

5. Once completed, P0 is now set up to call your AWS Lambda function.

<figure><img src="/files/LciYs7X0CTtK5Yj6nEkw" alt="" width="563"><figcaption></figcaption></figure>


# Managed services

P0 can also grant access to several managed services.

Navigate to the page for an individual service for more info.




---

[Next Page](/llms-full.txt/1)

