Documentation/Integration guide
Getting started

Integration guide

Build a reliable asynchronous moderation integration.

API v1Developer guide

Choose the checks your application needs

ModeraLens exposes a stable category API. GET /api/v1/capabilities returns a neutral catalog of id, label, available, and description fields. available means enabled routing is configured for that category; it is not a live health probe. Your application integrates with these category IDs without depending on the underlying service implementation.

POST /api/v1/jobs accepts optional checks: a nonempty array of unique IDs from adult_nsfw, weapon, drug_like_content, graphic_violence, sensitive_document, and ai_generated. Omit checks to request the default five; AI-origin detection is opt-in. An empty array, duplicate IDs, or unknown IDs is invalid.

For files, POST /api/v1/jobs/upload uses multipart reference, images[] and either checks[] or workflow_id. The Image uploads guide covers limits and retries. Both submission modes return the same job/result contract.

The returned job includes its canonical checks list. Only requested categories appear in result.scores, result.categories, and result.checked_categories. Supported checks you did not request are listed in result.not_checked_categories; they were not assessed.

Use immutable image URLs. Completed results include image_hashes: SHA-256 hashes of the original image bytes, ordered to match request.images. Before publishing, compare the SHA-256 hash of the exact bytes you will publish with the corresponding returned hash. A result does not cover replacement bytes served later at the same URL; submit changed images as a new job.

Request body · selected checks
{
  "reference": "catalog-image-42",
  "images": [{ "url": "https://media.example.com/image.jpg" }],
  "checks": ["adult_nsfw", "weapon"]
}

available reflects configured routing, not current service health or accuracy. A check listed in not_checked_categories is different from unsupported_categories, which lists capabilities outside ModeraLens’s supported catalog. Do not interpret either as a passing result.

Follow the job lifecycle

Submission is asynchronous. POST /api/v1/jobs returns HTTP 202; it does not mean the images have been checked. Jobs progress from queued to processing and then to completed or failed.

  • queued: accepted and awaiting a worker.
  • processing: a worker is evaluating the images.
  • completed: a result is available; inspect its decision and coverage.
  • failed: processing did not complete; route the content to manual review.

Do not publish content just because submission succeeded or a job stopped processing.

Retry submissions without duplicates

Send a unique Idempotency-Key containing 16–128 letters, digits, underscores, or hyphens with every new submission. Save the key with the exact request body until you know whether the request was accepted.

If a connection drops or a response is lost, retry with that same key and unchanged body. Generate a new key only for a genuinely new submission. Reusing a key with a changed payload returns HTTP 409.

Server-side JavaScript
const idempotencyKey = crypto.randomUUID();
const body = JSON.stringify({
  reference: "catalog-image-42",
  images: [{ url: "https://media.example.com/image.jpg" }],
});

// Retain idempotencyKey and body for a network retry.
const response = await fetch(`${apiOrigin}/api/v1/jobs`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
  },
  body,
});

Poll and paginate carefully

Retrieve GET /api/v1/jobs/{id} to observe status. Use a modest polling interval with backoff on transient errors, and stop polling once the job is completed or failed. Keep a clear timeout path that routes unresolved content to review.

The job list uses cursor pagination. Start with ?limit=20, then pass the returned meta.next_cursor as cursor. A null cursor means there are no further pages. Reset the cursor when changing the status filter.