Ana içeriğe geç

AI Topic Guard Policy

General Information​

Policy Type​

ai-topic-guard

Endpoints​

List Policies​

GET /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/

Add Policy​

POST /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Update Policy​

PUT /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Delete Policy​

DELETE /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Topic Matching​

The prompt is embedded and compared, by cosine similarity, against the descriptions in allowedTopics and deniedTopics. A denied-topic match is evaluated before allowedTopics, and wins — a prompt matching a denied topic is flagged/blocked even if it would otherwise pass the allow list.

Fields​

FieldTypeRequiredDescription
allowedTopicsarray of stringsNoAdmin-defined descriptions of the topics the traffic is allowed to be about.
similarityThresholdnumberNoMinimum cosine similarity, -1.0 to 1.0, for the prompt to be considered on-topic against allowedTopics.
actionstringNoFLAG or BLOCK on an off-topic (allow-list) detection.
deniedTopicsarray of stringsNoAdmin-defined descriptions of topics that are never allowed, regardless of allowedTopics. Can be populated by hand, or copied from the platform's built-in content-safety category catalog (Guardrails hub → Content Safety tab, MLCommons AILuminate hazard categories) — either way, this field stores plain text; no catalog reference is retained.
deniedSimilarityThresholdnumberNoMinimum cosine similarity, -1.0 to 1.0, for a deniedTopics match. null falls back to similarityThreshold.
deniedActionstringNoFLAG or BLOCK on a denied-topic match. null falls back to action.
embeddingProviderNamestringNoName of the LLM provider connection config used to compute embeddings. Apinizer resolves the name to the provider ID when it saves the policy. It looks in the project of the API proxy first, then in the admin project. A provider in the own project wins when both have the same name. Apinizer rejects an unknown name with 400. An ID of a provider that is visible in the project is also accepted.
embeddingModelIdstringNoEmbedding model identifier.
embeddingDimensionintegerNoExpected embedding vector dimension.

Evaluation Timing​

FieldTypeRequiredDescription
latencyModestringNoINLINE (default) or ASYNC. INLINE blocks the request thread for the embed + similarity check; ASYNC runs it bounded by maxWaitMs.
maxWaitMsintegerConditionalMax wait in milliseconds under ASYNC. Must be positive if set.
failOpenOnErrorbooleanNoWhether an embedding failure lets the request pass (true) or blocks it (false). An embedding failure includes an embedding provider that Apinizer cannot find at runtime. If you omit the field, the policy blocks (false). SHADOW never blocks.

External Guardrail​

engine decides which decision sources feed the policy's verdict.

ValueBehaviour
BUILTIN (default)Only the embedding similarity check (allowedTopics/deniedTopics) is evaluated.
BUILTIN_PLUS_EXTERNALThe similarity check and the external adapter both run. Either one can trigger a block.
EXTERNAL_ONLYOnly the external adapter is evaluated.

failMode governs what happens when the external adapter call itself errors or times out. It also governs the case where the external adapter or its provider cannot be resolved (for example, the adapter is missing or the connection was deleted). In that case, FAIL_OPEN continues with the BUILTIN evaluation only:

ValueBehaviour
FAIL_CLOSED (default)The request is blocked.
FAIL_OPENThe request continues; a FAIL_OPEN signal is recorded so the bypass is visible.

externalGuardrail Fields​

Only the llm-judge adapter is supported on this policy.

FieldTypeRequiredDescription
typestringYesMust be llm-judge.
llmProviderNamestringYesName of the LLM provider connection config used to run the judge call. Required whenever externalGuardrail is present, regardless of type. Apinizer looks for the name in the project of the API proxy first, then in the admin project (shared providers). A provider in the own project wins when both have the same name.
promptTemplatestringConditionalJudge prompt template. Required, non-blank, for a chat judge on this policy — unlike the Prompt Guard policy, a blank template here falls back to a generic native safety judge that does not actually answer a topic-relevance question, so it is rejected at save time instead (error key prompttemplaterequired). Not required for a TypeSafe Jev or TEI judge.
modelIdstringNoJudge model identifier.
jsonModebooleanNoWhether the judge is asked to respond in JSON mode.
maxOutputTokensintegerNoJudge max output tokens. Must be positive if set.
timeoutMsintegerNoHTTP call timeout in milliseconds. Must be positive if set.
maxInputCharsintegerNoMax input characters sent to the judge. Must be positive if set.
decisionModestringNoSTANDARD or REASONING. Unset is STANDARD. llm-judge only, chat judge. STANDARD: temperature 0, small output limit, default timeoutMs 2000, default maxOutputTokens 64. REASONING: for thinking models; no temperature is sent, maxOutputTokens is the budget for thinking and verdict together, default timeoutMs 15000 (up to 60000), default maxOutputTokens 1024. A value that you set always wins over the default
decisionThresholdnumberNoVerdict threshold for TypeSafe Jev and TEI judges. Range 0.05 to 0.99. Default 0.5. A value outside the range is set to the nearest limit. Jev: unsafe when the probability is at or above the value. TEI: unsafe when a selected label scores at or above the value
unsafeLabelsarray[string]ConditionalTEI judge only. Labels that mean unsafe, for example INJECTION and JAILBREAK. Matching is not case-sensitive. Required for a TEI judge: at least one label (error key unsafelabelsrequired).
decisionQuestionstringConditionalTypeSafe Jev judge only. The yes/no question that Jev answers about the text. yes means unsafe. Required for a TypeSafe Jev judge on this policy (error key decisionquestionrequired). Jev does not see allowedTopics and deniedTopics, so name your topics in the question. Default question in the Manager UI: Is this prompt about a topic that this AI service must not handle, outside its intended business scope?

Judge providers. llmProviderName can name an OpenAI-compatible chat provider, an Anthropic provider, a TypeSafe Jev provider (decision model), or a TEI provider (guard classifier). AWS Bedrock, Google Vertex AI, Cohere, the native Gemini wire format, embedding-only providers, and custom providers without an adapter family cannot judge. The provider must use credentials held by the AI gateway (Provided by AI gateway). TypeSafe Jev and TEI make decisions only: they do not use promptTemplate, jsonMode, or maxOutputTokens, and they cannot be a routing target, a pool member, a failover target, or an embedding provider.

If the judge provider refuses to answer for safety reasons, the verdict is UNSAFE with the category provider-refusal. FAIL_OPEN does not let this request through. maxWaitMs still limits the whole check, so a slow reasoning judge is cut off by it and failMode decides.


Other Fields​

FieldTypeRequiredDescription
decodeEncodedContentbooleanNoDecodes base64/hex/URL/unicode segments before embedding. Default false.
includeSystemPromptsbooleanNoAlso embeds system-role messages in the topic comparison. null means false — only the last user message is embedded by default.
includeAssistantPromptsbooleanNoAlso embeds assistant-role messages. null means false.
includeToolPromptsbooleanNoAlso embeds tool-role messages. null means false.
Opt-in, not opt-out

Unlike the role-scope fields on the Prompt Guard policy, includeSystemPrompts/includeAssistantPrompts/includeToolPrompts here default to embedding only the last user message. Turning any of them on switches this guard to whole-conversation scanning, which changes embedding size, cost and similarity scores — opt in deliberately.


List Policies​

Endpoint​

GET /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/

Request​

Headers​

HeaderValue
AuthorizationBearer {token}
Content-Typeapplication/json

Path Parameters​

ParameterTypeRequiredDescription
projectNamestringYesProject name
apiProxyNamestringYesAPI Proxy name

Response​

Success Response (200 OK)​

{
"status": "SUCCESS",
"resultList": [
{
"apiProxy": {
"name": "MyAiProxy",
"requestPolicyList": [
{
"type": "ai-topic-guard",
"name": "support-scope-guard",
"description": "Keeps the support assistant on billing/account topics",
"active": true,
"allowedTopics": [
"billing and invoices",
"account settings and password resets"
],
"similarityThreshold": 0.78,
"action": "BLOCK",
"deniedTopics": [
"medical advice or diagnosis"
],
"deniedSimilarityThreshold": 0.7,
"deniedAction": "BLOCK",
"embeddingProviderName": "internal-embedding-model",
"embeddingModelId": "text-embedding-3-small",
"embeddingDimension": 1536,
"latencyMode": "INLINE",
"failOpenOnError": true,
"engine": "BUILTIN"
}
],
"responsePolicyList": [],
"errorPolicyList": []
}
}
],
"resultCount": 1
}

cURL Example​

curl -X GET \
"https://demo.apinizer.com/apiops/projects/MyProject/apiProxies/MyAiProxy/policies/" \
-H "Authorization: Bearer YOUR_TOKEN"

Add Policy​

Endpoint​

POST /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Request​

Headers​

HeaderValue
AuthorizationBearer {token}
Content-Typeapplication/json

Path Parameters​

ParameterTypeRequiredDescription
projectNamestringYesProject name
apiProxyNamestringYesAPI Proxy name
policyNamestringYesPolicy name

Request Body​

Full JSON Body Example — Allow List Plus Denied Topics​
{
"operationMetadata": {
"targetScope": "ALL",
"targetPipeline": "REQUEST",
"deploy": true,
"deployTargetEnvironmentNameList": ["production"],
"order": 1
},
"policy": {
"type": "ai-topic-guard",
"description": "Keeps the support assistant on billing/account topics",
"active": true,
"allowedTopics": [
"billing and invoices",
"account settings and password resets"
],
"similarityThreshold": 0.78,
"action": "BLOCK",
"deniedTopics": [
"medical advice or diagnosis"
],
"deniedSimilarityThreshold": 0.7,
"deniedAction": "BLOCK",
"embeddingProviderName": "internal-embedding-model",
"embeddingModelId": "text-embedding-3-small",
"embeddingDimension": 1536,
"latencyMode": "ASYNC",
"maxWaitMs": 1000,
"failOpenOnError": true
}
}
Full JSON Body Example — With an External LLM-Judge​
{
"operationMetadata": {
"targetScope": "ALL",
"targetPipeline": "REQUEST",
"deploy": true,
"deployTargetEnvironmentNameList": ["production"],
"order": 2
},
"policy": {
"type": "ai-topic-guard",
"description": "Embedding similarity plus a judge model as a second opinion",
"active": true,
"allowedTopics": ["billing and invoices"],
"similarityThreshold": 0.78,
"action": "BLOCK",
"embeddingProviderName": "internal-embedding-model",
"engine": "BUILTIN_PLUS_EXTERNAL",
"failMode": "FAIL_CLOSED",
"externalGuardrail": {
"type": "llm-judge",
"llmProviderName": "internal-guard-model",
"promptTemplate": "Is the following user message strictly about billing, invoices, or account settings? Answer YES or NO.\n\nMessage: {{input}}",
"timeoutMs": 2000
}
}
}
Example — TypeSafe Jev Judge​

With a decision-only judge, promptTemplate is not needed. decisionQuestion is required:

"externalGuardrail": {
"type": "llm-judge",
"llmProviderName": "typesafe-jev",
"modelId": "jev-latest",
"decisionQuestion": "Is this message about something other than billing, invoices, or account settings?",
"decisionThreshold": 0.5
}

Request Body Fields​

See Topic Matching, Evaluation Timing, External Guardrail and Other Fields above. type (ai-topic-guard), description and active follow the common policy contract; name comes from the path parameter.

Response​

Success Response (200 OK)​

{
"status": "SUCCESS",
"deploymentResult": {
"success": true,
"deploymentResults": [
{
"environmentName": "production",
"success": true,
"message": "Deployment successful"
}
]
}
}

Error Responses​

CodeReason
400Blank name; similarityThreshold/deniedSimilarityThreshold outside -1.0–1.0; maxWaitMs not positive; engine is not BUILTIN but externalGuardrail is missing; externalGuardrail.type/llmProviderName blank; the judge instruction that matches the provider kind is missing: externalGuardrail.decisionQuestion for a TypeSafe Jev provider, externalGuardrail.unsafeLabels (at least one) for a TEI provider, externalGuardrail.promptTemplate for a chat provider (the error message names the missing field); timeoutMs/maxOutputTokens/maxInputChars not positive; proxy not found.
401Missing or invalid token.
403The token's account lacks permission on the project.

cURL Example​

curl -X POST \
"https://demo.apinizer.com/apiops/projects/MyProject/apiProxies/MyAiProxy/policies/support-scope-guard/" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operationMetadata": {
"targetScope": "ALL",
"targetPipeline": "REQUEST",
"deploy": true,
"deployTargetEnvironmentNameList": ["production"],
"order": 1
},
"policy": {
"type": "ai-topic-guard",
"description": "Keeps the support assistant on billing/account topics",
"active": true,
"allowedTopics": ["billing and invoices"],
"similarityThreshold": 0.78,
"action": "BLOCK",
"embeddingProviderName": "internal-embedding-model"
}
}'

Update Policy​

Endpoint​

PUT /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Request​

The body is the same shape as Add Policy. The update replaces the policy, so send the fields you want to keep — including every entry in allowedTopics and deniedTopics.

Full JSON Body Example — Adding a Denied Topic​
{
"operationMetadata": {
"targetScope": "ALL",
"targetPipeline": "REQUEST",
"deploy": true,
"deployTargetEnvironmentNameList": ["production"],
"order": 1
},
"policy": {
"type": "ai-topic-guard",
"description": "Keeps the support assistant on billing/account topics",
"active": true,
"allowedTopics": ["billing and invoices"],
"similarityThreshold": 0.78,
"action": "BLOCK",
"deniedTopics": [
"medical advice or diagnosis",
"legal advice"
],
"deniedAction": "BLOCK",
"embeddingProviderName": "internal-embedding-model"
}
}

Response​

Success Response (200 OK)​

{
"status": "SUCCESS",
"deploymentResult": {
"success": true,
"deploymentResults": [
{
"environmentName": "production",
"success": true,
"message": "Deployment successful"
}
]
}
}

cURL Example​

curl -X PUT \
"https://demo.apinizer.com/apiops/projects/MyProject/apiProxies/MyAiProxy/policies/support-scope-guard/" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operationMetadata": {
"targetScope": "ALL",
"targetPipeline": "REQUEST",
"deploy": true,
"deployTargetEnvironmentNameList": ["production"]
},
"policy": {
"type": "ai-topic-guard",
"active": true,
"allowedTopics": ["billing and invoices"],
"similarityThreshold": 0.78,
"action": "BLOCK",
"deniedTopics": ["medical advice or diagnosis", "legal advice"],
"embeddingProviderName": "internal-embedding-model"
}
}'

Delete Policy​

Endpoint​

DELETE /apiops/projects/{projectName}/apiProxies/{apiProxyName}/policies/{policyName}/

Request​

Headers​

HeaderValue
AuthorizationBearer {token}

Path Parameters​

ParameterTypeRequiredDescription
projectNamestringYesProject name
apiProxyNamestringYesAPI Proxy name
policyNamestringYesPolicy name

Response​

Success Response (200 OK)​

{
"status": "SUCCESS"
}

cURL Example​

curl -X DELETE \
"https://demo.apinizer.com/apiops/projects/MyProject/apiProxies/MyAiProxy/policies/support-scope-guard/" \
-H "Authorization: Bearer YOUR_TOKEN"

Notes and Warnings​

  • A denied-topic match always wins. deniedTopics is evaluated before allowedTopics, so a prompt matching a denied topic is flagged/blocked even if it would also match an allowed topic.
  • deniedSimilarityThreshold/deniedAction left null track similarityThreshold/action dynamically, not a frozen copy taken at save time — changing the allow-list threshold later also changes the effective denied-topic threshold, unless you set the denied fields explicitly.
  • An embed-call failure blocks the request when failOpenOnError is omitted or false. An embedding provider that Apinizer cannot find at runtime is handled the same way. Set failOpenOnError to true only if passing the request through during an embedding-provider outage is the safer failure mode for this traffic.
  • embeddingProviderName must name an existing provider. Older versions stored the name instead of the provider ID, and the gateway skipped the check without an error. The upgrade migration converts the stored names. See Off-Topic Protection.
  • promptTemplate is mandatory for a chat llm-judge external adapter on this policy — unlike the Prompt Guard policy, a blank template is rejected rather than silently falling back to a generic judge that can't answer a topic-relevance question. A TypeSafe Jev judge needs decisionQuestion instead. A TEI judge needs unsafeLabels instead.
  • includeSystemPrompts/includeAssistantPrompts/includeToolPrompts default to off — enabling any of them switches this guard from "last user message only" to whole-conversation embedding, which changes cost and similarity scores; opt in deliberately.
  • deniedTopics entries copied from the built-in content-safety catalog carry no back-reference. Editing the catalog category later does not update a policy that already copied its description — re-copy it by hand if you want the update.