Automated Content Safety · Text

Decide what to publish, review, or reject — in one call.

ACS Lite is the lighter, faster, text-only edition of Automated Content Safety. Every verdict names the policy rules that produced it, carries a calibrated probability and severity, and is pinned to the ruleset version bound to your credential.

Base URL
https://acs-lite.api.efficientstack.com/api/v1
Authentication
Bearer credential
Batch ceiling
200 items / request
Spec
OpenAPI 3.1

Introduction

ACS Lite is the text-only edition of Automated Content Safety: a lighter, faster service that decides whether a piece of user-generated text should be published, routed to a human reviewer, or rejected. Each credential is bound to a versioned ruleset. For every rule the service returns a calibrated violation probability, and thresholds convert those probabilities into one of three actions.

allow

Publish immediately. No rule reached its review threshold.

review

Route to a moderator. A rule is suspicious but below the block line.

block

Reject. A rule crossed its block threshold.

Design principles

  • One response shape. Single and batch calls return the same result object, so client code branches once.
  • Explainable outcomes. Every verdict names the rules that fired, with probabilities and a severity band.
  • Deterministic and versioned. The same input under the same ruleset version always yields the same verdict, and the ruleset travels with the response.
  • Policy without code changes. Rulesets, thresholds and rule text are configuration; your integration does not change when policy does.
FieldTypeDescription
Base URL string https://acs-lite.api.efficientstack.com/api/v1
Transport string HTTPS only (TLS 1.2+). JSON request and response bodies, UTF-8.
Authentication string Authorization: Bearer <credential> on every call.
Ruleset object Bound to each credential. Returned in ruleset and X-Ruleset.
Batch ceiling integer 200 items per request.
Content ceiling integer 8,000 characters per item.
Machine-readable spec string /openapi.json (OpenAPI 3.1).

Authentication

Every request carries a bearer credential issued to your integration:

http
Authorization: Bearer acs_live_XXXXXXXXXXXXXXXXXXXXXXXX
  • Credentials are displayed once at issue time and stored only as a digest. If one is lost it must be replaced.
  • They are server-side only. Never embed one in a browser, mobile binary or public repository.
  • Two scopes exist: check for moderation calls and read for /rules and /me. Requesting an out-of-scope endpoint returns 403 insufficient_scope.
  • Each credential is bound to one ruleset and may carry a spend budget. Both are managed by your integration owner.
  • Use a separate credential per workload and environment, so ceilings, scopes, rulesets, budgets and revocation remain independent.
  • Revocation and suspension take effect globally within 60 seconds.

Verify a credential

bash
curl https://acs-lite.api.efficientstack.com/api/v1/me -H "Authorization: Bearer $ACS_API_KEY"

A missing header returns 401 missing_api_key; an unknown or deleted credential returns 401 invalid_api_key. Neither response distinguishes between the two failure causes beyond the code.

Quickstart

Export the credential supplied by your integration owner and make one call.

bash
curl https://acs-lite.api.efficientstack.com/api/v1/check \
  -H "Authorization: Bearer $ACS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "anyone know her real name? i think she works at the cafe on 5th"}'

A blocking verdict looks like this:

json
{
  "object": "moderation",
  "id": "req_8ZQ1nT4pKcVm",
  "decision": "block",
  "flagged": true,
  "score": 4,
  "confidence": 0.91,
  "rules": [
    { "id": "TX-A1", "name": "Doxxing / private information",
      "probability": 0.91, "score": 4, "action": "block" }
  ],
  "reason": "tx-a1 doxxing / private information (p=0.91)",
  "cached": false,
  "ruleset": { "id": "default", "label": "comments-v1", "version": 7 },
  "model": "acs-decision-1",
  "latency_ms": 148
}

Three fields carry the decision: decision is what to do, rules is why, and score (0–5) is how severe. Everything else is metadata you should persist for audit.

Integration checklist
  • Set a client timeout on the inline path (2–3 seconds is typical) and a degradation policy for 5xx and 402.
  • Persist decision, rules, confidence, ruleset.id and ruleset.version with each moderated item.
  • Log X-Request-Id for every non-2xx response; support requests are resolved by that identifier.
  • Use /batch for anything asynchronous and /check for request-time decisions.

Core concepts

Ruleset

A named, versioned group of rules. Every credential is bound to exactly one ruleset, which decides the rules it is evaluated against. Rulesets can share rules — a common baseline — or hold rules and thresholds dedicated to a single integration. GET /api/v1/rules returns your ruleset; its identifier and version also appear in the X-Ruleset and X-Ruleset-Version headers of every verdict.

Rule

A stable identifier, a human name, criteria describing what is prohibited and what is explicitly permitted, and thresholds. A ruleset may override a rule's thresholds for its own clients without affecting anyone else.

Probability

For each evaluated rule the service produces a probability in $[0, 1]$ that the content violates that rule, judged only against that rule's criteria. Probabilities are independent: content can score high on one rule and zero on all others.

Thresholds

Two thresholds per rule convert a probability into an action. The block threshold is mandatory; the review threshold is optional and must sit below it.

text
probability >= block_threshold   ->  block
probability >= review_threshold  ->  review
otherwise                        ->  allow

Severity

score maps the winning probability onto a 1–5 band (default cut points 0.55 / 0.70 / 0.85 / 0.95), so escalation logic can distinguish a marginal hit from an unambiguous one without hard-coding probabilities. An allow verdict has score: 0.

Confidence

For a flagged item, confidence is the probability of the strongest rule. For an allow, it is $1 - p_{max}$ — that is, how clean the item looks. It is always a number in $[0, 1]$.

Budget

A credential may carry a monthly and/or daily spend budget. A hard budget rejects calls with 402 budget_exceeded once reached; a soft budget keeps serving and reports X-Budget-Status: exceeded.

POST /check

Single evaluation

Evaluate one item. This is the endpoint for inline, request-time checks. Passing an array in content is accepted as a convenience and returns a batch-shaped response.

Request body

FieldTypeDescription
content string | string[] required The text to evaluate, up to 8,000 characters per item.
id string optional Your own identifier, echoed back in the result. Truncated to 120 characters.
rules string[] optional Evaluate only these rule identifiers from your credential's ruleset. A narrower subset lowers latency. Identifiers outside your ruleset, or disabled, are rejected.
thresholds object optional Per-rule overrides, e.g. {"TX-A1":{"block":0.5,"review":0.2}}. A bare number is treated as the block threshold.
context object optional content_type, surface, locale. Sharpens judgement when one credential serves several placements.
include string[] optional Additional response fields. probabilities returns every evaluated rule, not only the hits. Defaults to ["rules","reason"].
cache boolean optional Defaults to true. Set false to force a fresh evaluation.
metadata object optional Opaque object echoed back untouched.
client_id string optional Free-form caller reference recorded with the request for correlation.
timeout_ms integer optional Your own server-side budget, 1000–60000. Defaults to 25000.

Response

FieldTypeDescription
object string Always moderation for a single evaluation.
id string Request identifier, identical to the X-Request-Id header.
decision string allow, review or block.
flagged boolean true for review and block. Convenient for a two-way branch.
score integer Severity 0–5 derived from the strongest hit.
confidence number Strongest hit probability, or cleanliness for an allow.
rules array Rules that fired, strongest first: id, name, probability, score, action.
reason string Short machine-generated summary for reviewer interfaces and audit trails.
probabilities object Only when requested through include. Probability for every evaluated rule.
cached boolean Whether the verdict was reused from the verdict cache.
ruleset object id, label and version. Persist them with your decision.
model string Opaque identifier of the evaluation model that produced the verdict.
latency_ms integer Server-side processing time.

Narrowing the evaluation

Pin a rule subset for narrow surfaces — for example only spam rules on a search box — to reduce latency:

json
{
  "content": "…",
  "rules": ["TX-E1", "TX-H1"],
  "include": ["rules", "reason", "probabilities"]
}

POST /batch

Bulk evaluation

One request, up to 200 items, evaluated concurrently. Use it for backfills, imports, re-scans after a policy change and any queue-driven pipeline.

bash
curl https://acs-lite.api.efficientstack.com/api/v1/batch \
  -H "Authorization: Bearer $ACS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "id": "c_1001", "content": "this is excellent work" },
      { "id": "c_1002", "content": "kys, nobody wants you here" },
      { "id": "c_1003", "content": "free premium at sketchy-site.example" }
    ],
    "include": ["rules", "reason"]
  }'

Results are returned in submission order and also carry index:

json
{
  "object": "moderation.batch",
  "id": "req_ba7Kd2Qm",
  "results": [
    { "index": 0, "id": "c_1001", "decision": "allow", "flagged": false,
      "score": 0, "confidence": 0.98, "rules": [], "reason": "", "cached": true },
    { "index": 1, "id": "c_1002", "decision": "block", "flagged": true,
      "score": 4, "confidence": 0.88,
      "rules": [{ "id": "TX-G1", "name": "Self-harm and suicide",
                  "probability": 0.88, "score": 4, "action": "block" }],
      "reason": "tx-g1 self-harm and suicide (p=0.88)", "cached": false },
    { "index": 2, "id": "c_1003", "decision": "review", "flagged": true,
      "score": 2, "confidence": 0.31,
      "rules": [{ "id": "TX-E1", "name": "Spam, scams and off-platform solicitation",
                  "probability": 0.31, "score": 2, "action": "review" }],
      "reason": "needs review: tx-e1 spam, scams and off-platform solicitation (p=0.31)",
      "cached": false }
  ],
  "summary": { "items": 3, "blocked": 1, "review": 1, "allowed": 1, "errors": 0, "cached": 1 },
  "ruleset": { "id": "default", "label": "comments-v1", "version": 7 },
  "model": "acs-decision-1",
  "latency_ms": 212
}

Request body

FieldTypeDescription
items array required Up to 200 entries. Each is a string, or an object with content and optional id and metadata.
rules string[] optional Applies to every item in the batch.
thresholds object optional Applies to every item in the batch.
include string[] optional Applies to every result.
cache boolean optional Defaults to true.

Partial failures

A batch returns 200 whenever the request itself was valid. An item that could not be evaluated carries "decision": "error" and an error object; the remainder of the batch is unaffected. Always inspect summary.errors and retry only the failed indices.

js
const failed = res.results.filter((r) => r.decision === "error");
if (failed.length) await retry(failed.map((r) => items[r.index]));
Sizing guidance

200 is the ceiling, not the target. Batches of 25–50 give the best latency per item; larger batches are more efficient per request but take longer to return. For throughput, run several medium batches in parallel rather than one maximal batch.

GET /rules

Policy introspection · scope read

Returns the ruleset bound to your credential, with its effective thresholds and version. Cache the payload in your application and refresh when version changes; the version is also returned in the X-Ruleset-Version header on every verdict.

json
{
  "object": "list",
  "ruleset": { "id": "acme-comments", "name": "Acme · comments", "label": "acme-comments-v1",
               "version": 7, "updated_at": "2026-09-18T09:12:44.118Z" },
  "defaults": { "block_threshold": 0.35, "review_threshold": 0.18, "score_bands": [0.55, 0.7, 0.85, 0.95] },
  "data": [
    { "id": "TX-A1", "name": "Doxxing / private information",
      "description": "Prohibited: … Allowed: …",
      "thresholds": { "block": 0.3, "review": 0.15 } }
  ]
}

Use this endpoint to render rule names in reviewer interfaces, to validate a rules subset before sending it, and to detect policy changes that warrant re-scoring historical decisions. Thresholds already include any override your ruleset applies.

GET /me

Credential introspection · scope read

Confirms that a credential works and reports its ruleset, entitlements, ceilings, budget and rolling 24-hour usage. budget is null when no budget is configured.

json
{
  "object": "api_key",
  "id": "key_7Rk2pQ",
  "name": "comments-service (production)",
  "status": "active",
  "scopes": ["check", "read"],
  "ruleset": { "id": "acme-comments", "name": "Acme · comments", "label": "acme-comments-v1", "version": 7 },
  "rate_limit": { "requests_per_second": 50, "scope": "per location, approximate" },
  "limits": { "max_batch_items": 200, "max_content_chars": 8000 },
  "usage_24h": { "requests": 184220, "items": 962104, "blocked": 7311, "review": 15402, "input_tokens": 41822190 },
  "budget": {
    "status": "ok", "mode": "hard", "enforced": true,
    "monthly_usd": 500, "daily_usd": null,
    "spent_month_usd": 212.4182, "spent_today_usd": 9.3071,
    "remaining_month_usd": 287.5818, "remaining_today_usd": null,
    "alert_pct": 80,
    "month_resets_at": "2026-10-01T00:00:00.000Z", "day_resets_at": "2026-09-27T00:00:00.000Z"
  },
  "created_at": "2026-04-02T11:20:06.441Z"
}

Usage and spend figures are approximate and refreshed periodically; treat them as operational telemetry rather than billing records.

GET /health

Liveness · no authentication

A dependency-free liveness probe suitable for synthetic monitoring. It does not evaluate content and does not consume rate-limit budget.

bash
curl https://acs-lite.api.efficientstack.com/api/v1/health
json
{ "object": "health", "status": "ok", "version": "1.0.0", "time": "2026-09-26T08:14:02.118Z" }

A 200 from this endpoint means the API surface is reachable. It does not assert that the evaluation path is healthy — use a low-volume /check canary for that.

Tuning thresholds

The strongest hit wins: if any rule blocks, the verdict is block; if none blocks but one reaches its review threshold, the verdict is review.

Precedence

text
request override  ->  ruleset override  ->  rule threshold  ->  platform default

Trialling a change

Thresholds live in your ruleset. Override them per request while you experiment, then ask for the winning values to be published to your ruleset:

json
{
  "content": "…",
  "thresholds": { "TX-E1": { "block": 0.55, "review": 0.25 } }
}

How to move them

  • Lower the block threshold to catch more, at the cost of a higher false-positive rate.
  • Raise it to block less, at the cost of a higher miss rate.
  • Add or widen the review tier when neither trade is acceptable. It converts silent misses into queued items rather than wrongful blocks, and is usually the cheaper intervention.

Measuring

Score a labelled sample with include: ["probabilities"] and evaluate candidate thresholds offline against the returned probabilities. Because the same input yields the same probabilities for a given ruleset version, one scoring pass supports any number of threshold candidates.

json
{ "items": [ … ], "include": ["probabilities"], "cache": false }

Caching and idempotency

Verdicts are reused per content + ruleset version + threshold override + context. Repetitive traffic — and comment traffic is highly repetitive — is served from the verdict cache without re-evaluating the model, and cached verdicts do not count towards your budget. Cached responses are identical in shape and set "cached": true together with an X-Cache header of hit, partial or miss.

  • Identical items within one batch are evaluated once.
  • Publishing a ruleset change invalidates previously cached verdicts automatically.
  • Send "cache": false to force a fresh evaluation, for example when measuring threshold candidates.

Retry safety

Evaluation has no side effects and the same input yields the same verdict for a given ruleset version, so retrying a failed or timed-out request is safe: there is nothing to duplicate and no state to reconcile. An Idempotency-Key header is accepted and ignored, so it is safe to send one from a generic HTTP client.

Budgets and usage

Your integration owner can attach a spend budget to a credential. Spend is the evaluation cost of the calls your credential makes, measured per UTC day and per calendar month. Cached verdicts are free.

FieldTypeDescription
Hard budget mode Once reached, calls return 402 budget_exceeded with a Retry-After header pointing at the next reset.
Soft budget mode Calls continue to be served; responses carry X-Budget-Status: exceeded.
X-Budget-Status header ok, warning (above the alert threshold) or exceeded. Sent only when a budget is configured.
GET /me endpoint Returns spend, remaining amount and reset times under budget.

Behaviour under load

Budgets are enforced at each serving location from a spend view refreshed about every 30 seconds, so a burst of concurrent traffic can overshoot a budget slightly before enforcement catches up. Treat warning as the signal to act.

js
if (res.headers.get("x-budget-status") === "warning") {
  metrics.increment("moderation.budget_warning");
}
if (res.status === 402) {
  return "review";            // budget reached: degrade, never drop
}

Rate limits and headers

Limits are expressed per credential in requests per second and enforced at each serving location, which makes them approximate in aggregate and generous in burst. Read your current limit from GET /api/v1/me or the RateLimit-Limit response header.

  • Exceeding the limit returns 429 with code rate_limit_exceeded.
  • Back off with jitter. A batch of 50 counts as a single request, so batching is the cheapest way to raise throughput.
  • Sustained higher ceilings are a configuration change — request one through your integration owner.

Response headers

FieldTypeDescription
X-Request-Id string Unique per request. Include it in any support conversation.
X-Response-Time-Ms integer Server-side processing time for the request.
X-Cache string hit, partial or miss for the verdict cache.
X-Ruleset string Identifier of the ruleset that produced the verdict.
X-Ruleset-Version integer Version of that ruleset.
X-Budget-Status string ok, warning or exceeded when a budget is configured.
RateLimit-Limit integer Requests per second allowed for the calling credential.

Recommended back-off

js
async function withRetry(fn, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (e) {
      const retryable = e.status === 429 || e.status >= 500;
      if (!retryable || i === attempts - 1) throw e;
      const base = 200 * 2 ** i;                  // 200ms, 400ms, 800ms
      await sleep(base + Math.random() * base);   // full jitter
    }
  }
}

Do not retry 4xx responses other than 429: they describe a problem with the request, or a budget, that a retry cannot fix.

Error handling

Every failure uses one envelope with a stable code you can branch on. type groups codes into families, param names the offending field when applicable, and request_id identifies the call.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "batch_too_large",
    "message": "A batch may contain at most 200 items (received 340).",
    "param": "items",
    "docs": "https://acs-lite.api.efficientstack.com/docs#errors",
    "request_id": "req_8ZQ1nT4pKcVm"
  }
}

Error families

FieldTypeDescription
invalid_request_error type The request was malformed or violated a documented constraint. Fix the call; do not retry.
authentication_error type The credential is missing, unknown, suspended, or lacks the required scope.
budget_error type The credential reached its hard spend budget. Degrade until the reset or until the budget is raised.
rate_limit_error type The credential exceeded its ceiling. Back off with jitter and retry.
api_error type The service or its evaluation path failed. Retry once, then apply your degradation policy.

Reference

StatusCodeAction
400missing_contentSend content for a single check, or items for a batch.
400invalid_content_typeSet Content-Type: application/json.
400invalid_jsonThe body is not valid JSON, or is not a JSON object.
400invalid_itemAn entry in items is neither a string nor an object with content.
400content_too_longTruncate or split the item to the documented character ceiling.
400batch_too_largeChunk the payload into batches of 200.
400empty_batchThe items array contained no entries.
400unknown_ruleRefresh GET /rules; the rule may have been renamed, disabled, or is not part of your ruleset.
400no_rules_selectedNone of the requested rules is enabled in your ruleset.
400invalid_thresholdThresholds must be numbers greater than 0 and at most 1.
401missing_api_keyAdd the Authorization header.
401invalid_api_keyThe credential is unknown or has been deleted.
401api_key_disabledThe credential was suspended. Contact your integration owner.
402budget_exceededThe hard budget is reached. Degrade until Retry-After, or ask for a higher budget.
403insufficient_scopeThe credential lacks check or read.
405method_not_allowedModeration endpoints accept POST only.
429rate_limit_exceededBack off with jitter, or batch to reduce request count.
502upstream_errorEvaluation failed. Retry once, then degrade.
503upstream_rate_limitedEvaluation capacity is saturated. Retry with backoff.
504upstream_timeoutRetry once. Consider a narrower rules subset.

Fail open or fail closed

Decide this deliberately and write it down. Most teams fail open into review: on a 5xx or 402, publish optimistically and queue the item for a human, rather than blocking a legitimate contribution or letting an unchecked item through silently.

js
try {
  const v = await moderate(text);
  return v.decision;                      // allow | review | block
} catch (e) {
  if (e.status === 402 || e.status === 429 || e.status >= 500) {
    metrics.increment("moderation.degraded");
    return "review";                      // degrade, never drop
  }
  throw e;                                 // other 4xx is an integration defect: surface it
}

Integration patterns

Inline, request-time

Call /check on the write path before persisting the item. Use a short client timeout (2–3 seconds) and degrade to review on failure. Store the verdict alongside the item.

text
POST /api/v1/check     ->  allow   ->  publish
                       ->  review  ->  persist as pending + enqueue
                       ->  block   ->  reject with a user-facing message

Asynchronous queue

Accept the item immediately, then evaluate in a worker with /batch. This removes moderation latency from the user's request entirely and is the highest-throughput arrangement.

js
while (const chunk = queue.take(50)) {
  const { results } = await batch(chunk.map((c) => ({ id: c.id, content: c.body })));
  for (const r of results) {
    if (r.decision === "error") queue.requeue(chunk[r.index]);
    else applyVerdict(r.id, r);
  }
}

Re-scan after a policy change

When ruleset.version increments, previously evaluated content may receive a different verdict. Re-scan the affected window with /batch and compare against the stored verdict to produce an exception report.

js
const fresh = await batch(items);
const changed = fresh.results.filter(
  (r, i) => r.decision !== stored[i].decision
);

Reviewer interface

Render rules and reason for the moderator, order the queue by score then confidence, and display ruleset.id and ruleset.version so decisions remain auditable after policy changes. Request include: ["probabilities"] when reviewers benefit from seeing near-miss rules.

Shadow evaluation

To validate a policy change without user impact, keep enforcing the current verdict while recording a second evaluation with candidate thresholds supplied per request. Compare the two streams before asking for the change to be published to your ruleset.

Data handling

The service is designed to hold as little of your content as possible.

FieldTypeDescription
Content in transit TLS 1.2 or higher is required. Plaintext HTTP requests are refused.
Content at rest Submitted text is processed for the duration of the call and is not written to durable storage.
Verdict cache Verdicts are keyed by a cryptographic digest of the content and its evaluation parameters, retained for a bounded, configurable lifetime and invalidated by any policy change.
Operational log Request metadata (timings, verdict, ruleset, activated rules, caller reference) is retained for 24 hours for support and capacity purposes.
Usage accounting Evaluation cost and token counts are aggregated per credential for budgets and capacity planning. They contain no content.
Payload samples An optional truncated sample may be retained with the operational log. Sampling can be disabled entirely for your deployment on request.
Credentials Stored only as salted digests. A credential cannot be recovered, only replaced.

Untrusted input

Submitted content is treated strictly as data, never as instruction. Do not prepend framing, usernames or directives to the text you send: it adds noise, changes probabilities, increases token usage and reduces cache reuse. Send the item exactly as the user wrote it and express everything else through context.

Minimisation

Send only the field being moderated. If you must correlate a verdict with your own records, use id, metadata or client_id rather than embedding identifiers in the content itself — those fields are echoed back and excluded from evaluation.

Code examples

Minimal, production-shaped clients. Each one reads the credential from the environment, fails fast on 4xx and degrades on 5xx.

const BASE = "https://acs-lite.api.efficientstack.com/api/v1";

export async function moderate(content, { signal } = {}) {
  const res = await fetch(`${BASE}/check`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ACS_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ content }),
    signal,
  });

  if (!res.ok) {
    const { error } = await res.json();
    const err = new Error(`${error.code}: ${error.message}`);
    err.status = res.status;
    err.requestId = error.request_id;
    throw err;
  }
  return res.json();
}

const verdict = await moderate(comment.body);

switch (verdict.decision) {
  case "block":  return reject(comment, verdict.rules[0]?.id);
  case "review": return queueForModerator(comment, verdict);
  default:       return publish(comment);
}

Batch helper

Chunking, bounded parallelism and per-item retry in a dozen lines:

js
async function moderateAll(items, { chunk = 50, parallel = 4 } = {}) {
  const chunks = [];
  for (let i = 0; i < items.length; i += chunk) chunks.push(items.slice(i, i + chunk));

  const out = [];
  for (let i = 0; i < chunks.length; i += parallel) {
    const batches = await Promise.all(
      chunks.slice(i, i + parallel).map((c) => withRetry(() => batch(c)))
    );
    for (const b of batches) out.push(...b.results);
  }
  return out;
}

Best practices

  • Store the whole verdict, not just the outcome. Keep decision, rules, confidence, ruleset.id and ruleset.version. When policy changes you can explain, and re-score, every past decision.
  • Use the review tier. It exists so you never have to choose between blocking too much and missing too much.
  • Batch anything asynchronous. Backfills, re-scans and queue workers belong on /batch; reserve /check for the request path.
  • Set a client timeout on the inline path and degrade rather than wait. A moderation call should never be the reason a page hangs.
  • Send only the content. No prompts, no usernames, no surrounding markup.
  • Pin a rule subset for narrow surfaces to cut latency and cost.
  • Cache GET /rules and refresh on version change rather than per request.
  • One credential per workload. Independent ceilings, rulesets, budgets and revocation, attributable usage.
  • Alert on degradation, not only on errors: a rising review rate, a falling cache hit rate or an X-Budget-Status: warning is usually the first sign of a shift.

Versioning

Two independent versions matter to an integration.

FieldTypeDescription
API version path prefix Carried in the URL (/api/v1). Breaking changes ship under a new prefix; the previous prefix continues to serve.
Ruleset version integer Carried in ruleset.version and the X-Ruleset-Version header. Increments whenever your ruleset changes.

What counts as non-breaking

  • New response fields, new optional request fields, new response headers.
  • New rules, renamed rule display names, changed thresholds, changed rule criteria, a changed ruleset assignment.
  • New error codes within an existing type family, and new families for new features.

Write clients that ignore unknown fields and branch on code rather than on message text, and these changes will never require a release on your side.

Changelog

VersionDateChange
v12026-09 · current ACS Lite: rulesets bound to each credential (ruleset.id, X-Ruleset), per-credential spend budgets (402 budget_exceeded, X-Budget-Status, budget in /me) and input-token usage in /me.
v1.0.0initial First general release: /check, /batch, /rules, /me, /health, verdict caching, per-request threshold overrides, severity banding and per-rule probabilities.

Support

Reporting a problem

Include the X-Request-Id of a failing call, the HTTP status and the error code. That is sufficient to locate the request; please do not send customer content.

Limits and policy

Rate ceilings, budgets, batch ceilings, rulesets, rule text and thresholds are configuration. Requests for changes go through your integration owner and require no client release.

Availability

Probe /api/v1/health for reachability. Service levels, maintenance windows and escalation paths are defined in your agreement.

ACS Lite API · v1 · reference generated for acs-lite.api.efficientstack.com Back to top