Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Analyze text and images for harmful content with customizable blocklists.
.claude/skills/azure-ai-contentsafety-ts/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
Analyze text and images for harmful content with customizable blocklists.
bashnpm install @azure-rest/ai-content-safety @azure/identity @azure/core-auth
bashCONTENT_SAFETY_ENDPOINT=https://<resource>.cognitiveservices.azure.com CONTENT_SAFETY_KEY=<api-key>
Important: This is a REST client. ContentSafetyClient is a function, not a class.
typescriptimport ContentSafetyClient from "@azure-rest/ai-content-safety"; import { AzureKeyCredential } from "@azure/core-auth"; const client = ContentSafetyClient( process.env.CONTENT_SAFETY_ENDPOINT!, new AzureKeyCredential(process.env.CONTENT_SAFETY_KEY!) );
typescriptimport ContentSafetyClient from "@azure-rest/ai-content-safety"; import { DefaultAzureCredential } from "@azure/identity"; const client = ContentSafetyClient( process.env.CONTENT_SAFETY_ENDPOINT!, new DefaultAzureCredential() );
typescriptimport ContentSafetyClient, { isUnexpected } from "@azure-rest/ai-content-safety"; const result = await client.path("/text:analyze").post({ body: { text: "Text content to analyze", categories: ["Hate", "Sexual", "Violence", "SelfHarm"], outputType: "FourSeverityLevels" // or "EightSeverityLevels" } }); if (isUnexpected(result)) { throw result.body; } for (const analysis of result.body.categoriesAnalysis) { console.log(`${analysis.category}: severity ${analysis.severity}`); }
typescriptimport { readFileSync } from "node:fs"; const imageBuffer = readFileSync("./image.png"); const base64Image = imageBuffer.toString("base64"); const result = await client.path("/image:analyze").post({ body: { image: { content: base64Image } } }); if (isUnexpected(result)) { throw result.body; } for (const analysis of result.body.categoriesAnalysis) { console.log(`${analysis.category}: severity ${analysis.severity}`); }
typescriptconst result = await client.path("/image:analyze").post({ body: { image: { blobUrl: "https://storage.blob.core.windows.net/container/image.png" } } });
typescriptconst result = await client .path("/text/blocklists/{blocklistName}", "my-blocklist") .patch({ contentType: "application/merge-patch+json", body: { description: "Custom blocklist for prohibited terms" } }); if (isUnexpected(result)) { throw result.body; } console.log(`Created: ${result.body.blocklistName}`);
typescriptconst result = await client .path("/text/blocklists/{blocklistName}:addOrUpdateBlocklistItems", "my-blocklist") .post({ body: { blocklistItems: [ { text: "prohibited-term-1", description: "First blocked term" }, { text: "prohibited-term-2", description: "Second blocked term" } ] } }); if (isUnexpected(result)) { throw result.body; } for (const item of result.body.blocklistItems ?? []) { console.log(`Added: ${item.blocklistItemId}`); }
typescriptconst result = await client.path("/text:analyze").post({ body: { text: "Text that might contain blocked terms", blocklistNames: ["my-blocklist"], haltOnBlocklistHit: false } }); if (isUnexpected(result)) { throw result.body; } // Check blocklist matches if (result.body.blocklistsMatch) { for (const match of result.body.blocklistsMatch) { console.log(`Blocked: "${match.blocklistItemText}" from ${match.blocklistName}`); } }
typescriptconst result = await client.path("/text/blocklists").get(); if (isUnexpected(result)) { throw result.body; } for (const blocklist of result.body.value ?? []) { console.log(`${blocklist.blocklistName}: ${blocklist.description}`); }
typescriptawait client.path("/text/blocklists/{blocklistName}", "my-blocklist").delete();
| Category | API Term | Description | |----------|----------|-------------| | Hate and Fairness | Hate | Discriminatory language targeting identity groups | | Sexual | Sexual | Sexual content, nudity, pornography | | Violence | Violence | Physical harm, weapons, terrorism | | Self-Harm | SelfHarm | Self-injury, suicide, eating disorders |
| Level | Risk | Recommended Action | |-------|------|-------------------| | 0 | Safe | Allow | | 2 | Low | Review or allow with warning | | 4 | Medium | Block or require human review | | 6 | High | Block immediately |
Output Types:
FourSeverityLevels (default): Returns 0, 2, 4, 6EightSeverityLevels: Returns 0-7typescriptimport ContentSafetyClient, { isUnexpected, TextCategoriesAnalysisOutput } from "@azure-rest/ai-content-safety"; interface ModerationResult { isAllowed: boolean; flaggedCategories: string[]; maxSeverity: number; blocklistMatches: string[]; } async function moderateContent( client: ReturnType<typeof ContentSafetyClient>, text: string, maxAllowedSeverity = 2, blocklistNames: string[] = [] ): Promise<ModerationResult> { const result = await client.path("/text:analyze").post({ body: { text, blocklistNames, haltOnBlocklistHit: false } }); if (isUnexpected(result)) { throw result.body; } const flaggedCategories = result.body.categoriesAnalysis .filter(c => (c.severity ?? 0) > maxAllowedSeverity) .map(c => c.category!); const maxSeverity = Math.max( ...result.body.categoriesAnalysis.map(c => c.severity ?? 0) ); const blocklistMatches = (result.body.blocklistsMatch ?? []) .map(m => m.blocklistItemText!); return { isAllowed: flaggedCategories.length === 0 && blocklistMatches.length === 0, flaggedCategories, maxSeverity, blocklistMatches }; }
| Operation | Method | Path | |-----------|--------|------| | Analyze Text | POST | /text:analyze | | Analyze Image | POST | /image:analyze | | Create/Update Blocklist | PATCH | /text/blocklists/{blocklistName} | | List Blocklists | GET | /text/blocklists | | Delete Blocklist | DELETE | /text/blocklists/{blocklistName} | | Add Blocklist Items | POST | /text/blocklists/{blocklistName}:addOrUpdateBlocklistItems | | List Blocklist Items | GET | /text/blocklists/{blocklistName}/blocklistItems | | Remove Blocklist Items | POST | /text/blocklists/{blocklistName}:removeBlocklistItems |
typescriptimport ContentSafetyClient, { isUnexpected, AnalyzeTextParameters, AnalyzeImageParameters, TextCategoriesAnalysisOutput, ImageCategoriesAnalysisOutput, TextBlocklist, TextBlocklistItem } from "@azure-rest/ai-content-safety";
This skill is applicable to execute the workflow or actions described in the overview.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.