# Custom Resource

The **Custom Resource** integration is designed to let you **securely manage access to external or on-premise applications**. Whenever a user is granted or revoked access through P0, your configured endpoint will be invoked automatically — giving you full control over how that access is provisioned or removed in your own systems.

The **Custom Resource** integration leverages **AWS Lambda** or **Google Cloud Run** to invoke your external applications securely and flexibly. This approach offers several key advantages:

* **No credentials to manage:** Authentication is handled via IAM or identity tokens, so there's nothing to rotate or store.
* **Full control:** You can filter, transform, or enrich the data before it reaches your downstream systems.
* **Easier debugging:** Logs live in your cloud environment, making it easy to trace, monitor, and troubleshoot.
* **Zero infrastructure lock-in:** Use any language or runtime your service supports — it's just a web endpoint.
* **Scales with your needs:** Cloud-native scaling handles bursty workloads without pre-provisioning.

Both deployment methods will receive the **same standardized event payload**, as defined by our OpenAPI specification. This makes it easy to reuse the same logic across environments or switch providers later with minimal effort.

## Set up Custom Resource Integration

### Configuration Parameters

These parameters are configured by you during setup.

The examples use an integration that connects users to an internal application for customer administration at a multi-tenant B2B company.

<table><thead><tr><th width="179.36224365234375">Parameter</th><th>Description</th><th>Example Value</th></tr></thead><tbody><tr><td>Application ID</td><td>The identifier of the system you are integrating*</td><td>customerAdminApp</td></tr><tr><td>Application name</td><td>The name of the custom resource integration</td><td>Internal Customer Admin App</td></tr></tbody></table>

\* Identifiers in P0 do not allow whitespace. By convention use camel-casing. Not visible to users.

## OpenAPI Specification

This specification describes the API endpoint you must implement to create a custom resource integration in P0.

## Handle access request event

> Grant a user access to a resource

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"tags":[{"name":"access","description":"Handle access requests for your resources"}],"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":{"AccessEvent":{"oneOf":[{"$ref":"#/components/schemas/GrantEvent"},{"$ref":"#/components/schemas/RevokeEvent"},{"$ref":"#/components/schemas/ListEvent"}],"discriminator":{"propertyName":"eventType"}},"GrantEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["grant"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"PermissionRequest":{"required":["type","principal","requestor","permission"],"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The value of this field is always going to be the Integration ID value you configured in P0."},"principal":{"type":"string","description":"E-mail address of the principal. Access should be provisioned to the user identified by this email."},"requestor":{"type":"string","description":"E-mail address of the requestor. Different from the principal only if the requestor initiated the access request on behalf of someone else."},"reason":{"type":"string","description":"The reason provided by the requestor"},"permission":{"$ref":"#/components/schemas/Access"}}},"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}},"RevokeEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["revoke"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"ListEvent":{"required":["eventType"],"type":"object","properties":{"eventType":{"type":"string","enum":["list"]}}},"AccessEventResponse":{"oneOf":[{"$ref":"#/components/schemas/Empty"},{"$ref":"#/components/schemas/ListItems"}]},"Empty":{"type":"object"},"ListItems":{"required":["items"],"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ListItem"}}}},"ListItem":{"required":["label","identifier"],"properties":{"label":{"type":"string","description":"The display label shown to users when they list resources."},"category":{"type":"string","description":"Optional. The category of this item. Results are grouped by the category in listers."},"identifier":{"type":"string","description":"The unique key that identifies the item in your system. Hidden from the user, except in the P0 CLI."}},"description":"An item that can be picked by the user to populate the requested access fields. Filtering the items based on user search term is handled by P0."},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["public","internal"]},"message":{"type":"string"},"errorId":{"type":"string"},"publicMessage":{"type":"string","description":"The message is shown verbatim to the user in the channel they used to create the request  (P0 CLI, Slack, MS Teams, etc.)."}},"required":["type","message"],"description":"If `type` is \"internal\", the `errorId` field must be present. P0 will display a generic message to the user: \"Error in service Internal Customer Admin App: P0 encountered an unknown error. Please contact p0sec-support@example.com for assistance. (Error ID {errorId}).\". If `type` is `public`, the `publicMessage` field must be present. P0 will display its content verbatim to the user. The regular `message` field will be logged in P0 but not displayed to the user. Note: P0 will retry all requests that return an error a few times."}},"responses":{"AccessEventResponse":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessEventResponse"}}}},"ErrorResponse":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/":{"post":{"tags":["access"],"summary":"Handle access request event","description":"Grant a user access to a resource","operationId":"grantRequest","requestBody":{"description":"Grant a user access to a resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccessEvent"}}},"required":true},"responses":{"200":{"$ref":"#/components/responses/AccessEventResponse"},"201":{"$ref":"#/components/responses/AccessEventResponse"},"202":{"$ref":"#/components/responses/AccessEventResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}}}}}}
```

## The AccessEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"AccessEvent":{"oneOf":[{"$ref":"#/components/schemas/GrantEvent"},{"$ref":"#/components/schemas/RevokeEvent"},{"$ref":"#/components/schemas/ListEvent"}],"discriminator":{"propertyName":"eventType"}},"GrantEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["grant"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"PermissionRequest":{"required":["type","principal","requestor","permission"],"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The value of this field is always going to be the Integration ID value you configured in P0."},"principal":{"type":"string","description":"E-mail address of the principal. Access should be provisioned to the user identified by this email."},"requestor":{"type":"string","description":"E-mail address of the requestor. Different from the principal only if the requestor initiated the access request on behalf of someone else."},"reason":{"type":"string","description":"The reason provided by the requestor"},"permission":{"$ref":"#/components/schemas/Access"}}},"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}},"RevokeEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["revoke"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"ListEvent":{"required":["eventType"],"type":"object","properties":{"eventType":{"type":"string","enum":["list"]}}}}}}
```

## The GrantEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"GrantEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["grant"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"PermissionRequest":{"required":["type","principal","requestor","permission"],"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The value of this field is always going to be the Integration ID value you configured in P0."},"principal":{"type":"string","description":"E-mail address of the principal. Access should be provisioned to the user identified by this email."},"requestor":{"type":"string","description":"E-mail address of the requestor. Different from the principal only if the requestor initiated the access request on behalf of someone else."},"reason":{"type":"string","description":"The reason provided by the requestor"},"permission":{"$ref":"#/components/schemas/Access"}}},"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}}}}}
```

## The RevokeEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"RevokeEvent":{"required":["eventType","data"],"type":"object","properties":{"eventType":{"type":"string","enum":["revoke"]},"data":{"$ref":"#/components/schemas/PermissionRequest"}}},"PermissionRequest":{"required":["type","principal","requestor","permission"],"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The value of this field is always going to be the Integration ID value you configured in P0."},"principal":{"type":"string","description":"E-mail address of the principal. Access should be provisioned to the user identified by this email."},"requestor":{"type":"string","description":"E-mail address of the requestor. Different from the principal only if the requestor initiated the access request on behalf of someone else."},"reason":{"type":"string","description":"The reason provided by the requestor"},"permission":{"$ref":"#/components/schemas/Access"}}},"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}}}}}
```

## The ListEvent object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"ListEvent":{"required":["eventType"],"type":"object","properties":{"eventType":{"type":"string","enum":["list"]}}}}}}
```

## The PermissionRequest object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"PermissionRequest":{"required":["type","principal","requestor","permission"],"type":"object","properties":{"requestId":{"type":"string","description":"Random identifier for the request generated by P0"},"type":{"type":"string","description":"The value of this field is always going to be the Integration ID value you configured in P0."},"principal":{"type":"string","description":"E-mail address of the principal. Access should be provisioned to the user identified by this email."},"requestor":{"type":"string","description":"E-mail address of the requestor. Different from the principal only if the requestor initiated the access request on behalf of someone else."},"reason":{"type":"string","description":"The reason provided by the requestor"},"permission":{"$ref":"#/components/schemas/Access"}}},"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}}}}}
```

## The Access object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"Access":{"required":["item"],"properties":{"item":{"type":"string","description":"The list item selected by the user. The value corresponds to the unique key that identifies the item in your system. This value is derived from the ListItem:identifier field."}}}}}}
```

## The AccessEventResponse object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"AccessEventResponse":{"oneOf":[{"$ref":"#/components/schemas/Empty"},{"$ref":"#/components/schemas/ListItems"}]},"Empty":{"type":"object"},"ListItems":{"required":["items"],"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ListItem"}}}},"ListItem":{"required":["label","identifier"],"properties":{"label":{"type":"string","description":"The display label shown to users when they list resources."},"category":{"type":"string","description":"Optional. The category of this item. Results are grouped by the category in listers."},"identifier":{"type":"string","description":"The unique key that identifies the item in your system. Hidden from the user, except in the P0 CLI."}},"description":"An item that can be picked by the user to populate the requested access fields. Filtering the items based on user search term is handled by P0."}}}}
```

## The ListItem object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"ListItem":{"required":["label","identifier"],"properties":{"label":{"type":"string","description":"The display label shown to users when they list resources."},"category":{"type":"string","description":"Optional. The category of this item. Results are grouped by the category in listers."},"identifier":{"type":"string","description":"The unique key that identifies the item in your system. Hidden from the user, except in the P0 CLI."}},"description":"An item that can be picked by the user to populate the requested access fields. Filtering the items based on user search term is handled by P0."}}}}
```

## The ListItems object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"ListItems":{"required":["items"],"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ListItem"}}}},"ListItem":{"required":["label","identifier"],"properties":{"label":{"type":"string","description":"The display label shown to users when they list resources."},"category":{"type":"string","description":"Optional. The category of this item. Results are grouped by the category in listers."},"identifier":{"type":"string","description":"The unique key that identifies the item in your system. Hidden from the user, except in the P0 CLI."}},"description":"An item that can be picked by the user to populate the requested access fields. Filtering the items based on user search term is handled by P0."}}}}
```

## The Error object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"Error":{"type":"object","properties":{"type":{"type":"string","enum":["public","internal"]},"message":{"type":"string"},"errorId":{"type":"string"},"publicMessage":{"type":"string","description":"The message is shown verbatim to the user in the channel they used to create the request  (P0 CLI, Slack, MS Teams, etc.)."}},"required":["type","message"],"description":"If `type` is \"internal\", the `errorId` field must be present. P0 will display a generic message to the user: \"Error in service Internal Customer Admin App: P0 encountered an unknown error. Please contact p0sec-support@example.com for assistance. (Error ID {errorId}).\". If `type` is `public`, the `publicMessage` field must be present. P0 will display its content verbatim to the user. The regular `message` field will be logged in P0 but not displayed to the user. Note: P0 will retry all requests that return an error a few times."}}}}
```

## The Empty object

```json
{"openapi":"3.0.4","info":{"title":"Custom Integration Access Lifecycle API","version":"0.0.1"},"components":{"schemas":{"Empty":{"type":"object"}}}}
```
