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
| Stage | What can go wrong | Recovery |
|---|
| Submission | 4xx validation error | Fix the request and resubmit |
| Submission | 5xx server error | Retry with exponential backoff |
| Processing | Job reaches failed status | Resubmit the original request |
| Result retrieval | 404 JOB_NOT_FOUND | Check the job ID; the job may belong to a different account |
| Result retrieval | 401 UNAUTHORIZED | Refresh 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`);
}