API reference
Choose your checks, submit images, and follow asynchronous results through the stable ModeraLens API.
Base URL
This installation uses the following API origin:
https://api.moderalens.comAuthentication
Send an mv_ Bearer key with customer API requests. Keep it on your application server. Account and billing actions require your customer account session.
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.
Find the request you need
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.
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]"{
"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.
curl -X GET "$API_BASE_URL/api/plans"{
"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.
curl -X GET "$API_BASE_URL/api/billing" \
-H "Authorization: Bearer $MODERAVAULT_SESSION"{
"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.
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}'{
"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.
curl -X POST "$API_BASE_URL/api/billing/checkouts/{checkout}/resume" \
-H "Authorization: Bearer $MODERAVAULT_SESSION" \
-H "Content-Type: application/json"{
"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.
curl -X POST "$API_BASE_URL/api/billing/portal" \
-H "Authorization: Bearer $MODERAVAULT_SESSION" \
-H "Content-Type: application/json"{
"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.
curl -X POST "$API_BASE_URL/api/billing/renewal" \
-H "Authorization: Bearer $MODERAVAULT_SESSION" \
-H "Content-Type: application/json" \
-d '{"cancel":true}'{
"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.
curl -X POST "$API_BASE_URL/api/billing/refresh" \
-H "Authorization: Bearer $MODERAVAULT_SESSION" \
-H "Content-Type: application/json"{
"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.
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"}'{
"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.
curl -X POST "$API_BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"your-password"}'{
"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.
curl -X GET "$API_BASE_URL/api/auth/me" \
-H "Authorization: Bearer $MODERAVAULT_SESSION"{
"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.
curl -X POST "$API_BASE_URL/api/auth/logout" \
-H "Authorization: Bearer $MODERAVAULT_SESSION" \
-H "Content-Type: application/json"{
"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.
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"}'{
"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.
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}'{
"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.
curl -X GET "$API_BASE_URL/api/v1/workflows" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
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}}'{
"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.
curl -X PATCH "$API_BASE_URL/api/v1/workflows/{workflow}" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'{
"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.
curl "$API_BASE_URL/api/v1/me" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
curl "$API_BASE_URL/api/v1/capabilities" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
curl "$API_BASE_URL/api/v1/usage" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
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"]}'{
"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.
curl "$API_BASE_URL/api/v1/jobs?limit=20&status=completed" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
curl "$API_BASE_URL/api/v1/jobs/$JOB_ID" \
-H "Authorization: Bearer $MODERAVAULT_API_KEY"{
"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.
Response errors
Check the HTTP status and the returned error body. A failed job is a separate processing outcome and requires manual review.
| Status | Meaning | What to do |
|---|---|---|
401 | Invalid or revoked API key | Reconnect with a valid key. |
402 | Package allowance or payment required | Review your package balance and payment status before submitting again. |
403 | Access not available | Check account activation and whether the credential has permission for this action. |
404 | Job not found | Verify the ID and ownership; expired records may be unavailable. |
409 | Request conflicts with the current state | For image submissions, retry the original key with its original body. Other operations may conflict with an existing subscription or checkout; read the endpoint notes. |
413 | Request too large | Uploads allow up to 10 MiB per image and 20 MiB total. Check server request limits too. |
415 | Unsupported request format | Use multipart/form-data for file uploads and application/json for JSON write requests. |
422 | Validation error | Correct the request fields or image constraints shown in the response. |
429 | Rate or capacity limit | Wait for the applicable request, UTC-day, or pending capacity limit to clear. |
503 | Service unavailable | Check readiness and restore dependencies before retrying. |
no_signal does not mean universally safe. Read coverage and limitations and retain a human review path for unsupported categories and processing failures.