Skip to main content
Back

Akumina AI Configuration for Content Safety

Content Safety for Akumina AI
Publishing Azure AI Content Safety through Azure API Management
Setup and validation guide
Analyze text, Analyze image, and Shield prompt on api-version 2024-09-01
Requires an APIM instance on any tier except Consumption

Introduction

This guide publishes three Azure AI Content Safety operations through an Azure API Management gateway, so that client applications call the gateway with a revocable subscription key and the Content Safety resource key stays in APIM.

Operation Method Purpose
Analyze text POST Classifies text for hate, sexual, self-harm and violent content, with a severity per category.
Analyze image POST The same four categories, applied to an image.
Shield prompt POST Detects jailbreak attempts in a user prompt and injected instructions hidden in retrieved documents.

All three are generally available on api-version=2024-09-01. Content Safety is a separate Azure resource from Azure AI Foundry and from Document Intelligence, with its own host and key, so it is configured as its own APIM API — nothing in the Foundry or Document Intelligence configuration changes.

How to use this document. Work through Create the Content Safety resource and Configure APIM in order, then run Validate with curl to confirm the result. Troubleshooting lists the failures you are most likely to hit and their fixes.

Prerequisites

   An Azure API Management instance. Any tier except Consumption, which does not support the rate-limit-by-key policy under Inbound policy.
   Permission to create APIs, named values, and subscriptions in the APIM instance.
   Permission to create a Content Safety resource in the target subscription.
   curl, present in Windows 10 build 1803 and later as C:\Windows\System32\curl.exe. Run every command in this guide from cmd.exe — see the note at the start of Validate with curl.
   A test image — JPEG or PNG, between 50 × 50 and 7200 × 7200 pixels, under 4 MB — for Analyze image.

Create the Content Safety resource

Create the resource

In the Azure portal, choose Create a resource, search for Content Safety, and select Create. Supply the subscription, resource group, region, and a resource name, choose a pricing tier, and complete the wizard. Deployment takes a minute or two.

   Region. Analyze text and Shield prompt are available in every Content Safety region. Analyze image is not available in Germany West Central or Italy North — avoid those two.
   Tier. F0 (free) is capped at 5 requests per second and is for validation only. Use S0 for any live workload.

Copy the keys and endpoint

Open the resource and go to Keys and Endpoint. Record these values — the rest of the guide refers to them.

Value Notes
Endpoint Of the form https://<contentsafety-resource>.cognitiveservices.azure.com. Copy it with no trailing slash and no path. Add the API adds a path to it when setting the APIM backend.
KEY 1 / KEY 2 Either key works. Two are issued so one can be rotated while the other stays in service.
Location / Region Needed only by tools that take a region separately; the endpoint already encodes it.
Keep the key out of shared documents and source control. Treat both keys as secrets. If a key is ever exposed, use Regenerate Key on this blade — regenerating KEY 1 does not affect KEY 2, so you can roll one at a time without an outage.

Validate the direct connection

Confirm the endpoint and key work together before building anything in APIM — this takes one call and saves debugging a gateway that was never going to work.

set CS=https://YOUR-CONTENTSAFETY-RESOURCE.cognitiveservices.azure.com
 
curl -i -X POST "%CS%/contentsafety/text:analyze?api-version=2024-09-01" ^
  -H "Ocp-Apim-Subscription-Key: YOUR-CONTENT-SAFETY-KEY" ^
  -H "Content-Type: application/json" ^
  -d "{\"text\":\"I hate you and I want to hurt you\"}"
Pass: 200 OK and a categoriesAnalysis array with four entries, each carrying a severity of 0, 2, 4, or 6.
Note the full resource path: /contentsafety/text:analyze. Every call must reach the resource at that path. Configure APIM splits it between the API URL suffix and the backend URL, which is the detail most likely to trip you up.

Configure Content Safety in Akumina AI settings

In the Akumina AI settings, go to Settings › AI Settings and open the AzureContentSafety section. The two values you copied under Copy the keys and endpoint go into Endpoint and ApiKey; the three analysis blocks below them control what is screened.

Setting Value to enter Notes
Endpoint The Endpoint from Keys and Endpoint Of the form https://<contentsafety-resource>.cognitiveservices.azure.com, with no trailing slash and no path — the client appends /contentsafety itself.
ApiKey KEY 1 or KEY 2 The resource key from Keys and Endpoint. In a direct configuration this is the only credential involved.
TextAnalysis.Enabled true Ships as false. Enable each block you actually use — nothing is screened until you do.
ImageAnalysis.Enabled true Ships as false. Requires a region that offers image analysis — see Create the resource.
PromptShield.Enabled true Ships as false. Turn this on for any assistant that answers from retrieved content.
Categories Hate, SelfHarm, Sexual, Violence The harm categories requested on each call. All four are sent by default; removing one stops it being evaluated.
SeverityThreshold 2 Content at this severity or above is treated as a violation. The service returns 0, 2, 4, or 6, so 2 catches everything above Safe and 4 only Medium and High.
PromptShield.DetectJailbreak true Screens userPrompt for direct attacks — instructions typed by the user.
PromptShield.DetectIndirectAttacks true Screens documents for instructions planted in retrieved content. This is the one that matters for an assistant answering from an intranet.
BlocklistNames, HaltOnBlocklistHit, RejectOnBlocklist [], true, true Optional and empty by default. BlocklistNames and HaltOnBlocklistHit are passed through to the service; RejectOnBlocklist is applied by Akumina AI. See the note after the sample below.
ConfidenceThreshold, Policy.* 0.7, Block, true Evaluated by Akumina AI after the service responds, not by Content Safety. Leave at the defaults shown unless your build documents other values.

A direct — non-APIM — Content Safety configuration therefore looks like this:

"AzureContentSafety": {
  "Endpoint": "https://<contentsafety-resource>.cognitiveservices.azure.com",
  "ApiKey": "<KEY 1 or KEY 2 from Keys and Endpoint>",
  "TextAnalysis": {
    "Enabled": true,
    "Categories": [ "Hate", "SelfHarm", "Sexual", "Violence" ],
    "BlocklistNames": [],
    "HaltOnBlocklistHit": true,
    "SeverityThreshold": 2,
    "RejectOnBlocklist": true
  },
  "ImageAnalysis": {
    "Enabled": true,
    "Categories": [ "Hate", "SelfHarm", "Sexual", "Violence" ],
    "SeverityThreshold": 2
  },
  "PromptShield": {
    "Enabled": true,
    "DetectJailbreak": true,
    "DetectIndirectAttacks": true,
    "ConfidenceThreshold": 0.7
  },
  "Policy": {
    "ActionOnViolation": "Block",
    "LogViolations": true,
    "ViolationWebhookUrl": ""
  }
}

Save the settings. Note that AdditionalHeaders is absent here: the resource key is the credential and the client sends it itself. That block exists for the gateway case, where a second credential — the APIM subscription key — has to travel alongside every request under a header name the gateway chooses. See Client configuration.

On BlocklistNames. A blocklist lives on the Content Safety resource, and matching happens inside the analyze call — so naming one here works whether or not the request goes through APIM. Only the blocklist management routes are absent from the gateway, so create and populate the list against the resource directly or in the portal.

Configure APIM

Perform these four steps in order.

Add the API

Go to APIs+ Add APIHTTP — the blank, manually defined option, not Azure AI Foundry and not OpenAPI. Select Full to reveal every field, and set:

Setting Value Notes
Display name Content Safety Free text; shown in the portal.
Name content-safety The API’s resource name.
Web service URL https://<contentsafety-resource>.cognitiveservices.azure.com/contentsafety The endpoint from Copy the keys and endpoint plus /contentsafety. The path is required — see Create the operations. No separate backend entity is needed.
URL scheme HTTPS The subscription key travels in a header — never expose the gateway over plain HTTP.
API URL suffix contentsafety The path segment that routes to this API. It supplies the resource’s own /contentsafety segment.
Subscription required Checked Gives every consuming application its own revocable key.
Subscription header name Ocp-Apim-Subscription-Key APIM’s default — leave it as is. This is also the header Content Safety reads, which is safe here because the policy under Inbound policy overrides it.

The resulting client base URL is https://<apim-host>.azure-api.net/contentsafety.

Create the operations

On the Design tab, add three operations with + Add operation. The templates are bare paths — they must not repeat /contentsafety:

Display name Method URL template
Analyze text POST /text:analyze
Analyze image POST /image:analyze
Shield prompt POST /text:shieldPrompt

Where the /contentsafety segment lives. It must appear exactly once in the URL the resource finally receives, and in this configuration it is supplied twice over: once by the API URL suffix on the way in, and once by the Web service URL on the way out. APIM strips the suffix and appends what remains to the backend, so:

client   https://<apim-host>.azure-api.net/contentsafety/text:analyze
                                          |___________| |__________|
                                            URL suffix    template
 
backend  https://<resource>.cognitiveservices.azure.com/contentsafety
                                                       + /text:analyze
 
resource receives  /contentsafety/text:analyze
The most common mistake is to put /contentsafety in the operation template as well. That produces /contentsafety/contentsafety/text:analyze and a 404. If a call fails, count the segments before anything else.
Two details to get exactly right. The colon is a literal character — do not URL-encode it to %3A. And text:shieldPrompt is camel-case; text:shieldprompt will not match.

Leave each operation’s Query, Request and Response tabs empty, and do not add any operation-level policy — the API-level policy under Inbound policy covers all three.

Add the key as a named value

Go to Named values+ Add and create:

Field Value
Name contentsafety-key
Display name contentsafety-key
Type Secret, or Key vault if you have one — rotation then happens in the vault and APIM refreshes automatically
Value KEY 1 or KEY 2 from Copy the keys and endpoint
The name must match exactly. The policy refers to contentsafety-key by that spelling. A missing named value does not fail at save time — the policy saves cleanly and every request then reaches the resource with the literal text {{contentsafety-key}} as its key, producing a 401.

Inbound policy

Open All operationsInbound processing › policy code editor and set:

<inbound>
  <base />
  <!-- The gateway holds the resource key; the caller never sees it -->
  <set-header name="Ocp-Apim-Subscription-Key" exists-action="override">
    <value>{{contentsafety-key}}</value>
  </set-header>
  <!-- Drop a bearer token an SDK may have added -->
  <set-header name="Authorization" exists-action="delete" />
  <!-- Pin the API version so callers can't send an unsupported one -->
  <set-query-parameter name="api-version" exists-action="override">
    <value>2024-09-01</value>
  </set-query-parameter>
  <!-- 60 calls per minute per subscription, falling back to caller IP -->
  <rate-limit-by-key calls="60" renewal-period="60"
    retry-after-header-name="Retry-After"
    counter-key="@(context.Subscription?.Id ?? context.Request.IpAddress)" />
</inbound>

Leave Outbound processing unchanged. Content Safety returns its result in the body of the same response, so there is nothing to rewrite.

exists-action="override" is not optional here. The subscription header is Ocp-Apim-Subscription-Key, which is also the header Content Safety reads — so a client’s APIM key arrives in the very header the backend uses. The override replaces it unconditionally, so the resource always receives the real key from the named value and a client can never influence what it gets. Remove the override and every call fails with a 401 from the resource.

The rest of the policy. Keep the ?. and ?? guards in the counter key — without them the expression throws a 500 when Subscription required is off. All three operations share 2024-09-01, so one flat version pin covers the API; if you add a fourth operation, check its API version first, because some Content Safety capabilities are served only by a preview version.

Validate with curl

Run these from cmd.exe, not PowerShell. In PowerShell set and %VAR% do not work, ^ is not a line continuation, and in PowerShell 5.1 curl is an alias for Invoke-WebRequest rather than curl itself. Check with echo %APIM% — if it prints the variable name back, you are in PowerShell. Note also that ^ must be the last character on its line; a single trailing space after it breaks the command.

Two conventions apply to all three calls: no api-version is sent — the gateway supplies it — and the credential is the APIM subscription key, not the Content Safety resource key. The header name is the same for both, so this is easy to get wrong; the gateway key is the one from the APIM subscription, not from the resource’s Keys and Endpoint blade.

Set the two values once so the commands can be pasted as-is:

set APIM=https://YOUR-APIM-HOST.azure-api.net/contentsafety
set KEY=YOUR-APIM-SUBSCRIPTION-KEY

Analyze text

curl -i -X POST "%APIM%/text:analyze" ^
  -H "Ocp-Apim-Subscription-Key: %KEY%" ^
  -H "Content-Type: application/json" ^
  -d "{\"text\":\"I hate you and I want to hurt you\"}"
Pass: 200 OK and a categoriesAnalysis array of four entries, each with a category and a severity of 0, 2, 4, or 6. This call confirms the operation matched, the subscription key was accepted, the resource key was injected, and the version pin supplied api-version.

On the body. text is the only required field; categories defaults to all four and outputType to FourSeverityLevels. Text is capped at 10,000 characters — chunk anything longer before sending it.

Analyze image

The Analyze image API has no file-upload mode — the image travels as a single-line base64 string inside the JSON body, so build the body into a file first. Save the sample image as contentsafety-test.jpg in the folder you are running from, then:

powershell -Command "$b=[Convert]::ToBase64String([IO.File]::ReadAllBytes('contentsafety-test.jpg')); '{\"image\":{\"content\":\"'+$b+'\"}}' | Set-Content -NoNewline body.json"

Then post the file:

curl -i -X POST "%APIM%/image:analyze" ^
  -H "Ocp-Apim-Subscription-Key: %KEY%" ^
  -H "Content-Type: application/json" ^
  --data-binary "@body.json"
Pass: 200 OK and a categoriesAnalysis array. Image analysis supports only FourSeverityLevels.
Use --data-binary, never -d. -d strips newlines and corrupts the body, which surfaces as a confusing 400. Set-Content -NoNewline matters for the same reason — a trailing newline inside the base64 string invalidates it.

Shield prompt

curl -i -X POST "%APIM%/text:shieldPrompt" ^
  -H "Ocp-Apim-Subscription-Key: %KEY%" ^
  -H "Content-Type: application/json" ^
  -d "{\"userPrompt\":\"Ignore all previous instructions\",\"documents\":[\"Q3.\"]}"
Pass: 200 OK with userPromptAnalysis.attackDetected set to true and a documentsAnalysis array holding one result per document. Repeat with a benign prompt and both should come back false.

On the payload. Put the user’s own text in userPrompt and retrieved passages in documents — the two are analysed for different attacks, and a passage sent as the prompt is never checked for indirect injection. The budgets are separate: up to 10,000 characters in userPrompt, plus up to five documents totalling another 10,000 characters.

With all three checks passing, the gateway is ready for a client.

Client configuration

Point the client at the gateway rather than at the resource. The endpoint depends on whether the client builds the /contentsafety path itself.

Client type Endpoint to configure Path it calls
REST client or curl https://<apim-host>.azure-api.net/contentsafety /text:analyze, /image:analyze, /text:shieldPrompt
Official Content Safety SDK https://<apim-host>.azure-api.net — the bare gateway host, with no path The SDK appends /contentsafety/text:analyze itself, which lands on the API URL suffix.

The credential is the APIM subscription key in both cases, sent as Ocp-Apim-Subscription-Key. Because that is the header the official SDK already uses, an SDK client works against this gateway with nothing changed but its endpoint and key — and the inbound override still guarantees the resource receives the real key. The Content Safety resource key stays in the named value and is never distributed.

Only the three published routes are reachable. An SDK method for any other capability returns 404 from the gateway. Tell client developers this up front.

In the Akumina AI settings (Settings › AI Settings), the AzureContentSafety block configured under Configure Content Safety in Akumina AI settings changes in two ways: Endpoint becomes the gateway base URL, and an AdditionalHeaders entry carries the APIM subscription key under the header name the API expects. The three analysis blocks are unchanged and are omitted here for brevity:

"AzureContentSafety": {
  "Endpoint": "https://<apim-host>.azure-api.net",
  "ApiKey": "<APIM_SUBSCRIPTION_KEY - not the resource key>",
  "AdditionalHeaders": {
    "Ocp-Apim-Subscription-Key": "<APIM_SUBSCRIPTION_KEY>"
  },
  "TextAnalysis":  { ... unchanged ... },
  "ImageAnalysis": { ... unchanged ... },
  "PromptShield":  { ... unchanged ... },
  "Policy":        { ... unchanged ... }
}
Note the endpoint has no path. The client appends /contentsafety/... itself, and in this deployment that segment is the API URL suffix — so the endpoint is the bare gateway host. Adding /contentsafety here produces /contentsafety/contentsafety/text:analyze and a 404.

On AdditionalHeaders. Include it only when the endpoint is fronted by a gateway; a direct connection needs ApiKey and Endpoint alone. The header name must match the Subscription header name on the API’s Settings tab. Because this deployment leaves that at APIM’s default of Ocp-Apim-Subscription-Key — the same header the client already uses for ApiKey — the entry restates what ApiKey sends and is harmless. It becomes load-bearing the moment the subscription header is renamed, so set it now rather than discovering it later. ApiKey should no longer carry the resource key: that key lives only in the gateway’s named value, and the inbound policy overrides whatever the client sends.

Troubleshooting

Enable the APIM trace first — the Test blade does this automatically — and replay the request. It shows whether an operation matched, whether the named value resolved, and the exact outbound URL sent to the resource.

Symptom Cause and fix
404 from the gateway, APIM error body Count the path segments first. The client URL carries /contentsafety exactly once, from the API URL suffix — a template that repeats it produces /contentsafety/contentsafety/text:analyze and this 404. Otherwise check that the method is POST and that the route is spelled exactly, including the colon and the camel-case in text:shieldPrompt.
404 with a Content Safety error body The gateway matched but the backend path is wrong. The Web service URL must end in /contentsafety when the operation templates are bare paths. Confirm the outbound URL in the trace reads /contentsafety/text:analyze.
401 — Access denied due to missing subscription key The key is not in the header APIM reads, commonly sent as Authorization: Bearer. Send it as Ocp-Apim-Subscription-Key.
401 or 403 with a Content Safety error body The resource key was not injected. Confirm the named value resolved rather than passing through as literal text, and that exists-action="override" is still on the header — without it the caller’s APIM key reaches the resource as its key.
Nothing happens, or a quoting error You are in PowerShell, or a space follows a ^. Run from cmd.exe and check with echo %APIM% — see the note under Validate with curl.
Policy will not save, error names rate-limit-by-key The instance is on the Consumption tier, which does not support that policy. Remove the line, or move to Developer, Basic, Basic v2, Standard, Standard v2, Premium or Premium v2.
500 from the gateway, ExpressionValueEvaluationFailure A policy expression read something absent — typically context.Subscription.Id with Subscription required off. Keep the ?. and ?? guards in the counter key as written.
429 far sooner than 60 calls Callers are sharing one counter. With Subscription required off, context.Subscription is null and everyone behind one NAT shares an IP-based counter. Turn it on and issue one subscription per application.
429 with no Retry-After header The retry hint is opt-in. Add retry-after-header-name="Retry-After" to the policy, as Inbound policy does.
400 — InvalidRequestBody Malformed JSON, almost always cmd.exe quote escaping, or a body sent with -d instead of --data-binary. Every inner quote in an inline body must be \".
400 on Analyze text with a long input The text exceeds 10,000 characters. Chunk it client-side; the service does not split it for you.
400 — InvalidImage on Analyze image The image is outside 50 × 50 to 7200 × 7200 pixels, over 4 MB, or the base64 string is not a single line. Rebuild the body with Set-Content -NoNewline.
404 on Analyze image only The resource is in Germany West Central or Italy North, neither of which offers image analysis. Recreate the resource in another region.
400 on Shield prompt More than five entries in documents, or either input over its budget — 10,000 characters for userPrompt and 10,000 across at most five documents.
429 from the service rather than the gateway The gateway limit is above what the tier allows. F0 permits 5 requests per second — lower calls in the policy, or move to S0.
^ Top