Troubleshooting

Solutions to common integration problems

Authentication issues#

401 UNAUTHORIZED#

Cause: The API key is missing, malformed, or has been revoked.

Fix:

  • Confirm the Authorization header uses the exact format: Bearer YOUR_API_KEY
  • Check that the key has not been revoked in the dashboard
  • Ensure the key belongs to an active account
Shell
# Test your key with a minimal moderation request
curl -X POST https://api.omnifence.ai/api/v1/moderate/text \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "text=hello"

Moderation submissions are multipart/form-data, not JSON. Send each field with -F. A JSON body returns 400 INVALID_REQUEST because the required field is never parsed.

403 ACCOUNT_TERMINATED#

Cause: Your account has been terminated, typically after a manual review of policy violations. See account termination.

Fix: Contact support at hello@omnifence.ai. This state cannot be resolved through the API.


Submission failures#

400 INVALID_REQUEST: missing fields#

Cause: The required field for the endpoint was missing.

Endpoint Required field
/moderate/text text
/moderate/image image
/moderate/video video
/moderate/audio audio

Fix: Include the field. For /moderate/image, pass a publicly reachable image.

Shell
# Correct: image included
curl -X POST https://api.omnifence.ai/api/v1/moderate/image \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "image=https://example.com/photo.jpg"

Using the wrong endpoint for your media type#

Cause: Each modality has its own endpoint and its own required field, listed in the table above. Submitting an image to the text endpoint (or vice versa) returns 400 INVALID_REQUEST.

Fix: Use the endpoint that matches your media type. /moderate/text takes a text field and screens it against your configured moderation policies. See text moderation.

400 INVALID_REQUEST: "image file upload is currently disabled"#

Cause: The /moderate/image endpoint does not accept a direct file upload. It accepts only the image URL field.

Fix: Host the image at a publicly reachable URL and pass it as the image field.

When direct file upload is available, the image file is limited to 10 MB. A larger file returns 400 INVALID_REQUEST.

400 INVALID_REQUEST: "video file upload is currently disabled"#

Cause: The /moderate/video endpoint does not accept a direct file upload. It accepts only the video URL field.

Fix: Host the video at a publicly reachable URL and pass it as the video field.

Video job fails: video too large or unreachable#

Cause: The video field points to a file larger than 100 MB, or the API could not download it. The submission still returns 202, but the job later ends with status: "failed".

Fix: Host a video under 100 MB at a publicly reachable HTTP or HTTPS URL. The API rejects a URL that resolves to a private or internal address, or that uses a non-HTTP scheme, at submission with 400 INVALID_REQUEST.

Image job fails: image unreachable#

Cause: The API could not fetch the image URL. The submission still returns 202, but the job later ends with status: "failed".

Fix: Host the image at a publicly reachable HTTP or HTTPS URL. The API rejects a URL that resolves to a private or internal address, or that uses a non-HTTP scheme, at submission with 400 INVALID_REQUEST.


Webhook issues#

Webhook is not receiving events#

Possible causes and fixes:

  1. No webhook registered: Call POST /api/v1/webhook/register with your URL, or include webhook_url in the moderation request.
  2. Webhook URL is HTTP: The API requires HTTPS. Use an HTTPS endpoint.
  3. Your server timed out: Webhook delivery has a 10-second timeout. Confirm your server responds within that window. See webhook reliability.
  4. Server returned non-2xx: The delivery is retried for 12 attempts over about five hours, and is then abandoned. Check your server logs, and read the result from GET /api/v1/job/{id} for anything that was already abandoned. See webhook reliability.
  5. URL rejected outright: A URL that is malformed, not HTTPS, or resolves to a private or internal address is dropped without any retry. Re-register a valid HTTPS URL.

Webhook fired but the payload looks wrong#

Fix: Verify you are reading the correct fields. The webhook payload format depends on the moderation type. See webhooks for the full schema.

Signature verification fails#

Every callback carries webhook-id, webhook-timestamp and webhook-signature headers. If your verifier rejects them, work through these in order:

  1. You verified a re-serialised body: The signature covers the exact bytes we sent. Parsing the JSON and re-serialising it reorders keys and changes whitespace, so the check can never pass. Pass the raw body — express.raw({ type: 'application/json' }) in Node, request.data in Flask, not request.get_json().
  2. Wrong secret: The secret is per account and starts with whsec_. Paste the whole value, including that prefix, into your Standard Webhooks library. Reveal the current one under Account → Webhooks.
  3. You rotated and have not redeployed: During the grace window webhook-signature carries two space-delimited v1,<base64> values and a library accepts whichever matches. Split that header on spaces — the comma belongs inside each value and is not a separator.
  4. Your server clock has drifted: Libraries reject a webhook-timestamp more than five minutes from their own clock. We sign each attempt as we send it, so a retry is always fresh; a failure across the board points at your clock, not at the retry. Run NTP.
  5. 403 FORBIDDEN when reading the secret: The webhook-secret endpoints need the webhook:manage scope, which is off by default on API keys. Read the secret from the dashboard instead.

See verifying a webhook for the signed string and a worked example.


Job issues#

Job stays queued for a long time#

Cause: A moderation job reports queued for its whole run and then flips straight to completed or failed. It never reports processing or percentage progress, so a long queued period is normal work, not a stall. Times vary: text and image moderation typically complete in seconds; long videos and audio clips take longer, because transcription time scales with clip length.

Fix: Register a webhook instead of polling tightly. If a job has been queued for more than 10 minutes, resubmit and contact support if the issue persists.

404 JOB_NOT_FOUND#

Cause: Either the job ID is incorrect, or the job belongs to a different client account. Jobs are scoped per account.

Fix: Confirm the job_id matches one returned by your account's submission.

is_prohibited is null#

Cause: The job has not yet completed (status is queued or processing), or it failed (status is failed).

Fix: Wait for status: "completed" before reading is_prohibited. For failed jobs, resubmit the request.

scores is missing, or a category key is missing#

Cause: The risk scores are optional. They are absent on an image or video job, on a pending or failed job, and on a completed job that produced no score, for example a text job rejected by the blocked-keyword list. A category you disabled in enabled_categories is not scored, and child-safety categories are never scored.

Fix: Treat scores and custom_scores, and every key in them, as optional. Base your decision on is_prohibited, never on a score.


Rate limiting#

429 RATE_LIMITED#

Cause: You have exceeded your account's request rate. See rate limiting.

Fix: Reduce request frequency or implement exponential backoff. See error recovery for a retry example.


Still stuck?#

Contact support at hello@omnifence.ai with your job_id and the API response body.