← Blog

Claude API Content Moderation Example for Developers

2026-10-18 · 5 min read · SubToAPI Team

Content moderation with Claude works by sending user-generated text (or images) to the model with a prompt that asks it to classify the content against your policy categories, then parsing the structured response in your application. Unlike a dedicated moderation endpoint, Claude gives you flexible, customizable judgment — you define the categories, the severity scale, and the action thresholds yourself.

This guide walks through a working example: a moderation function that takes arbitrary text input, returns a structured verdict (allowed, flagged, blocked), a confidence score per category, and a short explanation — all in one API call you can drop into a signup flow, comment section, or chat app.

Why use Claude instead of a keyword filter

Keyword and regex filters catch obvious terms but miss context. "I want to kill this bug in my code" and "I want to kill my neighbor" both contain "kill," but only one is a threat. Claude reads intent, sarcasm, coded language, and multi-sentence context, which is why most teams layer an LLM check on top of (not instead of) cheap pre-filters.

The tradeoff is latency and cost per call — you don't want to run a full LLM check on every keystroke. The common pattern is: cheap filter first, Claude moderation on anything ambiguous or on all submitted content before it goes live.

Designing the moderation prompt

The core trick is forcing structured, parseable output. Give Claude your exact category list and ask for JSON only — no prose, no hedging.

You are a content moderation system. Classify the following user-submitted
text against these categories: harassment, hate_speech, violence, sexual_content,
self_harm, spam. For each category return a score from 0 (not present) to 1
(clearly present). Also return an overall "action" field: "allow", "review",
or "block". Respond with JSON only, no other text.

Text: """{{user_input}}"""

Claude's instruction-following is strong enough that with this prompt, you consistently get clean JSON you can JSON.parse() without extra cleanup logic in most cases. Still, always wrap parsing in a try/catch and have a fallback (treat unparseable responses as "review").

A working moderation function

Here's a complete example using the Messages API through SubToAPI, which exposes Claude behind a standard HTTPS endpoint with application API keys — useful if you want usage metadata per moderation call without managing Anthropic console credentials directly.

async function moderateContent(text) {
  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 300,
      messages: [
        {
          role: "user",
          content: `You are a content moderation system. Classify the
following text against these categories: harassment, hate_speech, violence,
sexual_content, self_harm, spam. Score each 0-1. Return an "action" field:
"allow", "review", or "block". Respond with JSON only.

Text: """${text}"""`,
        },
      ],
    }),
  });

  const data = await response.json();
  const raw = data.content[0].text;

  try {
    return JSON.parse(raw);
  } catch {
    return { action: "review", error: "unparseable_response", raw };
  }
}

A typical response looks like:

{
  "harassment": 0.1,
  "hate_speech": 0.0,
  "violence": 0.0,
  "sexual_content": 0.0,
  "self_harm": 0.0,
  "spam": 0.85,
  "action": "block"
}

Your application logic then branches on action: allow publishes immediately, review routes to a human queue, block rejects with a user-facing message.

Tuning thresholds per category

Don't treat every category the same. A hate-speech score of 0.3 might warrant an automatic block, while a spam score of 0.3 might just mean "watch this user." Build a small config object instead of hardcoding logic:

const thresholds = {
  hate_speech: 0.3,
  violence: 0.4,
  sexual_content: 0.5,
  self_harm: 0.2,
  harassment: 0.4,
  spam: 0.7,
};

function evaluate(scores) {
  for (const [category, limit] of Object.entries(thresholds)) {
    if (scores[category] >= limit) return "block";
  }
  return "allow";
}

This lets product and legal teams adjust sensitivity without touching prompts or redeploying model logic — just update a config file.

Moderating images and screenshots

Claude's vision capability extends this same pattern to uploaded images: profile pictures, screenshots, memes. Send the image as base64 or a URL in the same message structure, same JSON-output instruction, same category list. This catches things a text-only filter can't: a slur embedded in an image, a violent meme with no caption, or NSFW content uploaded with a blank description.

Batching and cost considerations

Running every comment through a full moderation call adds up. A few practical patterns that keep costs sane:

If you're prototyping this, start with the quickstart to get an API key, then check the messages docs for the full request/response schema you'll be parsing against.

Handling false positives gracefully

No moderation system is perfect. Always give users a path to appeal a block, log the full model response (not just the action) so you can audit decisions later, and periodically sample "allow" verdicts to check for missed violations. Treat the category scores as signals for a decision pipeline, not as ground truth — especially for borderline "review" cases where a human should have the final say.

Questions

Can Claude moderation replace a dedicated moderation API entirely? It can for most text and image use cases, especially when you need custom categories a generic moderation endpoint doesn't support. For extremely high-volume, low-latency needs (like per-keystroke filtering), pair it with a cheap pre-filter.

How do I keep moderation output consistent across requests? Use a strict JSON-only prompt, low temperature settings, and validate the response schema in code. Treat any malformed response as "review" rather than silently allowing it.

Does streaming work for moderation calls? You generally don't need streaming for moderation since you want the full structured verdict before acting — see streaming docs if you have a use case that benefits from partial results anyway.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →