Documentation/API reference
REFERENCE

API reference

Choose your checks, submit images, and follow asynchronous results through the stable ModeraLens API.

REST · JSON & file uploadsBearer authenticationAPI v1

Base URL

This installation uses the following API origin:

API_BASE_URL
https://api.moderalens.com

Authentication

Send an mv_ Bearer key with customer API requests. Keep it on your application server. Account and billing actions require your customer account session.

Read the authentication guide →

Examples use placeholders and illustrative data. Set API_BASE_URL and MODERAVAULT_API_KEY in your trusted terminal or server environment. Read the capability catalog before choosing checks. Unselected categories are not assessed; omitting the checks field requests the five default checks. Select ai_generated explicitly for the optional, experimental AI-origin check; it only requests review and does not prove origin.

ENDPOINTS

Find the request you need

Response errors ↓

23 endpoints

POST/api/v1/jobs/uploadUpload images for moderationCustomer

Upload images for moderation

Submit private files using the same asynchronous job, workflow, quota and billing contract as URL submissions.

Request · cURL
curl "$API_BASE_URL/api/v1/jobs/upload" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY" \
  -H "Idempotency-Key: upload-product-123-01" \
  -F "reference=product-123" \
  -F "checks[]=weapon" \
  -F "images[][email protected]"
Illustrative response
{
  "data": {
    "job_id": "<job-uuid>",
    "reference": "product-123",
    "status": "queued",
    "checks": [
      "weapon"
    ],
    "workflow": null,
    "result": null,
    "error": null,
    "attempts": 0,
    "created_at": 1793318400,
    "updated_at": 1793318400
  }
}
  • Use multipart/form-data; let your client set the boundary. 1–8 images, 10 MiB/file, 16 megapixels/image and 20 MiB total. Static JPEG, PNG and WebP only.
  • Use checks[] or workflow_id, never both. Omit both for the five default checks. Retry identical ordered file bytes and metadata with the same key.
  • Upload access requires an active customer but no image hostname. Files are private and active copies are removed after terminal settlement. Poll the normal job endpoint; 202 is not a moderation decision.
GET/api/plansPublished packagesPublic

Published packages

Read public monthly USD packages and checkout configuration status.

Request · cURL
curl -X GET "$API_BASE_URL/api/plans"
Illustrative response
{
  "data": [],
  "checkout_ready": false
}
  • Only published packages are visible. No payment credentials are returned.
GET/api/billingCustomer billingCustomer

Customer billing

Read your package, period, consumed/reserved allowance and invoice history.

Request · cURL
curl -X GET "$API_BASE_URL/api/billing" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION"
Illustrative response
{
  "data": {
    "managed": false,
    "status": "manual",
    "plan": null,
    "used": 0,
    "reserved": 0,
    "remaining": 0,
    "checkout_ready": false,
    "invoices": [],
    "checkouts": []
  }
}
  • Requires a customer account session. Invoice history is tenant-scoped and limited to 50 recent records. The response also includes period_start, period_end, cancel_at_period_end and Stripe account/subscription presence flags.
POST/api/billing/checkoutCreate Stripe checkoutCustomer

Create Stripe checkout

Snapshot a published package and open monthly hosted Stripe checkout.

Request · cURL
curl -X POST "$API_BASE_URL/api/billing/checkout" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: replace-with-stable-checkout-key" \
  -d '{"plan_id":2}'
Illustrative response
{
  "data": {
    "checkout_id": "<checkout-uuid>",
    "url": "https://checkout.stripe.com/c/pay/<session>"
  }
}
  • Activated account and Stripe configuration required. Use a stable Idempotency-Key (16–128 characters). One unexpired pending checkout or recurring subscription per customer.
  • A checkout URL never grants paid entitlement; verified payment is required.
POST/api/billing/checkouts/{checkout}/resumeResume checkoutCustomer

Resume checkout

Resume your own pending checkout without starting another subscription.

Request · cURL
curl -X POST "$API_BASE_URL/api/billing/checkouts/{checkout}/resume" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json"
Illustrative response
{
  "data": {
    "checkout_id": "<checkout-uuid>",
    "url": "https://checkout.stripe.com/c/pay/<session>"
  }
}
  • Expired or completed checkouts return 409; another customer's ID returns 404.
POST/api/billing/portalStripe billing portalCustomer

Stripe billing portal

Create a short-lived Stripe portal session for the current customer.

Request · cURL
curl -X POST "$API_BASE_URL/api/billing/portal" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json"
Illustrative response
{
  "data": {
    "url": "https://billing.stripe.com/p/session/<session>"
  }
}
  • Portal configuration is required in Stripe. No arbitrary customer ID or return URL is accepted.
POST/api/billing/renewalCancel or resume renewalCustomer

Cancel or resume renewal

Set cancellation at the paid period end, or resume monthly renewal.

Request · cURL
curl -X POST "$API_BASE_URL/api/billing/renewal" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"cancel":true}'
Illustrative response
{
  "message": "Renewal setting updated."
}
  • Requires an existing Stripe subscription. Paid access lasts until the recorded period end.
POST/api/billing/refreshSynchronize paymentCustomer

Synchronize payment

Read current Stripe state to reconcile a missed webhook.

Request · cURL
curl -X POST "$API_BASE_URL/api/billing/refresh" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json"
Illustrative response
{
  "message": "Stripe status synchronized."
}
  • Uses authenticated server-to-Stripe reads; no payment assertion is accepted from the browser.
POST/api/auth/registerRegister customer accountPublic

Register customer account

Create a pending customer account. Public registration never creates administrators.

Request · cURL
curl -X POST "$API_BASE_URL/api/auth/register" \
  -H "Content-Type: application/json" \
  -d '{"name":"My service","email":"[email protected]","password":"replace-with-a-strong-password","password_confirmation":"replace-with-a-strong-password"}'
Illustrative response
{
  "data": {
    "session_token": "<one-time-session-token>",
    "expires_at": 1793275200,
    "account": {
      "id": 1,
      "name": "My service",
      "email": "[email protected]",
      "role": "user",
      "client_id": 1,
      "active": false
    }
  }
}
  • Requires 12–128 password characters and matching confirmation.
  • Administrator activation is required before /api/v1 usage. Exact image hosts are required only for URL submissions.
  • Rate limited. Never log credentials or the returned session token.
POST/api/auth/loginSign inPublic

Sign in

Sign in to your customer account and receive a four-hour session.

Request · cURL
curl -X POST "$API_BASE_URL/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"your-password"}'
Illustrative response
{
  "data": {
    "session_token": "<one-time-session-token>",
    "expires_at": 1793275200,
    "account": {
      "id": 1,
      "name": "My service",
      "email": "[email protected]",
      "role": "user",
      "client_id": 1,
      "active": true
    }
  }
}
  • Sessions are opaque, stored hashed on the backend and must be protected like credentials.
  • Customer and administrator privileges remain separate.
GET/api/auth/meRead account statusCustomer

Read account status

Read the account attached to a session, including pending activation status.

Request · cURL
curl -X GET "$API_BASE_URL/api/auth/me" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION"
Illustrative response
{
  "data": {
    "id": 1,
    "name": "My service",
    "email": "[email protected]",
    "role": "user",
    "client_id": 1,
    "active": false
  }
}
  • Requires an mvs_ account session, not an integration API key.
POST/api/auth/logoutSign outCustomer

Sign out

Revoke the current account session.

Request · cURL
curl -X POST "$API_BASE_URL/api/auth/logout" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json"
Illustrative response
{
  "message": "Signed out."
}
  • Requires an account session. The browser also clears its in-memory credentials.
POST/api/auth/passwordChange passwordCustomer

Change password

Change the current account password and revoke all account sessions.

Request · cURL
curl -X POST "$API_BASE_URL/api/auth/password" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"current_password":"current-password","password":"new-strong-password","password_confirmation":"new-strong-password"}'
Illustrative response
{
  "message": "Password changed. Sign in again."
}
  • Requires a valid account session and current password.
  • Integration API keys are managed separately.
POST/api/v1/api-key/rotateCreate or rotate integration keyCustomer

Create or rotate integration key

Display a fresh customer API key once.

Request · cURL
curl -X POST "$API_BASE_URL/api/v1/api-key/rotate" \
  -H "Authorization: Bearer $MODERAVAULT_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"grace_seconds":300}'
Illustrative response
{
  "api_key": "<save-new-api-key-once>"
}
  • Only an active customer account session may call this endpoint; mv_ integration keys cannot.
  • Grace period 0–3600 seconds, default 0. Only the most recent previous key can remain valid during grace.
GET/api/v1/workflowsList workflowsCustomer

List workflows

List this customer’s saved moderation rules, including disabled workflows.

Request · cURL
curl -X GET "$API_BASE_URL/api/v1/workflows" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": [
    {
      "id": "5f5b8c68-2908-421f-a530-652faebbe002",
      "name": "Product images",
      "checks": [
        "adult_nsfw",
        "weapon"
      ],
      "thresholds": {
        "nsfw_review": 0.55,
        "nsfw_block": 0.82,
        "vision_review": 0.25,
        "vision_block": 0.5
      },
      "enabled": true,
      "version": 1,
      "created_at": 1793260800,
      "updated_at": 1793260800
    }
  ]
}
  • Maximum 100 workflows per customer; other customers cannot view these records.
POST/api/v1/workflowsCreate workflowCustomer

Create workflow

Store selected checks and thresholds for future jobs.

Request · cURL
curl -X POST "$API_BASE_URL/api/v1/workflows" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Product images","checks":["adult_nsfw","weapon"],"thresholds":{"nsfw_review":0.55,"nsfw_block":0.82,"vision_review":0.25,"vision_block":0.5}}'
Illustrative response
{
  "data": {
    "id": "5f5b8c68-2908-421f-a530-652faebbe002",
    "name": "Product images",
    "checks": [
      "adult_nsfw",
      "weapon"
    ],
    "thresholds": {
      "nsfw_review": 0.55,
      "nsfw_block": 0.82,
      "vision_review": 0.25,
      "vision_block": 0.5
    },
    "enabled": true,
    "version": 1,
    "created_at": 1793260800,
    "updated_at": 1793260800
  }
}
  • name, checks and all four thresholds required; enabled defaults to true.
  • Thresholds must be numbers in 0–1 with review below block. Sensitive documents and AI-generated signals remain review-only.
PATCH/api/v1/workflows/{workflow}Update workflowCustomer

Update workflow

Update rules or disable new submissions. Existing jobs keep their snapshot.

Request · cURL
curl -X PATCH "$API_BASE_URL/api/v1/workflows/{workflow}" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
Illustrative response
{
  "data": {
    "id": "5f5b8c68-2908-421f-a530-652faebbe002",
    "name": "Product images",
    "checks": [
      "adult_nsfw",
      "weapon"
    ],
    "thresholds": {
      "nsfw_review": 0.55,
      "nsfw_block": 0.82,
      "vision_review": 0.25,
      "vision_block": 0.5
    },
    "enabled": false,
    "version": 2,
    "created_at": 1793260800,
    "updated_at": 1793260800
  }
}
  • Replace {workflow} with your workflow UUID. Fields are optional; thresholds, if sent, must include all four keys.
  • Submit jobs with workflow_id instead of checks. Exact idempotent retries preserve the original job.
GET/api/v1/meGet current clientCustomer

Get current client

Read the connected client, allowed image hosts, and current limits.

Request · cURL
curl "$API_BASE_URL/api/v1/me" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": {
    "id": 1,
    "name": "My application",
    "allowed_hosts": ["media.example.com"],
    "limits": {
      "requests_per_minute": 60,
      "jobs_per_day": 1000,
      "images_per_day": 5000,
      "max_pending": 20
    }
  }
}
  • Example limits are illustrative. Read the actual limits returned for your client.
GET/api/v1/capabilitiesList available checksCustomer

List available checks

Read the category catalog and configured routing availability before selecting checks.

Request · cURL
curl "$API_BASE_URL/api/v1/capabilities" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": [
    { "id": "adult_nsfw", "label": "Adult content", "available": true, "description": "Adult content signals." },
    { "id": "weapon", "label": "Weapons", "available": true, "description": "Weapon-related visual signals." },
    { "id": "drug_like_content", "label": "Drug-related content", "available": true, "description": "Drug-like visual content signals." },
    { "id": "graphic_violence", "label": "Graphic violence", "available": true, "description": "Graphic injury and violence signals." },
    { "id": "sensitive_document", "label": "Sensitive documents", "available": true, "description": "Potentially sensitive document signals." },
    { "id": "ai_generated", "label": "AI-generated images (experimental)", "available": false, "description": "Experimental synthetic-image signal; review only, not proof of origin." }
  ]
}
  • Labels, descriptions, and availability above are illustrative. Read current values from the API.
  • This neutral catalog contains no provider names, URLs, model metadata, or provenance.
  • ai_generated is optional and experimental. It uses vision_review, never automatically blocks, and does not prove whether an image is real or generated. Ask your administrator to enable this check.
  • Use the id values in POST /api/v1/jobs checks. available means enabled routing is configured; it is not a live health check or accuracy guarantee.
GET/api/v1/usageGet usageCustomer

Get usage

Read submission totals, pending capacity, status counts, and daily activity.

Request · cURL
curl "$API_BASE_URL/api/v1/usage" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": {
    "utc_day_start": 1793232000,
    "jobs_submitted": 0,
    "images_submitted": 0,
    "pending_jobs": 0,
    "counts": { "queued": 0, "processing": 0, "completed": 0, "failed": 0 },
    "daily": [{ "date": "<YYYY-MM-DD>", "jobs": 0, "images": 0 }],
    "limits": { "requests_per_minute": 60, "jobs_per_day": 1000, "images_per_day": 5000, "max_pending": 20 }
  }
}
  • utc_day_start is a numeric Unix epoch timestamp in seconds. Submission quotas use UTC days. Example values do not represent live usage.
POST/api/v1/jobsSubmit imagesCustomer

Submit images

Create one asynchronous job containing 1–8 image URLs and optional selected checks.

Request · cURL
curl -X POST "$API_BASE_URL/api/v1/jobs" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: image-request-0000001" \
  -d '{"reference":"catalog-image-42","images":[{"url":"https://media.example.com/image.jpg"}],"checks":["adult_nsfw","weapon"]}'
Illustrative response
{
  "data": {
    "job_id": "<job-uuid>",
    "reference": "catalog-image-42",
    "checks": ["adult_nsfw", "weapon"],
    "status": "queued",
    "result": null,
    "error": null,
    "attempts": 0,
    "created_at": 1800000000,
    "updated_at": 1800000000
  }
}
  • Returns HTTP 202. A reference is required and must contain 1–128 characters.
  • checks is optional. If omitted, the five default categories are requested. If supplied, it must be a nonempty array of unique catalog IDs from GET /api/v1/capabilities.
  • The response includes the canonical checks list. Changing selected checks changes the payload, so keep checks unchanged when retrying the original idempotency key.
  • Idempotency-Key is required: 16–128 letters, digits, underscores, or hyphens. Retry the same key with exactly the same body after a network failure.
  • Only static JPEG, PNG, and WebP. Default image limits: 10 MiB / 16 MP. Exact allowed hosts only; redirects and private IP addresses are rejected.
GET/api/v1/jobsList jobsCustomer

List jobs

List the current client’s jobs with cursor pagination and an optional status filter.

Request · cURL
curl "$API_BASE_URL/api/v1/jobs?limit=20&status=completed" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": [],
  "meta": { "next_cursor": null }
}
  • Status: queued, processing, completed, or failed.
  • Pass meta.next_cursor as cursor on the next request. A null cursor marks the final page.
  • Reset pagination when you change filters. List entries use the same job schema as job detail.
GET/api/v1/jobs/{id}Get job detailCustomer

Get job detail

Retrieve job status, result coverage, scores, policy, and processing errors.

Request · cURL
curl "$API_BASE_URL/api/v1/jobs/$JOB_ID" \
  -H "Authorization: Bearer $MODERAVAULT_API_KEY"
Illustrative response
{
  "data": {
    "job_id": "<job-uuid>",
    "reference": "catalog-image-42",
    "checks": ["adult_nsfw", "weapon"],
    "status": "failed",
    "result": null,
    "error": { "code": "<processing-error-code>", "decision": "review" },
    "attempts": 1,
    "created_at": 1800000000,
    "updated_at": 1800000010
  }
}
  • Timestamps are Unix epoch seconds. Results are null until available.
  • Completed results include decision (block, review, no_signal), categories, scores, checked_categories, not_checked_categories, unsupported_categories, image_hashes, policy_version, and thresholds.
  • Each categories value is a decision string, never a boolean: for example {"adult_nsfw":"no_signal","weapon":"review","graphic_violence":"block"}. Compare exact values; no_signal is a nonempty string and must not be interpreted through a truthiness check.
  • image_hashes contains SHA-256 hashes of the original image bytes in request.images order. Use immutable URLs and verify the exact bytes you publish against the corresponding hash. Changed bytes require a new moderation job; a URL alone does not identify the checked content.
  • Only requested checks appear in scores, categories, and checked_categories. not_checked_categories is the supported catalog minus the requested checks; unsupported_categories is the fixed list of unsupported areas.
  • A result with checks [adult_nsfw, weapon] leaves drug_like_content, graphic_violence, sensitive_document, and ai_generated in not_checked_categories. No outcome is asserted for those omitted checks.
  • The stable customer response excludes model metadata and provider names, URLs, or provenance. Provider configuration is administrator-only.
  • HTTP 404 covers both missing jobs and another client’s jobs. Failed jobs require manual review.
ERROR HANDLING

Response errors

Check the HTTP status and the returned error body. A failed job is a separate processing outcome and requires manual review.

API error status codes and recommended handling
StatusMeaningWhat to do
401Invalid or revoked API keyReconnect with a valid key.
402Package allowance or payment requiredReview your package balance and payment status before submitting again.
403Access not availableCheck account activation and whether the credential has permission for this action.
404Job not foundVerify the ID and ownership; expired records may be unavailable.
409Request conflicts with the current stateFor image submissions, retry the original key with its original body. Other operations may conflict with an existing subscription or checkout; read the endpoint notes.
413Request too largeUploads allow up to 10 MiB per image and 20 MiB total. Check server request limits too.
415Unsupported request formatUse multipart/form-data for file uploads and application/json for JSON write requests.
422Validation errorCorrect the request fields or image constraints shown in the response.
429Rate or capacity limitWait for the applicable request, UTC-day, or pending capacity limit to clear.
503Service unavailableCheck readiness and restore dependencies before retrying.
Interpret a result in context.

no_signal does not mean universally safe. Read coverage and limitations and retain a human review path for unsupported categories and processing failures.