> For the complete documentation index, see [llms.txt](https://docs.p0.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.p0.dev/agentic-access/using-the-gateway/connect-an-mcp-client/client-registration-api.md).

# 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](/agentic-access/agentic-gateway/gateway.md), 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](/agentic-access/using-the-gateway/connect-an-mcp-client.md).

{% hint style="info" %}
The AI 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. Register through the console, or get a user token from the [P0 CLI](/p0-api/authenticating-with-the-p0-api.md#p0-cli-token).
* **`GET /clients` (list)** accepts a bearer token.
* **`PATCH /clients/{clientId}` (update)** accepts a bearer token.

To list clients:

```bash
curl -H "Authorization: Bearer $P0_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](/agentic-access/using-the-gateway/connect-an-mcp-client.md). 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](/agentic-access/identity-provider.md). P0 does not issue a secret. Register these only when dynamic registration is disabled for the provider. See [Register an agent identity](/agentic-access/identity-provider.md#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`.

This example renames a client and disables it. `jq` URL-encodes the client ID.

```bash
curl -X PATCH \
  -H "Authorization: Bearer $P0_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. A non-loopback URI must match what the client sends exactly. For a loopback URI (`http` on `localhost`, `127.x`, or `[::1]`) the port is not compared and a registered root path (`/`) permits any callback path, but the query string must match."},"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](/agentic-access/using-the-gateway/connect-an-mcp-client.md): the full walkthrough, including the console path and a Python client example.
* [Identity provider](/agentic-access/identity-provider.md): register federated agent identities.
* [JWT-SVID](/agentic-access/using-the-gateway/spiffe-svid.md): connect an unattended workload instead of a user-delegated client.
