# OpenAI moderation replacement beta

`POST /v1/moderations` takes the same request and returns the same response as OpenAI's moderation endpoint, so the OpenAI SDKs work after you change the base URL and the key.

Beta, and tuned to be cautious.

We set every category to raise few false alarms, so it misses more than OpenAI's own model: on OpenAI's evaluation set it flags about 38% of harmful texts, and it still flags 9% of our harmless everyday test texts, mostly violent figures of speech (“kill the process”, “my feet are killing me”). For toxicity and insults in comments, our

Perspective-compatible scores

are more accurate. Use this endpoint when you need OpenAI's format and categories, and check it on your own texts first.

Endpoint

```http
POST https://layahost.com/v1/moderations
```

layahost is not affiliated with OpenAI. The scores come from a different model, the open Laya model, asked a few typed questions about each text and calibrated into OpenAI's categories. Why use it instead of OpenAI's free endpoint: your text is processed in the EU and never stored, you don't need an OpenAI account, and the same key gives you your own questions and [flows](https://layahost.com/docs/flows) when the fixed categories are not enough. How the two compare on real comments: [OpenAI Moderation API vs layahost](https://layahost.com/openai-moderation-api-comparison).

## Migrate

POST /v1/moderations

```python
from openai import OpenAI

client = OpenAI(
    api_key=LAYAHOST_API_KEY,
    base_url="https://layahost.com/v1",  # was: https://api.openai.com/v1
)

result = client.moderations.create(
    model="omni-moderation-latest",
    input="I will find you and hurt you.",
).results[0]

print(result.flagged, result.categories.violence, result.category_scores.violence)
```

POST /v1/moderations

```javascript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.LAYAHOST_API_KEY,
  baseURL: "https://layahost.com/v1", // was: https://api.openai.com/v1
});

const { results } = await client.moderations.create({
  model: "omni-moderation-latest",
  input: "I will find you and hurt you.",
});

console.log(results[0].flagged, results[0].category_scores.violence);
```

POST /v1/moderations

```bash
curl https://layahost.com/v1/moderations \
  -H "Authorization: Bearer $LAYAHOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "omni-moderation-latest", "input": "I will find you and hurt you."}'
```

The response has OpenAI's shape: an `id`, the `model` you asked for, and one result per input with `flagged`, `categories` (true or false), `category_scores` (0 to 1) and `category_applied_input_types` (always `["text"]`).

## What is supported

| Field | Behaviour |
| --- | --- |
| `input` | A string; a list of up to 32 strings (one result each); or a list of `{"type": "text", "text": ...}` parts, read as one text. Up to 32,000 characters per input. Image parts get `400`: layahost moderates text only. |
| `model` | `omni-moderation-latest</code>, <code>omni-moderation-2024-09-26</code>, <code>text-moderation-latest</code>, <code>text-moderation-stable</code>, <code>text-moderation-007`, or leave it out. All of them get the same layahost moderation; the name is echoed back. |
| Categories | All 13: `harassment</code>, <code>harassment/threatening</code>, <code>hate</code>, <code>hate/threatening</code>, <code>illicit</code>, <code>illicit/violent</code>, <code>self-harm</code>, <code>self-harm/intent</code>, <code>self-harm/instructions</code>, <code>sexual</code>, <code>sexual/minors</code>, <code>violence</code>, <code>violence/graphic`. |
| Errors | OpenAI's shape, `{"error": {"message", "type", "param", "code"}}`, so the SDKs raise their usual exceptions. An empty balance is `429` with code `insufficient_quota`, like OpenAI. |
| Languages | Calibrated on English. Other languages are scored, but less accurately; test on your own texts first. |

## Pricing

OpenAI's moderation endpoint is free; ours is not. Each input is 8 decisions (one per question asked about it), so $0.00012 per text at the pay-as-you-go price, or 125,000 texts per million decisions on a plan. Failed requests are not billed. See [pricing](https://layahost.com/docs/pricing).

## Accuracy

Every category is set to flag at most about 1% of the texts without it in the tuning half, so on everyday traffic false alarms stay rare at the cost of recall. Measured on the held-out half of [OpenAI moderation evaluation set (Markov et al. 2022, "A Holistic Approach to Undesired Content Detection"), 1,680 texts, 8 labelled categories](https://github.com/openai/moderation-api-release) (MIT (Copyright (c) 2022 OpenAI), github.com/openai/moderation-api-release/blob/main/LICENSE), 837 texts that were not used to choose questions or fit calibration. AUC is the chance that a random positive text scores higher than a random clean one (1.0 is perfect, 0.5 is guessing); precision and recall are at our `flagged` threshold for each category.

| Category | Positives | AUC | Precision | Recall |
| --- | --- | --- | --- | --- |
| `harassment` | 39 | 0.927 | 70% | 41% |
| `hate` | 83 | 0.924 | 88% | 17% |
| `hate/threatening` | 21 | 0.959 | 69% | 43% |
| `self-harm` | 25 | 0.984 | 74% | 68% |
| `sexual` | 113 | 0.959 | 86% | 37% |
| `sexual/minors` | 41 | 0.912 | 42% | 12% |
| `violence` | 45 | 0.927 | 100% | 31% |
| `violence/graphic` | 12 | 0.795 | 33% | 8% |
| **`flagged`** |  |  | 82% | 38% |

The dataset has no labels for `harassment/threatening</code>, <code>illicit</code>, <code>illicit/violent</code>, <code>self-harm/intent</code>, <code>self-harm/instructions`. We score them with questions checked on a small hand-written set only, so treat those scores as rough. In particular `self-harm/intent`, `self-harm/instructions` and `illicit/violent` also fire on violent threats against others and on recovery stories: use them as hints, not as decisions.

We also wrote 160 harmless everyday texts (support tickets, reviews, harsh complaints, idioms such as “I could murder a pizza” or “shoot the video”) and never tuned on them: 8.7% were flagged, mostly as `violence` or `self-harm` for violent figures of speech.

Where we are clearly behind OpenAI's own model on this set: `violence/graphic` and `sexual/minors`, and `harassment` misses many direct threats (`harassment/threatening` catches some). The set is about one-third harmful, so on everyday traffic, where harmful text is rare, precision is lower than in the table.

Scores are calibrated probabilities, but they come from a different model than OpenAI's, so the same threshold does not mean the same thing. If your code acts on `category_scores` with fixed thresholds, re-tune them on a [test set](https://layahost.com/docs/test-sets) of your own texts, or use `flagged` and `categories`.
