Build with nocensor.ai
REST API for image generation, video, face swap, enhance, and multi-stage pipelines. Scoped API key auth, async job polling, webhooks — same credit system as the UI.
API key auth
Bearer token with per-key rate limits. Max 5 keys per account.
Async jobs
202 Accepted + polling. No blocking requests. Signed URLs when complete.
Same credit system
Credits deducted on submission. Failed jobs auto-refunded by cron.
Prompt safety
Same content filter as the UI. CSAM blocked at all tiers.
Authentication
All endpoints (except /api/v1/health) require a Bearer token:
Authorization: Bearer nc_live_<your_key>
API keys are generated in Settings → API Keys. Keys are shown once at creation — store them securely.
Each key carries one or more scopes. Using a key on an endpoint outside its scope returns 403 INSUFFICIENT_SCOPE.
| Scope | Grants access to |
|---|---|
| generation | POST /generate, /video, /face-swap, /enhance, /pipelines, /undress |
| mgmt | GET /jobs, /jobs/{id}, /account, /credits, /payments, /models, /characters, /pipelines/{id}; DELETE /jobs/{id} |
| webhooks | All /webhooks/* endpoints |
API Reference
Full OpenAPI 3.1 spec available at /openapi.yaml.
Generation
/api/v1/generateGenerate an image
/api/v1/videoGenerate a video
/api/v1/undressAI undress
/api/v1/face-swapSwap faces in an image
/api/v1/enhanceEnhance / upscale an image
Pipelines
/api/v1/pipelinesSubmit a multi-stage image pipeline
Add `?dry_run=true` to get a cost breakdown (200) without submitting the job (202).
/api/v1/pipelines/{id}Get pipeline status
Jobs
/api/v1/jobsList generation jobs
/api/v1/jobs/{id}Get job status / result
/api/v1/jobs/{id}Cancel a pending job
/api/v1/jobs/{id}/eventsStream live job events (Server-Sent Events)
Events: `snapshot` (job detail) on connect; `status`; `progress` `{step,total,percent,stage:{key,label}}` (best-effort); terminal `completed` | `failed` | `cancelled` then close; `reconnect` at ~55s. Resume with `Last-Event-ID`. Authenticate with a Bearer key, or with `?token=` from `events_url` (browser-safe, 10 min, one job).
Webhooks
/api/v1/webhooksList webhooks
/api/v1/webhooksCreate a webhook
The `secret` field is only returned at creation time.
/api/v1/webhooks/{id}Get webhook details
/api/v1/webhooks/{id}Update a webhook
/api/v1/webhooks/{id}Delete a webhook
/api/v1/webhooks/{id}/deliveriesList webhook delivery attempts
/api/v1/webhooks/{id}/testSend a test delivery
Account
/api/v1/accountGet account details
/api/v1/creditsGet credit balance
/api/v1/paymentsList payment history
/api/v1/charactersList available characters
/api/v1/modelsList available models
System
/api/v1/healthHealth check
Quick start
Use the official TypeScript SDK (@nocensor/sdk) for end-to-end type safety, retries, and polling — or hit the REST API directly with curl.
SDK (Node, browsers, Deno, Bun, Workers)
npm install @nocensor/sdk
import { NoCensor } from '@nocensor/sdk'
const nc = new NoCensor({ apiKey: process.env.NOCENSOR_API_KEY })
const job = await nc.generate.createAndWait({
prompt: 'beautiful woman, photorealistic',
model: 'realistic',
})
console.log(job.outputs[0].url)1. Submit a job with curl (202 Accepted)
curl -X POST https://nocensor.ai/api/v1/generate \
-H "Authorization: Bearer nc_live_<your_key>" \
-H "Content-Type: application/json" \
-d '{"prompt": "beautiful woman, photorealistic", "model": "realistic"}'
# 202 Accepted
# {
# "data": { "id": "job_...", "status": "pending" },
# "meta": { "poll_url": "/api/v1/jobs/job_...", "credits_remaining": 490 }
# }2. Poll until complete
curl https://nocensor.ai/api/v1/jobs/<job_id> \
-H "Authorization: Bearer nc_live_<your_key>"
# 200 OK — when done:
# {
# "data": {
# "id": "job_...", "status": "completed",
# "outputs": [{ "url": "https://...", "media_type": "image", "dimensions": { "width": 832, "height": 1216 }, "expires_at": "..." }]
# }
# }Rate limits
| Limit | Value | Scope |
|---|---|---|
| Generation | 10 rpm | POST /generate, /video, /face-swap, /enhance, /pipelines, /undress |
| Management | 60 rpm | GET /jobs, /jobs/{id}, /account, /credits, /payments, /models, /characters, /pipelines/{id}; DELETE /jobs/{id} |
| Webhooks | 10 rpm | All /webhooks/* endpoints |
| Active keys | 5 max | Per account |
All responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. Rate-limited responses include a Retry-After header.
Error codes
| Code | HTTP | Trigger |
|---|---|---|
| UNAUTHORIZED | 401 | Missing, invalid, or revoked API key |
| INVALID_EVENTS_TOKEN | 401 | /jobs/{id}/events `token` is invalid, expired, or issued for another job |
| FORBIDDEN | 403 | Account suspended or key missing required scope |
| INSUFFICIENT_SCOPE | 403 | API key lacks the required scope for this endpoint |
| INSUFFICIENT_CREDITS | 402 | Not enough credits for the workflow |
| PURCHASE_REQUIRED | 402 | Endpoint requires a prior real purchase (e.g. /undress) |
| VIDEO_REQUIRES_PAID_TIER | 402 | /video — paid tier required; route the user to the /credits upsell page |
| RATE_LIMITED | 429 | Per-key or per-user rate limit exceeded |
| VALIDATION_ERROR | 422 | Request failed schema validation — `details[]` lists every failing field |
| INVALID_REQUEST | 400 | Malformed or unparseable JSON body |
| INVALID_IDEMPOTENCY_KEY | 400 | Idempotency-Key is not 1-255 printable ASCII characters |
| INVALID_INPUT | 400 | Invalid or missing request field |
| INVALID_WORKFLOW | 400 | Unknown model or workflow id in the request |
| UNKNOWN_WORKFLOW | 400 | Workflow id not registered at dispatch time |
| PROMPT_BLOCKED | 400 | Content policy filter triggered |
| PROMPT_REJECTED | 400 | Content policy filter triggered (enhance operations) |
| CONTENT_POLICY_VIOLATION | 400 | Source image rejected by the image safety check |
| CONSENT_REQUIRED | 400 | /video img2video submitted without biometricConsent: true |
| PAYLOAD_TOO_LARGE | 413 | Image exceeds 3.5MB |
| SOURCE_IMAGE_UNREACHABLE | 422 | /video i2v image URL could not be fetched — a nocensor output URL expires about an hour after it is issued, so re-read it from GET /api/v1/jobs/{id} or send inline base64 |
| NOT_FOUND | 404 | Job does not exist or not owned by caller |
| SOURCE_NOT_FOUND | 404 | job_<uuid> source reference not owned by caller or has no output |
| PIPELINE_NOT_FOUND | 404 | Pipeline correlation_id has no jobs owned by caller |
| LORA_NOT_FOUND | 404 | LoRA id in `loras[]` not owned by caller |
| JOB_NOT_CANCELLABLE | 409 | DELETE on a job already completed, failed, or cancelled |
| JOB_FAILED | 200 | /jobs/{id}/events `failed` event data.code — the job itself failed, not the stream |
| IDEMPOTENCY_KEY_REUSED | 409 | Idempotency-Key reused with a different request body |
| IDEMPOTENCY_IN_PROGRESS | 409 | A request with this Idempotency-Key is still running — retry after Retry-After |
| LORA_NOT_READY | 409 | LoRA is soft-deleted, still training, failed, or missing output |
| LORA_NOT_OWNED | 403 | LoRA id in a pipeline stage not owned by caller |
| LORA_INCOMPATIBLE | 400 | LoRA base model doesn't match the target model/endpoint |
| TOO_MANY_LORAS | 400 | More loras[] than the model allows (max 2 for images, max 1 for video) |
| PIPELINE_TOO_MANY_STAGES | 400 | Pipeline exceeds the 5-stage max |
| PIPELINE_INVALID_STAGE_ORDER | 400 | Image stage placed after a video-producing stage |
| CHARACTER_LOCKED | 403 | Character has not been unlocked by this account |
| REGION_BLOCKED | 451 | Feature unavailable in the caller's region (e.g. AI undress in banned US states, or restricted countries) |
| WEBHOOK_URL_INVALID | 400 | Webhook URL failed validation |
| WEBHOOK_LIMIT_REACHED | 409 | Maximum webhooks per account reached |
| WEBHOOK_INACTIVE | 409 | Webhook is disabled — reactivate before test or deliveries |
| GPU_UNAVAILABLE | 503 | All GPU workers throttled — retry shortly |
| IDEMPOTENCY_UNAVAILABLE | 503 | Idempotency check temporarily unavailable — retry |
| TIMEOUT | 503 | Dispatch timed out while the service was busy — retry |
| AUDIO_UNAVAILABLE | 503 | /video with sound while sound generation is down — retry later or set audioMode to "none" |
| FEATURE_UNAVAILABLE | 503 | Endpoint or model restricted to admin keys (v1/undress, video model wan-21) — use the alternative named in the message |
| STORAGE_ERROR | 500 | Transient storage failure signing a source URL — retry |
| DB_ERROR | 500 | Transient database error — retry |
| INTERNAL_ERROR | 500 | Unexpected server error — retry |
Ready to build?
Create an account, get your API key, and start generating.
Get started free