Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.omnifence.ai/llms.txt

Use this file to discover all available pages before exploring further.

This page covers how to handle errors at each stage of a moderation request — submission, processing, and result retrieval.

Error types

StageWhat can go wrongRecovery
Submission4xx validation errorFix the request and resubmit
Submission5xx server errorRetry with exponential backoff
ProcessingJob reaches failed statusResubmit the original request
Result retrieval404 JOB_NOT_FOUNDCheck the job ID; the job may belong to a different account
Result retrieval401 UNAUTHORIZEDRefresh or verify your API key

Transient vs permanent errors

Transient errors are temporary and safe to retry:
  • 429 RATE_LIMITED — wait and retry (see rate limiting)
  • 500 INTERNAL_ERROR — server-side failure; retry with backoff
  • 503 — service degraded; retry with backoff
Permanent errors should not be retried without changing something:
  • 400 INVALID_REQUEST — fix the request (missing fields, invalid file format, file too large)
  • 401 UNAUTHORIZED — provide a valid API key
  • 403 ACCOUNT_TERMINATED — contact support; the account cannot be recovered programmatically

Retrying submissions

For 5xx errors, use exponential backoff to avoid overwhelming the API during an outage.
async function submitWithRetry(formData, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch('https://api.evershield.ai/api/v1/moderate/image', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.STARS_API_KEY}` },
      body: formData,
    });

    if (response.ok) return response.json();

    const error = await response.json();

    // Don't retry permanent errors
    if (response.status < 500) throw new Error(error.message);

    // Exponential backoff for server errors
    if (attempt < maxAttempts) {
      await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
    }
  }

  throw new Error('Max retry attempts reached');
}

Handling failed jobs

A job with status: "failed" means processing encountered an unrecoverable error after the job was accepted. is_prohibited will be null.
{
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "image",
  "status": "failed",
  "is_prohibited": null,
  "categories": {
    "custom": {}
  },
  "created_at": "2026-03-24T12:00:00.000Z",
  "completed_at": "2026-03-24T12:00:10.000Z"
}
Failed jobs should be resubmitted as new requests. The original job_id cannot be retried.

Rate limit recovery

When you receive a 429 RATE_LIMITED response, the error body includes details:
{
  "error": "RATE_LIMITED",
  "message": "Rate limit exceeded",
  "statusCode": 429
}
Wait before retrying. The standard rate limit window is 60 seconds. See rate limiting for limits by tier.

Checking job status after a timeout

If your application restarts or a webhook is missed, poll the job status endpoint to recover in-flight jobs:
async function pollUntilComplete(jobId, timeoutMs = 300_000) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const response = await fetch(`https://api.evershield.ai/api/v1/job/${jobId}`, {
      headers: { Authorization: `Bearer ${process.env.STARS_API_KEY}` },
    });
    const job = await response.json();

    if (job.status === 'completed' || job.status === 'failed') return job;

    await new Promise((resolve) => setTimeout(resolve, 2000));
  }
  throw new Error(`Job ${jobId} did not complete within timeout`);
}