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.
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-proxyorai-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 withplainid-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:
-
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 -
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 -
Enable the plugins in Kong, either through the
KONG_PLUGINSenvironment variable or thepluginsfield inkong.conf:KONG_PLUGINS=bundled,plainid-api-authz,plainid-mcp-authz,plainid-prompt-authz,plainid-insights -
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:
- Open Gateway Manager and select your control plane.
- Go to Plugins > Custom Plugins.
- Select + New Custom Plugin.
- Upload the plugin's
schema.lua. - Repeat for each plugin you installed on your data planes.
See Kong Konnect's documentation 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. | |
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_secretas 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
envvault, which keeps the secret out ofkong.yaml:{vault://env/PLAINID_CLIENT_SECRET}. - The
encryptedsetting onclient_secrettakes 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://<your-api-host>
routes:
- name: cases
paths: [/cases]
plugins:
- name: plainid-api-authz
config:
plainid_pdp_url: https://<pdp-host>/api/runtime
client_id: <your-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. |
MCP Tools
Attach plainid-mcp-authz to an MCP server route. The plugin:
- Filters each
tools/listresponse, so a caller sees only the tools its Policies allow. - Authorizes each
tools/callrequest, 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://<account>.snowflakecomputing.com/api/v2/.../mcp-servers/<server>
routes:
- name: snowflake
paths: [/snowflake]
plugins:
# 1. Validate the caller's token.
- name: ai-mcp-oauth2
config:
authorization_servers: [https://<your-idp>/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://<pdp-host>/api/runtime
client_id: <your-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 <snowflake-token>
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:
| 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 logsDiscoverySkippedUnnamedRoute. - 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:
- Extracts the prompt and conversation from the request.
- Asks the PlainID PDP which Prompt Control categories the caller may use.
- Asks the PlainID AI Content Controls Service which categories the conversation touches.
- 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-Encodingother thanidentity, 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
promptandbothmodes, the plugin denies a request in a supported format that contains no prompt text. For example, an emptymessagesarray, or a Responses API request that refers to a stored prompt or an earlier response instead of sending its owninput. Inoutputmode, 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
outputandbothmodes, the plugin denies any request withstream: true, and any request with anUpgradeheader. - It supports specific LLM providers. When you use
ai-proxy, Output Control supports OpenAI, Azure OpenAI, Anthropic, and Amazon Bedrock (Converse API). Inoutputandbothmodes, 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://<pdp-host>/api/runtime
client_id: <your-client-id>
authentication_method: token
ccs_url: https://<content-controls-host>
prompt_control_asset_type: <your-prompt-control-asset-type>
output_control_asset_type: <your-output-control-asset-type>
Configuration Reference
In addition to the PlainID connection settings:
| 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
modeis nowboth, which requiresoutput_control_asset_type. If your existing configuration has nooutput_control_asset_type, Kong rejects it. Either addoutput_control_asset_type, or setmode: promptto keep Prompt Control only.
Deployment Considerations
- Allow time for long responses. In
outputandbothmodes, the plugin holds the complete response before masking it. Set Kong's proxy timeouts, such asupstream_read_timeout, high enough for your longest generations. - Masked responses skip some
ai-proxyfeatures. When the plugin masks a response, Kong skipsai-proxy's token usage analytics and theX-Kong-LLM-Modelresponse header for that response. The response body keeps the same format either way. - Other plugins that buffer responses.
plainid-prompt-authzcontrols Kong response buffering on its route. A custom plugin on the same route that depends on response buffering can lose it.ai-proxyisn'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://<collector-host>/collect
collector_certificate_id: <kong-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 logsInsightsSkippedUnnamedRoute. - 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, andX-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":"<request id>","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 |