Skip to main content

Content Safety API

The content safety endpoint scores a text on seven moderation categories — toxicity, profanity, harassment, hate speech, self-harm, sexual content, and violence — each with a probability from 0 (absent) to 1 (present). Use it to moderate user-generated content, screen LLM outputs before they reach users, or route messages for human review.

For simple token-level profanity flagging, see the profanity endpoint. For determining whether text is AI-generated, see the AI detector.

Sample Code

curl -X POST https://api.sapling.ai/api/v1/safety \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "text":"You are a worthless idiot and everyone hates you."}'

Sample Response

{
"flagged": true,
"flagged_categories": ["toxicity", "harassment"],
"scores": {
"toxicity": 0.92,
"profanity": 0.15,
"harassment": 0.88,
"hate_speech": 0.05,
"self_harm": 0.0,
"sexual": 0.0,
"violence": 0.02
},
"threshold": 0.5
}

Batch Requests

To score many short texts — a queue of chat messages, a page of reviews — in one request, send texts (a list of 1–10 strings) instead of text. The same threshold and spans options apply to every item, and the combined length of all items may be up to 20,000 characters (the same cap as a single text, measured on the submitted text — before HTML stripping). Exactly one of text and texts must be provided.

curl -X POST https://api.sapling.ai/api/v1/safety \
-H "Content-Type: application/json" \
-d '{"key":"<api-key>", "texts": ["You are a worthless idiot.", "What a lovely afternoon!"]}'

The batch response is {"results": [...]} with one entry per input, in input order; each entry has exactly the single-response shape above:

{
"results": [
{
"flagged": true,
"flagged_categories": ["toxicity", "harassment"],
"scores": {
"toxicity": 0.92,
"profanity": 0.15,
"harassment": 0.88,
"hate_speech": 0.05,
"self_harm": 0.0,
"sexual": 0.0,
"violence": 0.02
},
"threshold": 0.5
},
{
"flagged": false,
"flagged_categories": [],
"scores": {
"toxicity": 0.01,
"profanity": 0.0,
"harassment": 0.0,
"hate_speech": 0.0,
"self_harm": 0.0,
"sexual": 0.0,
"violence": 0.0
},
"threshold": 0.5
}
]
}

With spans: true, each entry's span offsets index that entry's own submitted text. Items are billed individually (a batch of N costs the same as N single requests), and each item is cached the same way as a single request — batches and single calls share the cache. If any item fails to score, the whole request returns a 400 and nothing is billed; the items that did succeed are already cached, so a retry only re-runs the failures.

Request Parameters

POST to https://api.sapling.ai/api/v1/safety

key: String
32-character API key. Can also be supplied via the Authorization header as a bearer token; if both are provided, the key parameter takes precedence.

text: String
Text to score, up to 20,000 characters (HTML tags are stripped before scoring). Provide exactly one of text and texts.

texts: List[String]
Batch form (see Batch Requests): 1–10 texts to score in one request, each treated exactly like text above, with a combined length of up to 20,000 characters. An item that is empty after HTML stripping returns a 400 naming its index. The response becomes {"results": [...]}, one entry per item in input order.

threshold: Float, optional, defaults to 0.5
Score at or above which a category is included in flagged_categories. Between 0 and 1 inclusive. Raise it to only flag high-confidence violations; lower it for stricter moderation. The threshold only affects the flags — the raw scores are always returned.

spans: Boolean, optional, defaults to false
When true, the response also includes spans: the specific offending passages, each with its text, character offsets, and per-passage category scores. Use it to highlight, quote, or redact just the violating passage instead of rejecting the whole text.

Response Parameters

flagged: Boolean
True if any category scored at or above the threshold.

flagged_categories: List[String]
Categories that scored at or above the threshold.

scores: Object
A probability from 0 to 1 for each category:

CategoryDescription
toxicityRude, disrespectful, or unreasonable language overall
profanitySwear words or vulgar language
harassmentAttacks, bullying, or intimidation directed at a person or group
hate_speechAttacks on protected characteristics (race, religion, gender, etc.)
self_harmEncouragement or expression of self-harm or suicide
sexualSexually explicit content
violenceThreats, incitement, or graphic descriptions of violence

threshold: Float
The threshold that was applied.

spans: List[Object] (only with spans: true)
The offending passages, most severe first. Each span has:

  • text: the passage, quoted from the submitted text.
  • start / end: character offsets of the passage in the submitted text (text[start:end] is the passage). Offsets count Unicode code points (Python str indexing) — the same unit as the quality API's sentences — not UTF-16 code units or bytes. Both are null when the passage cannot be located in the submitted text — for example when it sits inside stripped HTML markup.
  • scores: the category probabilities reported for this passage alone. Only categories the passage expresses are included; passages scoring below 0.25 on every category are not reported.
  • flagged_categories: the passage's categories at or above the request threshold. Spans with no category at or above the threshold are omitted entirely.

With spans: true the request above returns:

{
"flagged": true,
"flagged_categories": ["toxicity", "harassment"],
"scores": {
"toxicity": 0.92,
"profanity": 0.15,
"harassment": 0.88,
"hate_speech": 0.05,
"self_harm": 0.0,
"sexual": 0.0,
"violence": 0.02
},
"threshold": 0.5,
"spans": [
{
"text": "You are a worthless idiot and everyone hates you.",
"start": 0,
"end": 49,
"scores": {"toxicity": 0.92, "harassment": 0.88},
"flagged_categories": ["toxicity", "harassment"]
}
]
}

Tips

  • Scores are calibrated per category: a text can flag multiple categories at once (for example, a threat is usually both toxicity and violence).
  • Quoted, negated, or educational mentions of a topic score lower than direct expressions, but consider a lower threshold if your application must be conservative.
  • Results for identical text are cached briefly on Sapling's side, so re-checking the same content does not change scores.
  • Spans can overlap (a sentence and a longer passage containing it may each be reported); when highlighting, use the start/end offsets to merge or de-duplicate overlapping regions.
  • Because offsets are Unicode code-point indices, slice with Array.from(text) in JavaScript if the input may contain characters outside the Basic Multilingual Plane — its UTF-16 string indices would otherwise misalign.