--- title: "Configuring Kong" slug: "configuring-kong" updated: 2026-10-11T16:43:28Z published: 2026-10-11T16:43:28Z canonical: "docs.plainid.io/configuring-kong" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.plainid.io/llms.txt > Use this file to discover all available pages before exploring further. # Configuring Kong This page walks you through installing the PlainID plugins on Kong Gateway and configuring each one on a route. For an introduction to the plugins and what each one does, see [Kong Gateway](https://docs.plainid.io/v1/docs/kong). You install the plugins once per data plane. After that, the only thing you manage in Kong is each plugin's configuration on the routes you protect. PlainID evaluates every request against your Policies at request time, so you change who can do what in PlainID, not in Kong. ## Prerequisites Before you start, make sure you have: - Kong Gateway 3.15 or later on a self-managed data plane. If your data plane connects to Konnect, run the same gateway version as your control plane. A data plane older than its control plane can fail to sync configuration. - Your PlainID PDP URL and a Client ID from the PlainID Scope for this integration. If the Scope authenticates with a client secret, you also need the Client Secret. - For `plainid-prompt-authz`: Kong AI Gateway (`ai-proxy` or `ai-proxy-advanced`) on the LLM route, the URL of your PlainID AI Content Controls Service, and the names of your Prompt Control and Output Control Asset Types. - For `plainid-insights`, or for MCP discovery with `plainid-mcp-authz`: your PlainID Collector URL and a client certificate and key for mutual TLS. ## Installing the Plugins The plugins ship as five LuaRocks packages: one per plugin, plus a shared PlainID library that every plugin depends on. **To install the plugins:** 1. Install the shared library first. Each plugin package lists it as a dependency, and LuaRocks fails if it tries to fetch the library from the public registry. ``` luarocks make plainid-authz-common-0.2.1-1.rockspec ``` 2. Install the plugins you require. You don't need to install all four. ``` luarocks make kong-plugin-plainid-api-authz-0.1.1-1.rockspec luarocks make kong-plugin-plainid-mcp-authz-0.1.1-1.rockspec luarocks make kong-plugin-plainid-prompt-authz-0.3.1-1.rockspec luarocks make kong-plugin-plainid-insights-0.1.0-1.rockspec ``` 3. Enable the plugins in Kong, either through the `KONG_PLUGINS` environment variable or the `plugins` field in `kong.conf`: ``` KONG_PLUGINS=bundled,plainid-api-authz,plainid-mcp-authz,plainid-prompt-authz,plainid-insights ``` 4. Restart Kong. ### Use the PlainID Data Plane Image PlainID also provides a ready-made data plane image based on `kong/kong-gateway:3.15`. The image contains all four plugins and installs them from the same packages, so it always matches a LuaRocks installation. Pin the image tag to your Konnect control plane's gateway version. ### Register the Plugins on Konnect If you manage Kong through Konnect, register each plugin's schema once at the control plane level: 1. Open **Gateway Manager** and select your control plane. 2. Go to **Plugins** > **Custom Plugins**. 3. Select **+ New Custom Plugin**. 4. Upload the plugin's `schema.lua`. 5. Repeat for each plugin you installed on your data planes. See [Kong Konnect's documentation](https://developer.konghq.com/custom-plugins/konnect-hybrid-mode/) for more information. After you install or register the plugins, attach each one to its Services or Routes through the Kong Admin API, declarative configuration, or Konnect. ## Connect to PlainID The three enforcement plugins (`plainid-api-authz`, `plainid-mcp-authz`, and `plainid-prompt-authz`) share the same settings for connecting to the PlainID PDP. | Field | Default | Required | Description | | --- | --- | --- | --- | | `plainid_pdp_url` | | Yes | The PDP base URL, up to and including `/runtime`. For example, `https://<pdp-host>/api/runtime`. | | `client_id` | | Yes | The Client ID from your PlainID Scope. | | `authentication_method` | `token` | No | How the plugin authenticates to PlainID: `token`, `secret-header`, or `secret-body`. Must match your Scope's authentication method. See the table below. | | `client_secret` | | With `secret-header` or `secret-body` | The Client Secret from your PlainID Scope. Store it as a vault reference. See [Protect the Client Secret](/docs/configuring-kong#protect-the-client-secret). | | `user_auth_header` | `Authorization` | No | The inbound header that carries the caller's credential. The plugin forwards it to PlainID as a Bearer token. | | `token_forward_headers` | `[]` | No | Additional inbound headers to send to PlainID, for Scopes that identify callers by header. | | `runtime_request_timeout` | `60` | No | Seconds to wait for the PDP. On timeout, the plugin denies the request. | Set `authentication_method` to match the authentication method on your PlainID Scope. A mismatch always returns a 401 from PlainID, which the plugin reports as a denial. | Scope authentication method | `authentication_method` | | --- | --- | | Authorization JWT, or None | `token` | | Client Secret in header | `secret-header` | | Client Secret in request | `secret-body` | In every mode, the plugin forwards the caller's credential to PlainID so that PlainID can identify the caller. The secret modes send the Client Secret in addition to the caller's credential, not instead of it. ### Match Your Kong Authentication Plugin The plugins need to know who the caller is. How you set this up depends on the Kong authentication plugin on the route. | Kong authentication plugin | Setup | | --- | --- | | None, or `jwt` | Use any `authentication_method`. The caller's JWT already arrives on the `Authorization` header. | | `jwt-signer` | Set `user_auth_header` to the header you configured as `jwt-signer`'s `original_access_token_upstream_header`. `jwt-signer` can replace `Authorization` with its own token. | | `key-auth`, `basic-auth`, `ldap-auth`, `hmac-auth`, `mtls-auth`, `oauth2`, `oauth2-introspection` | These plugins don't produce a JWT. Use `secret-header` or `secret-body`, set `token_forward_headers: ["X-Consumer-Username"]`, and configure a header matcher on that value in your PlainID Scope. | | `openid-connect`, authorization code flow | No caller JWT reaches the plugin. Use a secret mode, map the claims you need to headers with `openid-connect`'s `upstream_headers_claims`, and configure a matching header matcher in your PlainID Scope. | | `openid-connect`, Bearer flow | `openid-connect` replaces `Authorization` with its own token. Contact PlainID Support to validate this setup. | ### Protect the Client Secret - **Konnect:** Always store `client_secret` as a vault reference. Konnect returns plugin configuration in plain text to anyone who can list plugins, so anyone with that access can read a literal secret. - **Self-managed Kong with declarative configuration:** A literal secret works, but we recommend the `env` vault, which keeps the secret out of `kong.yaml`: `{vault://env/PLAINID_CLIENT_SECRET}`. - The `encrypted` setting on `client_secret` takes effect only with Kong Enterprise keyring encryption. ### Trust the PDP Certificate Kong verifies only one level of the certificate chain by default, which isn't enough for many public certificate chains. If the plugins can't connect to the PDP over HTTPS, set these variables on the Kong container: ``` KONG_LUA_SSL_TRUSTED_CERTIFICATE=system KONG_LUA_SSL_VERIFY_DEPTH=3 ``` ## Authorize REST API Calls Attach `plainid-api-authz` to a REST API route. For each request, the plugin asks PlainID for a single permit or deny decision. Kong forwards permitted requests unchanged and returns an error for denied ones. ``` services: - name: cases-api url: https:// routes: - name: cases paths: [/cases] plugins: - name: plainid-api-authz config: plainid_pdp_url: https:///api/runtime client_id: authentication_method: token ``` `plainid-api-authz` needs no route-specific settings. It sends the full request to PlainID: the caller's IP, headers, path, query parameters, scheme, method, and body. If the body is JSON, PlainID receives it as a JSON object, so your Policies can use body attributes directly. The plugin always sends the body, because REST Policies often depend on it. If a request body is larger than Kong's `client_body_buffer_size`, the plugin can't read it. The plugin still asks PlainID for a decision, but without body attributes, and writes an `Error` trace line. If your Policies depend on body attributes, raise `client_body_buffer_size` to cover your largest requests. By default, a denied request returns `403` with the body `{ "code": 403, "error": "Forbidden" }`. You can change the denial response with these fields: | Field | Default | Description | | --- | --- | --- | | `on_prevent_status_code` | `403` | HTTP status code for denied requests. | | `on_prevent_body` | `{ "code": 403, "error": "Forbidden" }` | Response body for denied requests. | | `on_prevent_content_type` | `application/json` | Content type of the denial response. | | `enable_tracing` | `false` | Writes a trace line for each decision. See [Troubleshoot With Trace Logs](/docs/configuring-kong#troubleshoot-with-trace-logs). | ## MCP Tools Attach `plainid-mcp-authz` to an MCP server route. The plugin: - Filters each `tools/list` response, so a caller sees only the tools its Policies allow. - Authorizes each `tools/call` request, and blocks tool calls the caller may not make before they reach the MCP server. - Reports the MCP server's full tool inventory to the PlainID Collector, so PlainID can discover the tools and you can write Policies for them. Use the `mode` field to choose what the plugin does: | `mode` | Behavior | | --- | --- | | `discovery` | Reports tool inventories only. Makes no PDP calls, so it keeps working even when the PDP is unavailable. | | `enforcement` | Filters tools and authorizes tool calls only. | | `both` | Discovery and enforcement. | This example puts three plugins on a route in front of an MCP server. The first validates the caller's token, `plainid-mcp-authz` decides what the caller can do, and the last swaps in the credential the upstream MCP server expects. ``` services: - name: snowflake-mcp url: https://.snowflakecomputing.com/api/v2/.../mcp-servers/ routes: - name: snowflake paths: [/snowflake] plugins: # 1. Validate the caller's token. - name: ai-mcp-oauth2 config: authorization_servers: [https:///v2.0] passthrough_credentials: true # 2. Ask PlainID which tools this caller may see and use. - name: plainid-mcp-authz config: mode: both plainid_pdp_url: https:///api/runtime client_id: authentication_method: token mcp_resource_type: Tools # 3. Replace the caller's token with the upstream's credential. - name: request-transformer config: replace: headers: - Authorization:Bearer ``` With this configuration, an Agent that lists the server's tools sees only the tools its Policies allow. If the Agent calls a tool it isn't authorized to use, Kong blocks the call and the request never reaches the MCP server. ### Configuration Reference In addition to the [PlainID connection settings](/docs/configuring-kong#connect-to-plainid): | Field | Default | Required | Description | | --- | --- | --- | --- | | `mode` | `both` | No | `discovery`, `enforcement`, or `both`. | | `mcp_resource_type` | `Tools` | No | The Asset Type for MCP tools in PlainID. Must match the Asset Type name in your Policies. | | `mcp_error_code` | `-32603` | No | JSON-RPC error code for denied requests. | | `mcp_error_message` | `Access Denied` | No | JSON-RPC error message for denied requests. | | `enable_tracing` | `false` | No | Writes a trace line for each decision and each discovery step. | ### How the Plugin Handles MCP Requests | Request | What the plugin does | | --- | --- | | `tools/list` | Asks PlainID which tools the caller may use, and removes every other tool from the response. Works with JSON and streamed (SSE) responses. | | `tools/call` | Asks PlainID whether the caller may call this tool, and blocks the call if not. | | Other MCP methods, such as `initialize` | Forwards them without a PlainID call, and adds the header `X-Authorized-By: PlainID (MCP bypass)`. These methods don't expose or run tools. | | JSON-RPC batch requests | Denied. A batch can contain any number of tool calls. | | `tools/call` without a tool name | Denied, without a PlainID call. | | A request body the plugin can't read | Denied. Without the body, the plugin can't tell a tool call from other MCP traffic. | | A request that isn't JSON-RPC | Forwarded, with a `Skipped` trace line. This usually means the route serves REST traffic and needs `plainid-api-authz` instead. | For `tools/call`, the plugin sends the tool's arguments to PlainID at the root of the request body. Your Policies can refer to an argument directly, for example `$.branch`. If the MCP server returns a compressed `tools/list` response, the plugin can't filter it and denies the response. The plugin asks the server for uncompressed responses, so this happens only when a server ignores that request. ### MCP Denial Responses MCP clients match every response to a pending call by its JSON-RPC `id`. A plain HTTP error doesn't reach that layer, so the client would wait until its own timeout. Instead, the plugin returns HTTP `200` with a JSON-RPC error that echoes the request `id`: ``` {"jsonrpc":"2.0","id":7,"error":{"code":-32603,"message":"Access Denied"}} ``` You can change the error code and message with `mcp_error_code` and `mcp_error_message`. The rest of the response follows the JSON-RPC format and you can't change it. ### Discovery Requirements Discovery records the server's full tool inventory, before the plugin filters it for the caller, so every caller's `tools/list` reports the same inventory. Discovery never affects the caller's response: if it fails, the plugin logs the failure and the response continues normally. Discovery skips a tool inventory it can't attribute to a specific MCP server and gateway. Make sure that: - **Every MCP route has a `name`.** PlainID uses the route name to identify the MCP server. Kong allows routes without names, but discovery skips them and logs `DiscoverySkippedUnnamedRoute`. - **Your data plane connects to a Konnect control plane.** PlainID uses the control plane ID to identify the gateway. On a data plane without Konnect, discovery stops and logs `DiscoverySkippedNoClusterId`. Enforcement keeps working. Neither case returns an error to callers, so turn on `enable_tracing` the first time you deploy and confirm that neither event appears. ## Control Prompts and Responses Attach `plainid-prompt-authz` to an LLM route served by Kong AI Gateway. The plugin works in two directions: - **Prompt Control (input):** Before the request reaches the LLM provider, the plugin checks which content categories the conversation touches and blocks the request if the caller isn't permitted to use them. - **Output Control (output):** Before the model's response reaches the caller, the plugin masks or encrypts sensitive data in it, based on the caller's Output Control Policy. Use the `mode` field to choose which directions the plugin controls: | `mode` | Behavior | | --- | --- | | `prompt` | Prompt Control only. | | `output` | Output Control only. | | `both` (default) | Prompt Control and Output Control. | ### Where to Attach It Attach `plainid-prompt-authz` to routes whose AI Gateway `route_type` is one of: - `llm/v1/chat`: OpenAI chat format, and Anthropic native chat format (`llm_format: anthropic`). - `llm/v1/completions`: OpenAI legacy completions. - `llm/v1/responses`: OpenAI Responses API. The plugin applies only to routes you attach it to. Other AI Gateway route types, such as embeddings, audio, images, files, batches, assistants, and real-time WebSocket traffic, don't carry a text prompt the plugin can read. On those routes the plugin lets requests pass unchanged. ### How Prompt Control Works For each request, the plugin: 1. Extracts the prompt and conversation from the request. 2. Asks the PlainID PDP which Prompt Control categories the caller may use. 3. Asks the PlainID AI Content Controls Service which categories the conversation touches. 4. Compares the two sets and permits or denies the request. The plugin never changes the prompt or request body it forwards. It does handle these requests specially: - **Compressed requests:** The plugin denies a request whose body uses a `Content-Encoding` other than `identity`, because it can't read the prompt. - **Requests without a body:** The plugin forwards them unchanged. They can't carry a prompt. - **Empty conversations:** In `prompt` and `both` modes, the plugin denies a request in a supported format that contains no prompt text. For example, an empty `messages` array, or a Responses API request that refers to a stored prompt or an earlier response instead of sending its own `input`. In `output` mode, the plugin forwards these requests and still masks the response. The plugin removes any `X-Authorized-By` header the caller sends, and adds `X-Authorized-By: PlainID` only after Prompt Control permits the request. In `output` mode, the plugin doesn't add the header. ### How Output Control Works The PDP decision the plugin already fetched for the request includes the caller's Output Control Policy, so Output Control needs no second PDP call. The plugin holds the model's response, sends each text field to the AI Content Controls Service for masking, and returns the masked response to the caller. The rest of the response, including IDs, the model name, and token usage, stays the same. The masked response keeps the upstream status code, `Content-Type`, and JSON structure, so existing clients and SDKs parse it normally. For each entity in the caller's Output Control Policy: | Policy action | Result in the response | | --- | --- | | `MASK` | The plugin replaces the value with `***`. | | `ENCRYPT` | The plugin replaces the value with an encrypted value. | | Both `MASK` and `ENCRYPT` | The plugin masks the value. | `ENCRYPT` requires an encryption key on your AI Content Controls Service deployment, set through `PLAINID_ANONYMIZATION__ENCRYPT_KEY`. Without it, the service rejects the request, and the plugin denies the response instead of returning it unmasked. The plugin masks the model-generated text in these fields: | Response format | Masked fields | | --- | --- | | OpenAI chat | Each choice's `message.content` | | OpenAI completions | Each choice's `text` | | OpenAI Responses | Each `output_text` item's `text` | | Anthropic messages | Each `text` block, and each `thinking` block | Output Control has these limits: - **It applies to responses only.** The plugin doesn't mask the prompt, so data a user types reaches the LLM provider as written. It also doesn't mask conversation history or tool results inside a request. Because the plugin masks every response, a client that sends its history back already sends masked text. - **It doesn't mask tool call arguments.** Changing them would break the tool call the client is about to make. - **It doesn't support streaming.** In `output` and `both` modes, the plugin denies any request with `stream: true`, and any request with an `Upgrade` header. - **It supports specific LLM providers.** When you use `ai-proxy`, Output Control supports OpenAI, Azure OpenAI, Anthropic, and Amazon Bedrock (Converse API). In `output` and `both` modes, the plugin denies successful responses from other providers, such as Gemini, Cohere, Llama, Mistral, and Hugging Face. Bedrock support requires Kong Gateway 3.6 or later. ### Example ``` services: - name: openai-chat routes: - name: chat paths: [/chat] plugins: - name: ai-proxy config: route_type: llm/v1/chat # ... your provider and model settings - name: plainid-prompt-authz config: mode: both plainid_pdp_url: https:///api/runtime client_id: authentication_method: token ccs_url: https:// prompt_control_asset_type: output_control_asset_type: ``` ### Configuration Reference In addition to the [PlainID connection settings](/docs/configuring-kong#connect-to-plainid): | Field | Default | Required | Description | | --- | --- | --- | --- | | `mode` | `both` | No | `prompt`, `output`, or `both`. | | `ccs_url` | | Yes | Base URL of your PlainID AI Content Controls Service. | | `ccs_request_timeout` | `30` | No | Seconds to wait for the AI Content Controls Service. On timeout, the plugin denies the request. | | `prompt_control_asset_type` | | In `prompt` and `both` modes | Your Prompt Control Asset Type. Matches exactly and is case-sensitive. | | `output_control_asset_type` | | In `output` and `both` modes | Your Output Control Asset Type. Matches exactly and is case-sensitive. | | `category_match` | `all` | No | `all`: PlainID must permit every category the conversation touches. `any`: one permitted category is enough. | | `include_system_prompt` | `true` | No | Whether to include system messages when checking the conversation. | | `on_no_category` | `permit` | No | `permit` or `deny`, when the conversation touches no categories at all. | | `upstream_response_format` | `auto` | No | The response format to mask. `auto` detects OpenAI and Anthropic responses, and Bedrock responses when `ai-proxy` uses the Bedrock provider. `openai` or `anthropic` accepts only that format and denies any other. `bedrock` always treats the response as a Bedrock Converse response, for routes that call Bedrock without `ai-proxy`. | | `on_prevent_status_code` | `403` | No | HTTP status code for denied requests. | | `on_prevent_body` | `{ "code": 403, "error": "Forbidden" }` | No | Response body for denied requests. | | `on_prevent_content_type` | `application/json` | No | Content type of the denial response. | | `enable_tracing` | `false` | No | Writes a trace line for each decision. | Prompt Control uses `category_match`, `include_system_prompt`, and `on_no_category` only in `prompt` and `both` modes. > **Upgrading from an earlier version:** The default `mode` is now `both`, which requires `output_control_asset_type`. If your existing configuration has no `output_control_asset_type`, Kong rejects it. Either add `output_control_asset_type`, or set `mode: prompt` to keep Prompt Control only. ### Deployment Considerations - **Allow time for long responses.** In `output` and `both` modes, the plugin holds the complete response before masking it. Set Kong's proxy timeouts, such as `upstream_read_timeout`, high enough for your longest generations. - **Masked responses skip some `ai-proxy` features.** When the plugin masks a response, Kong skips `ai-proxy`'s token usage analytics and the `X-Kong-LLM-Model` response header for that response. The response body keeps the same format either way. - **Other plugins that buffer responses.** `plainid-prompt-authz` controls Kong response buffering on its route. A custom plugin on the same route that depends on response buffering can lose it. `ai-proxy` isn't affected. ## Send Transactional Data With Insights Attach `plainid-insights` to any route to send transactional data for each inbound request to the PlainID Collector. PlainID uses this data for agentic insights. You can attach it on its own, or alongside `plainid-api-authz`, `plainid-mcp-authz`, or `plainid-prompt-authz`. Insights never blocks or changes a request, and it sends data for requests that other plugins deny or rate-limit. ``` plugins: - name: plainid-insights config: collector_url: https:///collect collector_certificate_id: enable_tracing: true ``` | Field | Default | Required | Description | | --- | --- | --- | --- | | `collector_url` | | Yes | Full Collector endpoint. Must use `https`. | | `collector_certificate_id` | | One of these two | The ID of a Kong `certificate` entity that holds the client certificate and key. | | `collector_client_cert` | | One of these two | The client certificate PEM, inline or as a vault reference. | | `collector_client_key` | | With `collector_client_cert` | The client key PEM, inline or as a vault reference. | | `collector_timeout_ms` | `1000` | No | Milliseconds to wait for the Collector. The request never waits for the Collector. | | `enable_tracing` | `true` | No | Writes a trace line when sending to the Collector fails. Keep this on: without tracing, a Collector failure is silent. | Provide exactly one certificate source: either `collector_certificate_id`, or `collector_client_cert` together with `collector_client_key`. If you store `collector_client_key` as a vault reference, Kong doesn't check that it matches the certificate, so a wrong reference only fails when the plugin sends data. ### Insights Requirements Like MCP discovery, Insights sends a request's transactional data only when PlainID can attribute it to a specific route and gateway. Make sure that: - **Every route with Insights has a `name`.** Insights skips requests on unnamed routes and logs `InsightsSkippedUnnamedRoute`. - **Your data plane connects to a Konnect control plane.** On a data plane without Konnect, Insights sends no transactional data and logs `InsightsSkippedNoClusterId`. Neither case affects the request, so keep `enable_tracing` on and confirm that neither event appears after you deploy. If a request body is too large for Kong to read into memory, Insights still sends the request's transactional data, without the body, and logs `InsightsBodyUnreadable`. If anything else goes wrong while Insights captures or sends the data, it drops that request's data and logs the failure. Insights never delays, blocks, or fails a request. ### What Insights Sends Insights sends the complete request as transactional data, so attach it only to routes whose traffic you're willing to store in the Collector: - The full request body, without redaction. On a login route, this includes the password. - Every request header, including `Authorization`, `Cookie`, `X-Api-Key`, and `X-Client-Secret`. - The full URL, including the query string. You can't limit what Insights captures. The only control is which routes you attach it to. Treat the Collector as a store that holds credentials. `plainid-mcp-authz` discovery also sends request headers and the URL, but sends the tool inventory instead of the request body. Insights uses one background timer per request while it sends to the Collector. By default, OpenResty allows 256 running timers. On a route above about 256 requests per second with a slow or unreachable Collector, Insights drops transactional data. If you attach Insights to a high-traffic route, raise `lua_max_running_timers` and keep `enable_tracing` on. Insights also reads every request body into memory, including on routes that previously streamed request bodies to the upstream. ## Failure Behavior A caller receives the same denial response for a Policy denial and for an infrastructure failure. Use trace logs to tell them apart. | Condition | Result | | --- | --- | | PlainID permits the request | Kong forwards it unchanged and adds `X-Authorized-By: PlainID`. | | PlainID denies the request | Denied. | | The PDP is unreachable, times out, or returns an error | Denied. | | The AI Content Controls Service is unreachable, times out, or returns an error | Denied. | | The request body is too large to read | `plainid-mcp-authz` and `plainid-prompt-authz` deny the request. `plainid-api-authz` asks PlainID for a decision without body attributes. `plainid-insights` sends the transactional data without the body. | | An LLM request body is compressed | Denied. | | An LLM request body contains no prompt the plugin recognizes | Forwarded unchanged. Without a valid prompt, the LLM can't generate a response. | | The upstream LLM returns a status other than `200` | Forwarded unchanged. | | The caller's Output Control Policy asks for no masking | Response forwarded unchanged. | | The LLM response is compressed, isn't JSON, or isn't in a supported format | Denied. | | Masking fails | Denied. The plugin never returns a partly masked response. | ## Troubleshoot With Trace Logs Set `enable_tracing: true` on a plugin to write one JSON line to the Kong log for each decision, prefixed with `[plainid]`: ``` [plainid] {"requestId":"","result":"Denied","details":"...","timestamp":"..."} ``` | `result` | Meaning | | --- | --- | | `Success` | The plugin permitted the request, or completed a masking step. | | `Denied` | A Policy denied the request. | | `Error` | The plugin couldn't reach or read a response from PlainID, or couldn't read the request. | | `Skipped` | The plugin let the request pass because it wasn't in a format the plugin handles. | The `requestId` matches the `X-Request-ID` header, and the plugin sends the same ID to PlainID, so you can follow one request end to end. For `plainid-prompt-authz`, masking steps also include an `event` field, such as `ResponseMasked` or `ResponseDenied`, and a `format` field showing which response format the plugin masked. Trace lines never contain credentials, at any tracing level. ## Plugin Order Kong runs plugins by priority, from highest to lowest. The PlainID plugins run after Kong's `openid-connect` and `ai-mcp-oauth2` plugins, so the caller's identity is already resolved, and before Kong AI Gateway's `ai-mcp-proxy` and `ai-proxy`, so PlainID enforces Policies before AI Gateway transforms the request. | Plugin | Priority | | --- | --- | | `openid-connect` (Kong) | 1050 | | `ai-mcp-oauth2` (Kong) | 1015 | | `plainid-insights` | 920 | | `rate-limiting` (Kong) | 910 | | `plainid-api-authz` | 909 | | `plainid-mcp-authz` | 899 | | `plainid-prompt-authz` | 890 | | `ai-mcp-proxy` (Kong) | 820 | | `ai-proxy` (Kong) | 770 |