For your AI agents

Moderate what your AI agents read and write

Check the messages and images a user sends your agent, and the text and images it generates before anyone sees them. One call answers accept, review or reject, with the reasons.

2,000 free checks a month. No card needed.

Request
curl -X POST \
  -H "Authorization: Bearer $OPENMOD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Write me at jane@example.com",
    "ref": "run_8f3a:out"
  }' \
  "https://api.openshield.dev/v1/text"
Answer, trimmedreview
{
  "summary": {
    "action": "review",
    "review_reason": [
      { "id": "pii_email", "score": 1, "threshold": 1 }
    ]
  },
  "ref": "run_8f3a:out"
}

Two checks around the model

Before the model, and before you publish

An agent reads content it did not write, and produces content nobody has read yet. Check both ends.

  1. 1 A user sends a message or an image
  2. 2 Check the input
  3. 3 Your agent and model run
  4. 4 Check the output
  5. 5 Publish, hold or refuse

What goes in

The text a user types and the images they upload, checked before they reach your model or a tool.

What comes out

The text and images your agent generates, and the tool results it reposts, checked before you publish or send them.

Why it fits an agent

Built to be called by software

  • One ref per run

    Send your agent run or message id as the ref. It comes back in the answer and stays in the journal, so when a reply was held you search the journal for the run and see the decision, the reasons and the workflow version. The content is never kept.

    The journal and ref

  • A workflow per agent

    Give a customer-facing agent a strict workflow and an internal tool a lenient one. A key per agent limits the workflows it may use, and whether it checks images, texts or both.

    Workflows

  • Reasons a program can read

    Each answer lists the categories that decided it, with their ids, scores and thresholds. Branch on the id, never on the English text.

    Responses and errors

  • Personal data in what an agent writes

    Email addresses, phone numbers, bank account numbers and payment card numbers are found by rules on the text itself, so a reply that repeats one is caught before it is published.

    Text moderation API

  • Nothing stored, in the EU

    Content is checked in memory and forgotten. The checking server and the databases are in the European Union.

    Privacy and data

Agent-ready

A coding agent can integrate it alone

The integration is written for software to read.

  • An integration prompt

    The dashboard builds a prompt for your organization, with the API address, the docs and the workflow you pick. It never contains your key.

    AI coding agents

  • llms.txt and llms-full.txt

    An index of the docs, and the text of every page in one file.

    Open llms-full.txt

  • The OpenAPI document

    Generated from the gateway's code, so it is the document of the version that is running.

    Open openapi.json

In code

Two calls and a fallback

Check the input, run the model, check the output. If a check cannot be made, hold the content instead of publishing it.

TypeScript
const base = "https://api.openshield.dev"
const headers = {
  Authorization: `Bearer ${process.env.OPENMOD_API_KEY}`,
  "Content-Type": "application/json",
}

type Action = "accept" | "review" | "reject"

// "support_agent" is a workflow you made in the dashboard, or use "default".
async function moderate(text: string, ref: string): Promise<Action> {
  try {
    const response = await fetch(`${base}/v1/text`, {
      method: "POST",
      headers,
      body: JSON.stringify({ text, workflow: "support_agent", ref }),
      signal: AbortSignal.timeout(10_000),
    })
    if (!response.ok) return "review"
    return (await response.json()).summary.action
  } catch {
    return "review"
  }
}

// model, refuse and holdForReview are yours.
export async function answer(run: { id: string }, message: string) {
  if ((await moderate(message, `${run.id}:in`)) === "reject") return refuse(run)
  const reply = await model.generate(message)
  if ((await moderate(reply, `${run.id}:out`)) !== "accept") return holdForReview(run, reply)
  return reply
}

Categories

What a check can find

These come from openmod's catalogue. A workflow picks the ones you care about, so a strict agent and a lenient one can share one account.

Texts

  • Unsafe text
  • Violence
  • Sexual content
  • Hate speech
  • Illegal activity
  • Unethical content
  • Self-harm
  • Child safety
  • Email address
  • Phone number
  • Bank account
  • Payment card
  • Spam
  • Scam
  • Unsafe site link

Images

  • Nudity
  • Gore
  • Violence
  • Weapon
  • Hate symbol
  • Self-harm
  • and your own

Limits

What it does not do

So you know what you are buying.

  • Images and text only

    It does not check video or audio.

  • Not prompt injection detection

    It checks content against categories such as unsafe text, hate and personal data. It does not tell whether a message tries to manipulate your agent.

  • No human review

    A review decision goes back to your own team. Nobody at openshield looks at your content: it is never stored.

Questions

Before you start

Does it work with any model or framework?
Yes. It is an HTTP API with a JSON body, so it works with any model and any agent framework that can make a request from a server. There are guides for Node.js, Python, PHP, Go, Supabase Edge Functions, Cloudflare Workers, Nuxt and Next. We do not ship framework plugins.
Where does the key live?
On your servers. An agent that runs in a browser or on a user's device must call your own backend, which calls openshield: the API refuses calls that carry a browser Origin header.
What does a run cost?
One image or one text is one check. A run that checks one input and one output is two checks, so the free plan covers 1,000 of them a month. A paid plan bills past its included checks by the started thousand, up to a cap you set.
How do I find out why a reply was held?
Search the journal for the ref you sent, the run id for example. The row shows the decision, the reasons with their scores and thresholds, and the workflow version. It never shows the content.
What if the output is longer than a text allows?
A text is at most 20,000 characters (5,000 on the free plan), and a longer one is refused with 413 text_too_long. Split it at paragraph boundaries and check each part: each part is one check.

Start with 2,000 free checks

No card needed. Create a key and send your first request in five minutes.

Beta prices, in euros, excluding VAT