SpicyAPI V1
One REST API for uncensored image, video and chat generation. No GPUs to rent, no queues to run — you send JSON, you get media back. Every request is billed per generation from your account balance, with no subscription.
The base URL for all endpoints is https://api.spicyapi.com.
curl https://api.spicyapi.com/v1/images/generations \
-H "Authorization: Bearer $SPICYAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "spicy-image-1",
"prompt": "a woman on a beach at golden hour, photorealistic"
}'Models
| MODEL | TYPE | PRICE | NOTES |
|---|---|---|---|
| spicy-pov-missionary-1 | Video (i2v) | from $0.175 / sec | Our POV missionary fine-tune |
| spicy-pov-doggystyle-1 | Video (i2v) | from $0.175 / sec | Our POV doggystyle fine-tune |
| spicy-pov-blowjob-1 | Video (i2v) | from $0.175 / sec | Our POV kneeling-oral fine-tune |
| spicy-image-1 | Image | $0.06 / image | Fast realistic text-to-image |
| spicy-image-1-pro | Image | $0.09 / image | Highest quality, best prompt adherence |
| spicy-image-edit-1 | Image edit | $0.15 / image | Edit with up to 3 reference images |
| spicy-motion-1 | Video (i2v) | from $0.175 / sec | NSFW-tuned image-to-video with audio |
| spicy-motion-2 | Video (i2v) | from $0.175 / sec | Latest gen, up to 1080p / 15s |
| spicy-video-1 | Video (t2v) | from $0.175 / sec | Text-to-video, no source image |
| spicy-motion-3 | Video (i2v / t2v) | from $0.10 / sec | Wan 3.0 class: up to 30s, 480p to 1080p, native audio, source image optional |
| spicy-motion-3-fast | Video (i2v / t2v) | from $0.14 / sec | Spicy Motion 3 on the accelerated backbone, same limits, faster turnaround |
| spicy-chat-1 | Chat | $0.58 / 1M tokens | Uncensored roleplay chat, OpenAI-compatible |
Call GET /v1/models for the live catalog including per-model size, duration and resolution limits.
Authentication
Authenticate with a bearer token. Create keys in the dashboard — a key is shown once at creation and stored only as a hash, so save it somewhere safe.
Authorization: Bearer sk-spicy-xxxxxxxxxxxxxxxxxxxx
Never ship a key in client-side code. Check your balance any time with GET /v1/account.
Image generation
POST /v1/images/generations — synchronous. Returns image URLs directly, typically in 10–30 seconds.
curl https://api.spicyapi.com/v1/images/generations \
-H "Authorization: Bearer $SPICYAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "spicy-image-1",
"prompt": "your prompt",
"negative_prompt": "blurry, low quality",
"size": "832*1216",
"n": 2
}'
{
"id": "sj_2f1c...",
"object": "image.generation",
"model": "spicy-image-1",
"data": [{ "url": "https://cdn.spicyapi.com/outputs/..." }],
"cost_usd": 0.05
}Every returned url is permanently hosted on our CDN and registered to your account — these URLs are what you pass later as inputs to image editing and video generation.
Treat the URL as your master copy and provenance token, not as end-user hosting: download the file once and serve it to your own users from your own storage or CDN. Sustained hotlinking of output URLs into consumer-facing apps falls outside fair use and may be rate-limited.
Serving a copy does not affect reuse: keep the original URL in your database, and it remains valid forever as an input to /v1/images/edits and /v1/videos/generations — your users view your copy, your backend passes our URL.
Image editing
POST /v1/images/edits — edit or recompose an existing image from a prompt. Pass up to three reference images. Each must be an output previously generated by your own account — use the url values returned by /v1/images/generations or /v1/images/edits. External image URLs are rejected with a 400 — see Input images for why, and for the integration pattern.
curl https://api.spicyapi.com/v1/images/edits \
-H "Authorization: Bearer $SPICYAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "spicy-image-edit-1",
"prompt": "change the outfit to a red dress",
"image_urls": ["https://cdn.spicyapi.com/outputs/…/sj_2f1c…-0.png"]
}'Input images: no uploads, by design
SpicyAPI does not accept uploaded or external images anywhere. The only images that can be edited or animated are images that were generated through the API by your own account. This is a safety and legal-compliance requirement, not a technical limitation: because every video frame and edit source traces back to a moderated, machine-generated origin, neither you nor we can be handed real-world photographs of real people — which keeps the API (and every product built on it) compliant with international law on non-consensual and abusive imagery.
Enforcement is server-side: each generated output URL is registered to the account that created it, and input URLs are checked against that registry before anything is charged. A URL that was not minted for your account — including another customer's output, a guessed CDN path, or a re-hosted copy — is rejected with a 400.
Integrating this into your product — the pattern is a gallery: your users first generate images, browse what they've made, then pick one to edit or animate.
1. When a generation completes, store the returned urlin your own database against the end-user who made it. Your API account's library is shared across your whole app, so this per-user mapping is yours to keep.
2. To build the gallery, use your stored URLs — or fetch the account-wide library from GET /v1/images, newest first:
curl "https://api.spicyapi.com/v1/images?limit=50" \
-H "Authorization: Bearer $SPICYAPI_KEY"
{
"object": "list",
"data": [
{ "url": "https://cdn.spicyapi.com/outputs/…/sj_2f1c…-0.png",
"source": "image", "created": 1755856800 }
]
}3. When the user picks an image, pass its exact URL straight into /v1/images/edits or /v1/videos/generations:
// e.g. an Express handler behind your own user auth
app.post("/animate", async (req, res) => {
// look the image up in YOUR db so users can only
// animate images they generated themselves
const image = await db.images.findOwn(req.user.id, req.body.imageId);
const task = await fetch("https://api.spicyapi.com/v1/videos/generations", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SPICYAPI_KEY}`,
"Content-Type": "application/json" },
body: JSON.stringify({
model: "spicy-motion-2",
prompt: req.body.prompt,
image_url: image.url, // exact URL from /v1/images/generations
resolution: "720P",
duration: 5,
}),
}).then(r => r.json());
res.json({ taskId: task.id });
});Pass the URL byte-for-byte as it was returned — adding query parameters or changing the encoding will fail the provenance check.
Video generation
POST /v1/videos/generations — asynchronous. Returns a task immediately; poll it or receive a webhook. Video generation typically takes 1–5 minutes.
image_url must be an image generated by your own account: first create the frame with /v1/images/generations (or /v1/images/edits), then pass the exact url it returned. Uploaded or external images are rejected with a 400 — see Input images.
curl https://api.spicyapi.com/v1/videos/generations \
-H "Authorization: Bearer $SPICYAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "spicy-motion-2",
"prompt": "she turns towards the camera and smiles",
"image_url": "https://cdn.spicyapi.com/outputs/…/sj_2f1c…-0.png",
"resolution": "720P",
"duration": 5
}'
{ "id": "sj_9a2b...", "status": "queued", "cost_usd": 0.125 }Poll with GET /v1/videos/tasks/{id}. Status moves through queued → processing → succeeded or failed. Failed generations are refunded automatically.
{
"id": "sj_9a2b...",
"status": "succeeded",
"output": { "video_url": "https://cdn.spicyapi.com/outputs/..." },
"cost_usd": 0.125
}As with images, download the finished video and serve it to your end users from your own infrastructure — the CDN URL is your master copy, not a streaming host for your traffic.
POV fine-tunes
These are our own models — fine-tuned in-house on the wan2.7 backbone for specific POV scenes, and the reason most people use this API. They take the same request shape as any other video model, so switching is a one-word change.
| MODEL | SCENE | MOTION |
|---|---|---|
| spicy-pov-missionary-1 | POV missionary | Steady thrust rhythm, locked overhead framing |
| spicy-pov-doggystyle-1 | POV doggystyle | Accelerating rear-entry motion, camera fixed behind |
| spicy-pov-blowjob-1 | POV blowjob (kneeling) | Building oral rhythm, camera looking down and still |
Actual outputs from each fine-tune — this is what the model produces with a single API call, no prompt engineering.
They are priced identically to the general video models — $0.175/sec at 720p — and are image-to-video, so pass a first frame that is already in the position you want animated — generated by your account via /v1/images/generations, like any other input image.
curl https://api.spicyapi.com/v1/videos/generations \
-H "Authorization: Bearer $SPICYAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "spicy-pov-missionary-1",
"prompt": "amateur bedroom, phone flash lighting",
"image_url": "https://cdn.spicyapi.com/outputs/…/sj_2f1c…-0.png",
"resolution": "720P",
"duration": 5
}'Each fine-tune has an activation token (returned as lora_trigger on GET /v1/models). You do not need to add it — we insert it for you when it is missing, so the tuned motion always engages. Include it yourself only if you want to control where in the prompt it sits.
Chat completions
POST /v1/chat/completions is OpenAI-compatible, including streaming. Point any OpenAI SDK at our base URL and change the model name.
from openai import OpenAI
client = OpenAI(
api_key="sk-spicy-...",
base_url="https://api.spicyapi.com/v1",
)
stream = client.chat.completions.create(
model="spicy-chat-1",
messages=[
{"role": "system", "content": "You are Emma, a flirty girlfriend."},
{"role": "user", "content": "hey, what are you up to?"},
],
stream=True,
)Webhooks
Set a webhook URL in your dashboard and we POST there when a video task reaches a terminal state — no polling required.
POST your-endpoint
X-SpicyAPI-Signature: <hex hmac-sha256 of the raw body>
{
"event": "task.succeeded",
"data": {
"id": "sj_9a2b...",
"status": "succeeded",
"output": { "video_url": "https://cdn.spicyapi.com/outputs/..." }
}
}Verify the signature against your signing secret before trusting the payload. We retry on the next poll cycle if your endpoint is unreachable.
Errors & limits
Errors return a standard shape with an HTTP status: 401 bad key, 402 insufficient balance, 400 invalid parameters, 422 prompt blocked by moderation, 429 model at capacity.
{ "error": { "message": "...", "type": "insufficient_balance" } }All prompts pass through content moderation before generation. Blocked prompts are never charged. See our acceptable use policy.