{"openapi":"3.1.0","info":{"title":"Spicy API","version":"1.0.0","description":"Spicy API (also written SpicyAPI) is one REST API for uncensored image, image-editing, video, video-editing, speech, live voice call, transcription, embedding and chat generation, sold per generation from a prepaid US-dollar balance: no subscription, no GPUs to run. It is OpenAI-compatible (point the OpenAI SDK at https://api.spicyapi.com/v1 with a Spicy API key) and includes ready-made sex actions for video and role-play companion models. It has been at spicyapi.com since June 2025, is operated by Wayfinity Ltd, and is not affiliated with spicyapi.ai, an unrelated model aggregator. Images from $0.02, video from $0.05 per second. OPENAI-COMPATIBLE: point the OpenAI SDK at https://api.spicyapi.com/v1 with a Spicy API key; client.images.generate maps onto POST /v1/images/generations and client.chat.completions.create onto POST /v1/chat/completions (streaming included). client.embeddings.create maps onto POST /v1/embeddings and client.audio.transcriptions.create onto POST /v1/audio/transcriptions. Image edits take JSON with image_url (not the SDK's multipart upload) and video (generations, edits, motion transfer) is a plain HTTP task you poll. Every response carries cost_usd, the exact amount debited. Prompts are screened before any charge (422, never billed); failed generations are refunded. Sandbox keys (created with sandbox: true) answer every endpoint from fixtures flagged sandbox: true and bill nothing. Machine-readable overview: https://www.spicyapi.com/llms.txt. MCP server for agents: https://www.spicyapi.com/api/mcp.","contact":{"name":"Spicy API support","email":"contact@spicyapi.com","url":"https://www.spicyapi.com/docs"},"termsOfService":"https://www.spicyapi.com/terms-of-service"},"servers":[{"url":"https://api.spicyapi.com","description":"Public API host (also reachable at https://www.spicyapi.com/api)."}],"x-openai-compatible":{"baseUrl":"https://api.spicyapi.com/v1","compatible":["/v1/chat/completions","/v1/images/generations","/v1/models","/v1/embeddings","/v1/audio/transcriptions"],"notes":"Image sizes use width*height (1024*1024). Speech returns JSON with a url unless stream is true. Live calls follow the OpenAI Realtime event shape over wss://api.spicyapi.com/v1/realtime after POST /v1/realtime/sessions. Edits and video are Spicy API specific."},"x-mcp":{"endpoint":"https://www.spicyapi.com/api/mcp","transport":"streamable-http","auth":"Authorization: Bearer sk-spicy-...","docs":"https://www.spicyapi.com/docs","llms":"https://www.spicyapi.com/llms.txt","skills":"https://www.spicyapi.com/skills/index.json"},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk-spicy-...","description":"Per-account API key from https://www.spicyapi.com/dashboard/api-keys (shown once, stored hashed). Sandbox keys bill nothing."}}},"tags":[{"name":"Core"},{"name":"Images"},{"name":"Moderation"},{"name":"Characters"},{"name":"Audio"},{"name":"Video"},{"name":"Chat"},{"name":"Embeddings"},{"name":"Live calls"},{"name":"Agent tools","description":"REST mirror of the MCP server at https://www.spicyapi.com/api/mcp: same key, same handlers, same guardrails."},{"name":"default","description":"Always available with a valid key."},{"name":"guarded","description":"Spending and credential tools: need approval_token unless the account turned approvals off."}],"paths":{"/v1/models":{"get":{"operationId":"listModels","summary":"List Models","description":"List every model available to your account. Returns the live catalogue: ids, pricing, limits and what each model accepts. Video models also carry `inputs`: which of first frame, last frame, reference images, clip and audio the model takes, the audio mode (driving or reference) and `combinations`, the valid input combinations in plain sentences. Build video requests from it; the same rules are listed under Input combinations on `POST /v1/videos/generations`. Kind-specific blocks: `speech` on speech models (voices, tags, streaming), `video_edit` on `POST /v1/videos/edits` models, `realtime` on live-call models, plus the flags `silent` (the video never has sound) and `bills_input_video_seconds` (an input `video_url` clip is billed per second too).","tags":["Core"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"The models your key can call.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"The model id you pass as `model`."},"kind":{"type":"string","description":"One of `image`, `image-edit`, `video`, `video-edit`, `chat`, `speech`, `realtime`, `transcription`, `embedding`."},"label":{"type":"string","description":"Human-readable name."},"description":{"type":"string","description":"What the model is good for."},"featured":{"type":"boolean","description":"Reserved for highlighted models; false on every current model."},"requires_image":{"type":"boolean","description":"True when the model needs an `image_url` input."},"accepts_image":{"type":"boolean","description":"True when `image_url` is allowed. False on pure text-to-video models, which reject one."},"pricing":{"type":"object","description":"Unit and USD rate. `unit: \"image\"` with `usd`; `unit: \"second\"` with `usd_by_resolution` (video and video edits, keyed by resolution, or by `standard` / `pro` quality on motion transfer); `unit: \"10K characters\"` with `usd_per_10k_chars` (speech); `unit: \"minute\"` with `usd_per_minute` (transcription, billed by the second); `unit: \"1M tokens\"` with `usd_input` and `usd_output` (chat, embeddings), plus `usd_image` on image embeddings and `usd_audio_input` / `usd_audio_output` on live calls."},"accepts_reference":{"type":"boolean","description":"True when the video model takes `character` and `reference_image_urls` (`spicy-character-video-1`, `spicy-motion-3`, `spicy-motion-3-fast`, `spicy-cinema-1-character`)."},"limits":{"type":"object","description":"What the model accepts: `sizes` and `maxOutputs` on image models (6 on Spicy Image 1, 12 on Spicy Image 2 in set mode); `resolutions`, `minDuration`, `maxDuration`, `aspectRatios` (text-to-video shapes), `maxPromptChars` (prompt cap in characters, enforced with a 400) and `maxInputVideoSeconds` (longest `video_url` clip) on video and video-edit models; `maxPromptChars` (the `input` cap: 600 on Spicy Voice 1, 5,000 on Spicy Voice 2 and 3) on speech models."},"inputs":{"type":"object","properties":{"first_frame":{"type":"string","description":"`required`, `optional` (text-to-video without it) or `none` for `image_url`."},"last_frame":{"type":"boolean","description":"Whether `last_frame_url` is accepted (always together with a first frame or a continuation clip)."},"reference_images":{"type":"integer","description":"Maximum `reference_image_urls` (a `character` counts as one); 0 when not supported."},"reference_video":{"type":"boolean","description":"Whether `video_url` is a reference clip to extend or edit (never with a first frame)."},"continuation_clip":{"type":"boolean","description":"Whether `video_url` is a clip to continue, taking the place of the first frame."},"audio":{"type":"object","description":"`mode`: `driving` (the clip is the soundtrack and the mouth follows it), `reference` (the model generates sound and uses the clip for voice, tone and beat) or `none`; `max_clips`; `max_total_seconds` where capped; `with_first_frame`, whether audio may be sent alongside `image_url`."},"aspect_ratio":{"type":"boolean","description":"Whether `aspect_ratio` is accepted (text-to-video only)."},"smart_duration":{"type":"boolean","description":"Whether `duration: -1` is accepted."},"combinations":{"type":"array","items":{"type":"string"},"description":"The valid input combinations, one sentence each. Quoted back in any 400 for an invalid mix."}},"required":["first_frame","last_frame","reference_images","reference_video","continuation_clip","audio","aspect_ratio","smart_duration","combinations"],"description":"Video models only: what the model takes and which inputs may travel together. The route enforces exactly this."},"speech":{"type":"object","properties":{"voices":{"type":"array","items":{"type":"string"},"description":"Preset voice names; empty on `spicy-voice-1-custom`, which speaks your own voices."},"custom_voices":{"type":"boolean","description":"True when the model speaks your designed and cloned voices (`vc_...`)."},"instructions":{"type":"boolean","description":"Whether `instructions` are accepted."},"languages":{"type":"array","items":{"type":"string"},"description":"Accepted `language` values, `Auto` first."},"output_format":{"type":"string","description":"The default format, `wav`. Spicy Voice 2 and 3 models also return `mp3`."},"streaming":{"type":"boolean","description":"Whether `stream: true` is accepted (Spicy Voice 2 and 3 models)."},"stream_formats":{"type":"array","items":{"type":"string"},"description":"Streaming models only: `pcm`, `wav`, `mp3`."},"tags":{"type":"object","description":"Spicy Voice 2 and 3 models: `{ control, sounds }`, the inline tags the model performs inside `input` (on Spicy Voice 3 any short direction in brackets works too)."},"voice_creation_usd":{"type":"object","description":"`spicy-voice-1-custom` only: `{ designed, cloned }`, the one-off fee per voice."}},"required":["voices","custom_voices","instructions","languages","output_format","streaming"],"description":"Speech models only: voices, options and output."},"video_edit":{"type":"object","properties":{"mode":{"type":"string","description":"`instruction` (edit a clip from a prompt and reference images) or `motion` (the person in `image_url` performs the clip's moves)."},"min_clip_seconds":{"type":"integer","description":"Shortest input clip accepted."},"max_clip_seconds":{"type":"integer","description":"Longest input clip accepted."},"max_output_seconds":{"type":"integer","description":"The output is the clip's length, capped here."},"reference_images":{"type":"integer","description":"Maximum `reference_image_urls`; 0 on motion transfer."},"aspect_ratio":{"type":"boolean","description":"Whether `aspect_ratio` can reshape the output."},"keep_audio":{"type":"boolean","description":"Whether `keep_audio: true` keeps the clip's soundtrack."},"bills_input_seconds":{"type":"boolean","description":"True when the input clip's seconds are billed as well as the output's."},"qualities":{"type":"array","items":{"type":"string"},"description":"Motion transfer only: `standard`, `pro`, priced separately."}},"required":["mode","min_clip_seconds","max_clip_seconds","max_output_seconds","reference_images","aspect_ratio","keep_audio","bills_input_seconds"],"description":"`POST /v1/videos/edits` models only: how the model uses its clip."},"bills_input_video_seconds":{"type":"boolean","description":"Present and true on video models that also bill the seconds of an input `video_url` clip at the same per-second rate (`spicy-motion-3`, `spicy-motion-3-fast`)."},"actions":{"type":"boolean","description":"Present and true on video models that take `action` on `POST /v1/videos/generations` (`spicy-motion-3`, `spicy-motion-3-fast`); the library is `GET /v1/videos/actions`."},"image_actions":{"type":"boolean","description":"Present and true on image models that only run with `action` on `POST /v1/images/generations` (`spicy-image-action-1`); the library is `GET /v1/images/actions`."},"silent":{"type":"boolean","description":"Present and true on video models that always render without sound (`spicy-motion-draft-1`)."},"realtime":{"type":"object","properties":{"voices":{"type":"array","items":{"type":"string"},"description":"Voices accepted as `voice` on `POST /v1/realtime/sessions`."},"default_voice":{"type":"string","description":"Used when `voice` is omitted."},"max_seconds":{"type":"integer","description":"Longest call on one session."},"input_audio":{"type":"string","description":"What to send: PCM16 mono 16 kHz, base64."},"output_audio":{"type":"string","description":"What comes back: PCM16 mono 24 kHz, base64."}},"required":["voices","default_voice","max_seconds","input_audio","output_audio"],"description":"Live-call models only."},"examples":{"type":"array","items":{"type":"string"},"description":"Example output URLs for this model. For sandbox keys and accounts still owing their regional age check, stills come blurred and clips are left out, and the list carries `restricted` and `note`."}},"required":["id","kind","label","description","featured","requires_image","accepts_image","pricing","accepts_reference"]},"description":"Every model available to you."},"restricted":{"type":"string","description":"Only on the locked shape: `explicit_media`."},"note":{"type":"string","description":"Only on the locked shape: why, and how to get the full library."}},"required":["data"]},"example":{"data":[{"id":"spicy-motion-3","kind":"video","label":"Spicy Motion 3","featured":false,"requires_image":false,"accepts_image":true,"accepts_reference":true,"pricing":{"unit":"second","usd_by_resolution":{"480P":0.1,"720P":0.2,"1080P":0.4}},"limits":{"resolutions":["480P","720P","1080P"],"minDuration":2,"maxDuration":30,"aspectRatios":["16:9","9:16","1:1","4:3","3:4","21:9"],"maxPromptChars":20000},"inputs":{"first_frame":"optional","last_frame":true,"reference_images":10,"reference_video":true,"continuation_clip":false,"audio":{"mode":"reference","max_clips":5,"max_total_seconds":15,"with_first_frame":false},"aspect_ratio":true,"smart_duration":true,"combinations":["Text prompt only, with aspect_ratio; dialogue written in the prompt is spoken natively","First frame (image_url), optionally a last frame (last_frame_url); no references or audio in the same request","Reference images (up to 10, or a character) plus a text prompt: same person, framing chosen by the model","Reference images, a reference video (video_url, 15s max) and reference audio (up to 5 clips, 15s total) in any mix, with a text prompt; audio guides voice, tone and beat while the words come from the prompt"]},"bills_input_video_seconds":true,"actions":true},{"id":"spicy-image-1","kind":"image","label":"Spicy Image 1","featured":false,"requires_image":false,"accepts_image":false,"accepts_reference":false,"pricing":{"unit":"image","usd":0.06},"limits":{"sizes":["1024*1024","832*1216","1216*832","1280*1280","1440*1440","1024*1536","1536*1024","1080*1920","1920*1080","1152*2048","2048*1152"],"maxOutputs":6}},{"id":"spicy-voice-1-custom","kind":"speech","label":"Spicy Voice 1 Custom","featured":false,"requires_image":false,"accepts_image":false,"accepts_reference":false,"pricing":{"unit":"10K characters","usd_per_10k_chars":0.3},"limits":{"maxPromptChars":600},"speech":{"voices":[],"custom_voices":true,"instructions":false,"languages":["Auto","English","Chinese","German","Italian","Portuguese","Spanish","Japanese","Korean","French","Russian"],"output_format":"wav","streaming":false,"voice_creation_usd":{"designed":0.4,"cloned":0.02}}}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/account":{"get":{"operationId":"getAccount","summary":"Get Account","description":"Read your balance and account status.","tags":["Core"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"The account this key belongs to."},"balance_usd":{"type":"number","description":"Remaining balance in US dollars."},"limits":{"type":"object","description":"This account's limits: `requests_per_minute` (600 to start, raised automatically up to 6,000 for accounts that keep reaching it, or on request), `declined_prompts_per_minute`, `screened_requests_per_end_user_per_minute` and `video_jobs_in_progress` (20 by default). Higher limits are available on request: contact@spicyapi.com."},"character_import":{"type":"object","description":"Only on accounts that had a character import window: `status` (`open`, `expired`, `exhausted`, `revoked` or `not_enabled`), `expires_at`, `used`, `limit` and `remaining`. See `POST /v1/characters/import`."}},"required":["email","balance_usd","limits"]},"example":{"email":"you@example.com","balance_usd":42.75,"sandbox":false,"limits":{"requests_per_minute":600,"declined_prompts_per_minute":60,"screened_requests_per_end_user_per_minute":30,"video_jobs_in_progress":20}}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/images/generations":{"post":{"operationId":"createImage","summary":"Create Image","description":"Generate images from a text prompt. Billed per output image. Prompts are screened before anything is charged, every image is screened after generation (withheld ones are refunded), and every request is steered to depict adults only. Models: `spicy-image-1` ($0.06), `spicy-image-1-pro` ($0.09) and `spicy-image-photo-1` ($0.08, candid natural-light photo look) render 1K stills, one to six per call. Reference images: `spicy-image-reference-1` ($0.12), open to every account, takes `reference_image`, a picture of your own (a public https URL or a data URL, JPEG, PNG or WebP, up to 10 MB by URL), instead of a prompt. The picture is described in detail (medium, camera height and lens, where each person sits in the frame, pose, clothing, setting, light direction and colours; identifying facial features left out), its exposure, contrast, white balance and main colours are measured, and a brand new image is drawn from that description alone, in the picture's shape and style, then its overall colour balance is matched to the picture's, so it has the picture's look with new people; the picture itself is never edited or shown to the image model. The picture is screened first (anyone who may be under 18, known people); a refused picture costs nothing. The upload itself is never stored and cannot be used as an input elsewhere; the result is a normal output you can edit and animate. The response adds `description`, a written prompt of the picture (with the measured colours) you can edit and send as a `prompt` to any text-to-image model. One image per call, about 30 to 60 seconds. Image actions: `spicy-image-action-1` ($0.15) takes `action` (one of 68 stills from `GET /v1/images/actions`) and `image_url` of the woman, one of your own generated images, and returns her face, hair and skin tone in that scene: the act, pose, framing, room and her body come from the still. No `prompt`. One to four per call; a swap takes a minute or two.","tags":["Images"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Request id, useful for support."},"object":{"type":"string","description":"Always `image.generation`."},"model":{"type":"string","description":"The model that produced the images."},"created":{"type":"integer","description":"Unix timestamp."},"data":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Durable CDN URL. Reusable as an input to edits and video."}},"required":["url"]},"description":"One entry per generated image."},"withheld":{"type":"integer","description":"How many outputs were withheld by the post-generation image screen. Those are deleted and refunded, so `cost_usd` covers only the images in `data`. Omitted when zero."},"character":{"type":"string","description":"The character id used, when one was passed."},"character_views":{"type":"array","items":{"type":"string"},"description":"The character's body views sent with this request, when any were."},"description":{"type":"string","description":"With `reference_image`: the picture written as a prompt (up to 1900 characters). Edit it and send it as `prompt` to `spicy-image-photo-1` for more takes on the same scene."},"cost_usd":{"type":"number","description":"What this request cost, in US dollars, already debited from your balance."}},"required":["id","object","model","created","data","cost_usd"]},"example":{"id":"req_8f2a91c4","object":"image.generation","model":"spicy-image-1","created":1755950400,"data":[{"url":"https://cdn.spicyapi.com/outputs/a1b2/req_8f2a91c4-0.png"},{"url":"https://cdn.spicyapi.com/outputs/a1b2/req_8f2a91c4-1.png"}],"cost_usd":0.12}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"description":"Outputs are persisted to the SpicyAPI CDN and returned as durable URLs. Those URLs are the only images accepted as inputs to `/v1/images/edits`, `/v1/videos/generations`, `/v1/videos/edits` and `/v1/embeddings`.","content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"An image model id from `GET /v1/models`: `spicy-image-1`, `spicy-image-1-pro`, `spicy-image-photo-1`, `spicy-image-action-1` for image actions, or `spicy-image-reference-1` with `reference_image`."},"prompt":{"type":"string","description":"What to generate. Up to 4000 characters. Not accepted with `action` or `reference_image`."},"reference_image":{"type":"string","description":"`spicy-image-reference-1` only, and required there: the picture to recreate, as a public https URL or a data URL (`data:image/jpeg;base64,...`). JPEG, PNG or WebP, 256 to 8192 pixels a side; request bodies are capped at 4.5 MB, so send larger files by URL (up to 10 MB). The output keeps the picture's shape. `prompt`, `action`, `character`, `size`, `style`, `enhance_prompt` and `n` above 1 are a 400 with it; `negative_prompt` and `seed` are accepted. Governed by section 2.3 of the Terms of Service and section 3.5 of the Acceptable Use Policy: until the account has accepted terms version 2026.10.06 or later in the dashboard, it is a 403 `reference_terms_required`."},"action":{"type":"string","description":"`spicy-image-action-1` only, and required there: the id of a ready-made explicit still from `GET /v1/images/actions` (e.g. `pov-missionary`, `pov-blowjob-deepthroat`). The still supplies the act, pose, framing, room, output size and her body; `image_url` (or a `character`) supplies her face, hair and skin tone. `style` picks the still's `anime`, `3d` or `cartoon` version (see `styles` in the list) so a drawn woman lands in a drawn scene; omit it for the photo, or pass `studio` for the photo with a warm glamour colour grade. `prompt`, `negative_prompt`, `size` and `enhance_prompt` are a 400 with it."},"image_url":{"type":"string","description":"With `action`: the woman, a URL returned by `/v1/images/generations` or `/v1/images/edits` on your account (`GET /v1/images` lists them). Uploads and other URLs are a 400 before any charge."},"negative_prompt":{"type":"string","description":"What to avoid. SpicyAPI's safety terms are always appended."},"enhance_prompt":{"type":"boolean","description":"Let the model expand the prompt with lighting, camera and detail cues before generating. Defaults to false: the prompt is sent exactly as written."},"enhance_mode":{"type":"string","description":"How the prompt extender works when `enhance_prompt` is true: `direct` (default) rewrites the prompt once; `agent` plans the shot first, then writes. Rejected with a 400 without `enhance_prompt: true`."},"size":{"type":"string","description":"`width*height`, one of the model's `limits.sizes`. `1024*1024`, `832*1216`, `1216*832`, `1280*1280`, `1440*1440`, `1024*1536`, `1536*1024`, `1080*1920`, `1920*1080`, `1152*2048`, `2048*1152`, all in the same price tier. Defaults to `1024*1024`. Any other value is a 400."},"n":{"type":"integer","description":"How many images to return, 1 to 6. Billed per delivered image. Defaults to 1."},"seed":{"type":"integer","description":"Reuse a seed to reproduce a result. Random when omitted."},"character":{"type":"string","description":"A saved character id (`chr_...`) from `POST /v1/characters`: the same person is rendered in the new scene. The request runs as an edit of the sheet and is billed at the `spicy-image-edit-1` rate."},"views":{"type":"array","items":{"type":"string"},"description":"With `character`: the body view ids to send (`cv_...` from the character's `views`), up to 2, or `[]` for none. Omitted: the views that fit the prompt are picked for you (a back view for a shot from behind, a close-up for a close-up), listed in the response as `character_views`. No extra charge."},"style":{"type":"string","description":"An art style id from `GET /v1/styles`: `photorealistic`, `studio`, `anime`, `3d` or `cartoon`. Adds a tuned style prompt and matching negatives; same model and price. Omit it for the model's natural look. Unknown ids are a 400. With `action`, `photorealistic` (or no style) is the photo still, `studio` gives the finished picture a warm glamour colour grade, and `anime`, `3d` or `cartoon` pick the still's drawn version instead (same price); pass the style that matches her image."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model","prompt"]}}}}}},"/v1/images/edits":{"post":{"operationId":"editImage","summary":"Edit Image","description":"Edit an image you generated earlier, guided by a prompt. One to six outputs per call. Every `image_url` must be an image your own account generated. Uploads and external URLs are rejected before you are charged. Billed per output image. Outputs are screened after generation; withheld ones are deleted and refunded. Upscaling: `model: \"spicy-upscale-1\"` with one `image_url` and `scale` 2 (default, $0.02) or 4 (images up to about 1 megapixel, $0.04). No prompt; detail is restored rather than enlarged, the picture and person stay the same. Returns one image with `width` and `height`, screened and labelled like any output.","tags":["Images"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Request id."},"object":{"type":"string","description":"Always `image.edit`."},"data":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Durable CDN URL."}},"required":["url"]},"description":"One entry per edited image."},"withheld":{"type":"integer","description":"How many outputs were withheld by the post-generation image screen. Those are deleted and refunded, so `cost_usd` covers only the images in `data`. Omitted when zero."},"character":{"type":"string","description":"The character id used, when one was passed."},"character_views":{"type":"array","items":{"type":"string"},"description":"The character's body views sent with this request, when any were."},"cost_usd":{"type":"number","description":"What this request cost, in US dollars, already debited from your balance."}},"required":["id","object","data","cost_usd"]},"example":{"id":"req_31c7de05","object":"image.edit","model":"spicy-image-edit-1","created":1755950400,"data":[{"url":"https://cdn.spicyapi.com/outputs/a1b2/req_31c7de05-0.png"},{"url":"https://cdn.spicyapi.com/outputs/a1b2/req_31c7de05-1.png"}],"cost_usd":0.3}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"An image-edit model id, e.g. `spicy-image-edit-1`."},"prompt":{"type":"string","description":"The change to make. Up to 4000 characters."},"image_url":{"type":"string","description":"A single source image. Use this or `image_urls`."},"image_urls":{"type":"array","items":{"type":"string"},"description":"Up to 3 source images, all owned by your account. With `character` the sheet counts as one, so up to 2 of yours."},"negative_prompt":{"type":"string","description":"What to avoid."},"enhance_prompt":{"type":"boolean","description":"Let the model expand the prompt with lighting, camera and detail cues before generating. Defaults to false: the prompt is sent exactly as written."},"size":{"type":"string","description":"Output `width*height`, one of the model's `limits.sizes` (`1024*1024`, `832*1216`, `1216*832`, `1280*1280`, `1440*1440`, `1024*1536`, `1536*1024`, `1080*1920`, `1920*1080`, `1152*2048`, `2048*1152`). When omitted the model chooses the output size."},"n":{"type":"integer","description":"How many variations to return, 1 to 6 (the model's `limits.maxOutputs`). Billed per image. Defaults to 1."},"seed":{"type":"integer","description":"Reproducibility seed."},"character":{"type":"string","description":"A saved character id (`chr_...`). The character's reference sheet goes in as Image 1 and your `image_urls` follow as Image 2 and 3; `image_urls` becomes optional."},"views":{"type":"array","items":{"type":"string"},"description":"With `character`: the body view ids to send (`cv_...` from the character's `views`), up to the free image slots (3 images in all, the sheet counts as one), or `[]` for none. Omitted: the views that fit the prompt are picked for you (a back view for a shot from behind, a close-up for a close-up), listed in the response as `character_views`. No extra charge."},"mask_url":{"type":"string","description":"Inpainting: a black and white mask (an https URL or a PNG or JPEG data URL), white where the image should change and black where it must stay. Works with exactly one `image_url`, no `character` and no `size`. The prompt describes the change; the result is blended back so everything under black is the original, pixel for pixel, with a soft edge. Same price as an edit."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model","prompt"]}}}}}},"/v1/images":{"get":{"operationId":"listImages","summary":"List Images","description":"List the images your account has generated. These are exactly the URLs accepted as inputs elsewhere in the API. Use this to look up what you can edit or animate.","tags":["Images"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","description":"Durable CDN URL."},"source":{"type":"string","description":"`image` or `image-edit`."},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The prompt that produced it (null for images generated before September 2026)."},"tags":{"type":"array","items":{"type":"string"},"description":"Auto-assigned content tags, usable with the `tag` filter."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 timestamp."}},"required":["url","source"]},"description":"Your generated images, newest first (or best match first when `q` is set)."},"has_more":{"type":"boolean","description":"True when the page came back full: pass the last `url` as `after`."}},"required":["data","has_more"]},"example":{"data":[{"url":"https://cdn.spicyapi.com/outputs/a1b2/req_8f2a91c4-0.png","source":"image","prompt":"blonde woman sunbathing on a private beach, golden hour","tags":["solo","outdoor","realistic"],"created_at":"2026-08-23T10:14:02.000Z"}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"limit","in":"query","required":false,"description":"How many to return, newest first, 1 to 100. Defaults to 50.","schema":{"type":"integer","description":"How many to return, newest first, 1 to 100. Defaults to 50."}},{"name":"after","in":"query","required":false,"description":"The `url` of the last image on the previous page, to get the next page. Not with `q` or `tag`.","schema":{"type":"string","description":"The `url` of the last image on the previous page, to get the next page. Not with `q` or `tag`."}},{"name":"q","in":"query","required":false,"description":"Plain-language search over your library, e.g. `blonde on a beach at sunset`. Ranks your most recent 250 images by how well their prompts match; returns nothing when no image plausibly fits.","schema":{"type":"string","description":"Plain-language search over your library, e.g. `blonde on a beach at sunset`. Ranks your most recent 250 images by how well their prompts match; returns nothing when no image plausibly fits."}},{"name":"tag","in":"query","required":false,"description":"Filter by an auto-assigned tag: `solo`, `couple`, `group`, `pov`, `outdoor`, `indoor`, `lingerie`, `nude`, `explicit`, `anime`, `realistic`, `closeup`.","schema":{"type":"string","description":"Filter by an auto-assigned tag: `solo`, `couple`, `group`, `pov`, `outdoor`, `indoor`, `lingerie`, `nude`, `explicit`, `anime`, `realistic`, `closeup`."}}]}},"/v1/styles":{"get":{"operationId":"listStyles","summary":"List Styles","description":"The art styles for `style` on image and video generation. No key needed. 5 styles: `photorealistic`, `studio`, `anime`, `3d`, `cartoon`. Pass the `id` as `style` on `POST /v1/images/generations` or `POST /v1/videos/generations`. A style only changes the prompt text: same models, same prices. Omitting `style` keeps each model's natural look. On video a style tells the model the whole clip's art style, matched to the first frame or reference image. With `action` it keeps an anime, 3D or cartoon woman in her style through the act instead of drifting toward realistic.","tags":["Images"],"security":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `style`."},"object":{"type":"string","description":"Always `style`."},"label":{"type":"string","description":"Display name."},"description":{"type":"string","description":"The look, in a few words."},"preview_url":{"type":"string","description":"A 480px 3:4 WebP of the same woman in this style."}},"required":["id","object","label","description","preview_url"]},"description":"The styles."}},"required":["data"]},"example":{"object":"list","data":[{"object":"style","id":"photorealistic","label":"Photorealistic","description":"Camera-real photo look","preview_url":"https://cdn.spicyapi.com/styles/photorealistic-e7d45042.webp"},{"object":"style","id":"anime","label":"Anime","description":"Modern detailed TV anime","preview_url":"https://cdn.spicyapi.com/styles/anime-9ee14f87.webp"}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/moderations":{"post":{"operationId":"moderations","summary":"Check a Prompt","description":"Run a prompt or conversation through the same screen the generation endpoints use, without generating. Use it to check your end users' prompts before you spend credits, or to power a live indicator in your own UI. Blocked prompts are never charged at generation time either; this endpoint exists so you can tell users *why* before they hit submit. Flat price of $0.001 per call. Free when called from the SpicyAPI playground. The screen runs three layers: a keyword blocklist, contextual pattern rules, and a semantic classifier that returns per-category probabilities. `outcome` is `allow`, `review` (uncertain; would be declined at generation with a request to reword) or `block`.","tags":["Moderation"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"flagged":{"type":"boolean","description":"`true` when `outcome` is not `allow`."},"outcome":{"type":"string","description":"`allow`, `review` or `block`."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The category driving the outcome: `minor`, `real_person`, `non_consent`, `bestiality`, `gore`, `injection`, `keyword`, or a contextual rule code."},"categories":{"type":"object","description":"Per-category probabilities from the semantic layer (0 to 1). Empty when a deterministic layer decided."},"layer":{"type":"string","description":"`keyword`, `contextual` or `jev`."},"cost_usd":{"type":"number","description":"What this call cost."}},"required":["flagged","outcome","category","categories","layer","cost_usd"]},"example":{"object":"moderation","flagged":false,"outcome":"allow","category":null,"categories":{"minor":0.03,"real_person":0.01,"non_consent":0.02,"bestiality":0,"gore":0,"injection":0.01},"layer":"jev","cost_usd":0.001}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"description":"Provide exactly one of `input` or `messages`.","content":{"application/json":{"schema":{"type":"object","properties":{"input":{"type":"string","description":"A generation prompt (optionally with the negative prompt appended). Up to 8000 characters."},"messages":{"type":"array","items":{"type":"object"},"description":"An OpenAI-style message array (system, user, assistant). The whole conversation is screened, including the system prompt."}},"required":[]}}}}}},"/v1/end-users/{user}":{"delete":{"operationId":"eraseEndUser","summary":"Erase an End User","description":"Erase what was generated for one of your users, for their privacy requests. Pass the same `user` id you sent on their requests (URL-encoded). Erased: the request records sent with that id (prompt, parameters, inputs and output links removed; the charge, model and date stay for your invoices) and the images, clips and audio those requests produced, from the CDN and from your library. Kept: content screening records (declines, strikes), which abuse prevention and the law require us to keep. If the end user has any decline or strike on record, nothing is erased and the response is `held_for_review`: email contact@spicyapi.com with the id and we will review it. Requests sent without `user` cannot be tied to a person, so send it on every request if your users can ask for erasure.","tags":["Core"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"`erased` or `held_for_review`."},"requests":{"type":"integer","description":"Request records erased."},"files":{"type":"integer","description":"Generated files deleted."},"reason":{"type":"string","description":"With `held_for_review`: why, and what to do."}},"required":["status"]},"example":{"object":"end_user.erasure","user":"user_8f3a2c","status":"erased","requests":42,"files":37}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/moderations/appeals":{"post":{"operationId":"moderationAppeals","summary":"Report a Wrong Decline","description":"Tell us a declined request should have been allowed. Every `422` from content screening carries `error.moderation_id`. Send it here when the decline looks wrong, for example when your end user disputes it. We review every report; a wrong decline gets its rule fixed, so the same request and similar ones pass next time. Free. One report per decline (sending it again returns the same report). Up to 30 reports a minute per account.","tags":["Moderation"],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Report received","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The moderation_id reported."},"status":{"type":"string","description":"`open` until reviewed."}},"required":["id","status"]},"example":{"object":"moderation.appeal","id":"Xq3f9KcTb2LmNp8RsV4w","status":"open"}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"moderation_id":{"type":"string","description":"`error.moderation_id` from the `422` response."},"reason":{"type":"string","description":"Optional: what was intended, up to 1000 characters."}},"required":["moderation_id"]}}}}}},"/v1/characters":{"post":{"operationId":"createCharacter","summary":"Create Character","description":"Save a reusable identity from your own generated images, to get the same person again across images, edits and video. A character is built only from images your account generated (the same provenance rule as every input), so a real photograph can never become one. Creation renders one neutral reference sheet and a short text descriptor; both are stored as ordinary assets in your library. Billed as one `spicy-image-edit-1` image. There is no limit on how many characters an account keeps. Pass the returned id as `character` to `/v1/images/generations`, `/v1/images/edits` or `/v1/videos/generations`. The sheet shows the face and upper body. To keep the rest of the body the same from other angles (from behind, a pose, a close-up), add body views: your own images of the person, each labelled with what it shows, with `POST /v1/characters/{id}/views`. Requests with the character then send the views that fit the prompt. Moving your own platform's existing AI-generated characters to SpicyAPI? Registered businesses can apply for `POST /v1/characters/import` in the dashboard under Settings, Character import: https://www.spicyapi.com/dashboard/import.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Character","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Character id, `chr_...`."},"name":{"type":"string","description":"The label you gave."},"sheet_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rendered reference sheet (also listed in `GET /v1/images` once the character is active). `null` while the character is `under_review`."},"source_urls":{"type":"array","items":{"type":"string"},"description":"The images it was built from. Empty while the character is `under_review`."},"description":{"type":"string","description":"Text descriptor of the person, appended to prompts that use the character."},"imported":{"type":"boolean","description":"`true` for a character brought in with `POST /v1/characters/import`."},"views":{"type":"array","items":{"type":"object"},"description":"Body views (`id`, `label`, `url`, and for a cropped view `source_url` and `crop`): your own images of this person from other angles, added with `POST /v1/characters/{id}/views`. Empty while `under_review`."},"status":{"type":"string","description":"`active`, or `under_review` for an import a person has to approve first. Using an `under_review` character returns 409."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."}},"required":["id","name","sheet_url","source_urls","description","imported","views","status"]},"example":{"id":"chr_9f2a1c3b7d4e5a60","object":"character","name":"Mara","sheet_url":"https://cdn.spicyapi.com/outputs/a1b2/chr_9f2a1c3b7d4e5a60.png","source_urls":["https://cdn.spicyapi.com/outputs/a1b2/sj_2f1c…-0.png"],"description":"Adult woman in her late 20s, oval face, deep-set pale blue eyes, straight narrow nose, full lips, long straight ash-blonde hair parted in the centre, fair skin, slim build.","imported":false,"status":"active","created":1758553200}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A label, up to 60 characters."},"image_urls":{"type":"array","items":{"type":"string"},"description":"1 to 3 generated images of the same person, all from your account (`GET /v1/images`). More angles give a steadier identity."}},"required":["name","image_urls"]}}}}},"get":{"operationId":"listCharacters","summary":"List Characters","description":"Your saved characters, newest first.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"},"description":"Characters, newest first."},"has_more":{"type":"boolean","description":"True when there are more: pass the last id as `after`."}},"required":["data","has_more"]},"example":{"object":"list","data":[{"id":"chr_9f2a1c3b7d4e5a60","object":"character","name":"Mara","sheet_url":"https://cdn.spicyapi.com/outputs/a1b2/chr_9f2a1c3b7d4e5a60.png","source_urls":["…"],"description":"…","imported":false,"status":"active","created":1758553200}],"has_more":false}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"limit","in":"query","required":false,"description":"How many to return, newest first. 1 to 500, default 500.","schema":{"type":"integer","description":"How many to return, newest first. 1 to 500, default 500."}},{"name":"after","in":"query","required":false,"description":"The id of the last character on the previous page, to get the next page.","schema":{"type":"string","description":"The id of the last character on the previous page, to get the next page."}}]}},"/v1/characters/import":{"post":{"operationId":"importCharacter","summary":"Import Character","description":"Bring an existing AI-generated character over from your own platform. Only for verified businesses with an open import window. SpicyAPI does not accept uploaded images: image editing and image-to-video only take images your own account generated. `spicy-image-reference-1` takes a picture only to recreate it as new people (the upload is never kept or reusable); use that if you have a picture rather than a platform. The other exception is for registered businesses moving their own platform's existing AI-generated characters to SpicyAPI. Apply in the dashboard under Settings, Character import (https://www.spicyapi.com/dashboard/import): the account needs at least $50 of paid balance, you prove a mailbox on your platform's domain (your account email if it is on that domain, or any address there we send a code to), you upload the certificate of incorporation and an official list of current directors from the last 3 months, and the director you name signs the Character Import Agreement (https://www.spicyapi.com/import-agreement) from a link we email them, with a drawn signature. We read the documents and check them against the company's registry record, the website and the Internet Archive; when everything matches, the window opens straight away for 7 days, otherwise a person reviews the application, usually within one business day. Outside the window this endpoint returns 403 with `import_not_enabled`, `import_window_closed` or `import_limit_reached`; `GET /v1/account` shows your window as `character_import`. The limit grows with evidence: you start with 10 imports, and every import whose source page verifies (or that we approve) gives its place back. Moving a large library? Say how many characters when you apply, or email contact@spicyapi.com, and we can set a larger allowance up front. If any image fails screening, import is paused for the account while we review it. Imports are governed by Schedule 1 (Character Import Terms) of the Terms of Service (https://www.spicyapi.com/terms-of-service) and the Character Import Agreement. Until the account has accepted terms that include Schedule 1, this endpoint returns 403 `import_terms_required`. The feature is for migrating your own existing library: do not expose it to your end users or send images they supplied, and never a real person, whatever permission they gave. Keep records of how each imported character was created, since we may ask for them. Every import names `source_url`, the public page where that character appears on your verified website. A URL on another domain is refused (422 `import_source_mismatch`), and so is a page that does not exist (422 `import_source_not_found`); nothing is stored or charged. We fetch the page, look for the uploaded image on it, and check that the Internet Archive has the page (or that image) from at least 30 days before the import. When any of that cannot be confirmed, the import is held for review rather than refused. Send 1 to 3 images of the same character: JPEG, PNG or WebP, 256 to 8192 pixels a side, at most 10 MB each, as multipart `file` parts or as `image_urls` we download from public https addresses. Multipart bodies are limited to about 4 MB in total, so send `image_urls` for larger files. `consent: true` is required on every request: it confirms the warranties in Schedule 1, that every image shows a fictional, AI-generated adult character your platform created, is not and was not made from an image of a real person, and was not supplied by an end user. It is stored with the time, the key, the terms version and the `user` you send. Every image is screened before anything is rendered or charged, with the same checks as generated output (apparent minors, recognisable real people, real animals with nudity). Files that carry camera metadata are refused as photographs (422 `import_photo_metadata`). If an image fails screening it is deleted, nothing is charged, and character import is switched off for the account while we review it (422 `import_blocked`). When screening is unreachable the request returns 503 and nothing is stored or charged. Stylised characters (anime, cartoon, illustration, 3D) with a verified source page are ready at once. Every photorealistic character waits for a person, and we also search the web for the image (whole-image matching, never face recognition): an exact copy on a stock photo site or on several other sites is refused (422 `import_found_elsewhere`, nothing stored or charged). Held imports return 202 with `status: \"under_review\"` and no `sheet_url` until approved, and using the character returns 409 until it is approved, usually within one business day. The result is an ordinary character. The uploads are rendered into a new reference sheet, billed as one `spicy-image-edit-1` image, and you pass its id as `character` to `/v1/images/generations`, `/v1/images/edits` or `/v1/videos/generations`. The uploaded files are never inputs themselves: they cannot be edited or animated directly. Imported characters keep working after the window closes; how many you can import is set by your import allowance. We review imported characters and what is generated with them; anything that breaks the acceptable use policy is taken down together with everything made from it.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"201":{"description":"202 instead of 201 when the import is held for review (`status: \"under_review\"`, with a `note`).","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Character id, `chr_...`."},"name":{"type":"string","description":"The label you gave."},"sheet_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rendered reference sheet (also listed in `GET /v1/images` once the character is active). `null` while the character is `under_review`."},"source_urls":{"type":"array","items":{"type":"string"},"description":"The images it was built from. Empty while the character is `under_review`."},"description":{"type":"string","description":"Text descriptor of the person, appended to prompts that use the character."},"imported":{"type":"boolean","description":"`true` for a character brought in with `POST /v1/characters/import`."},"views":{"type":"array","items":{"type":"object"},"description":"Body views (`id`, `label`, `url`, and for a cropped view `source_url` and `crop`): your own images of this person from other angles, added with `POST /v1/characters/{id}/views`. Empty while `under_review`."},"status":{"type":"string","description":"`active`, or `under_review` for an import a person has to approve first. Using an `under_review` character returns 409."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."},"import_id":{"type":"string","description":"Import id, `imp_...`, for support requests."},"cost_usd":{"type":"number","description":"The reference sheet render, billed as one `spicy-image-edit-1` image."},"provenance":{"type":"object","description":"`source_url`, and `verified`: whether the image was found on that page and the page is in the Internet Archive from at least 30 days ago. Unverified imports are held for review."},"character_import":{"type":"object","properties":{"status":{"type":"string","description":"`open`, `expired`, `exhausted`, `revoked` or `not_enabled`."},"expires_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp the window closes."},"used":{"type":"integer","description":"Imports used so far."},"limit":{"type":"integer","description":"Imports allowed in total: 10, plus one for every import whose provenance verified or that we approved."},"remaining":{"type":"integer","description":"`limit` minus `used`."}},"required":["status","used","limit","remaining"],"description":"Your import window after this request."}},"required":["id","name","sheet_url","source_urls","description","imported","views","status","import_id","cost_usd","provenance","character_import"]},"example":{"id":"chr_4b7e2a9c1d3f5e60","object":"character","name":"Mara","sheet_url":"https://cdn.spicyapi.com/outputs/a1b2/chr_4b7e2a9c1d3f5e60.png","source_urls":["https://cdn.spicyapi.com/imports/a1b2/imp_9d3c…-0.png"],"description":"Adult woman in her late 20s, oval face, deep-set pale blue eyes, full lips, long straight ash-blonde hair, fair skin, slim build.","imported":true,"status":"active","created":1758553200,"import_id":"imp_9d3c5a1e7f2b4c68","cost_usd":0.09,"provenance":{"source_url":"https://example.com/characters/mara","verified":true},"character_import":{"status":"open","expires_at":1759762800,"used":12,"limit":21,"remaining":9}}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"description":"Send `file` parts (multipart/form-data) or `image_urls` (an `application/json` body), not both.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A label, up to 60 characters."},"source_url":{"type":"string","description":"The public https page where this character appears on your platform, on the website verified in your application. We look for the uploaded image on it and in the Internet Archive."},"file":{"type":"string","description":"An image of the character: JPEG, PNG or WebP, 256 to 8192 pixels a side, at most 10 MB. Repeat the part for up to 3 images of the same character."},"image_urls":{"type":"array","items":{"type":"string"},"description":"1 to 3 public https URLs of the same character we download on your behalf (same limits; private, loopback and plain-IP hosts are refused)."},"consent":{"type":"boolean","description":"Must be `true` (the string `true` in multipart): your attestation under Schedule 1 of the Terms of Service that every image shows a fictional, AI-generated adult character your platform created, is not and was not made from an image of a real person, and was not supplied by an end user."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["name","source_url","consent"]}}}}}},"/v1/characters/{id}":{"delete":{"operationId":"deleteCharacter","summary":"Delete Character","description":"Remove a saved character. Its reference sheet stays in your image library.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The deleted character id."},"deleted":{"type":"boolean","description":"Always true."}},"required":["id","deleted"]},"example":{"id":"chr_9f2a1c3b7d4e5a60","object":"character","deleted":true}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Character id from POST /v1/characters."}]}},"/v1/characters/{id}/views":{"post":{"operationId":"addCharacterView","summary":"Add Character View","description":"Add one of your own images of this person from another angle (from behind, a pose, a close-up), so the body stays the same in every shot. The character's sheet shows the face and upper body; anything it does not show is drawn anew each time. A view fills that in: an image of the same person from your account (`GET /v1/images`), with a `label` saying what it shows, for example `close-up of her buttocks from behind` or `close-up between her legs`. Pick views you are happy with: marks, proportions and anatomy are kept as they appear in them. Close-ups of one body area work best (the buttocks with their moles, the intimate area from the front or from behind). A full-body view keeps the body too but pulls the pose and framing toward its own: a standing view turned a face-down prompt into a standing shot in our tests, where a close-up of the same body kept the prompt's pose. Requests with the character then send the views that fit the prompt after the sheet (up to 2, picked from the labels; `views` on the request chooses them yourself), and the model keeps that body: the same proportions, skin, moles, freckles and intimate anatomy, with the pose, angle, clothing and setting from the prompt. `character_views` in the response lists the views sent. Free. Up to 8 views per character; adding the same whole image again returns the character unchanged. The label is screened like a prompt. A close-up is easiest to make with `crop`: draw the box around the area on any of your images of her.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Character","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Character id, `chr_...`."},"name":{"type":"string","description":"The label you gave."},"sheet_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rendered reference sheet (also listed in `GET /v1/images` once the character is active). `null` while the character is `under_review`."},"source_urls":{"type":"array","items":{"type":"string"},"description":"The images it was built from. Empty while the character is `under_review`."},"description":{"type":"string","description":"Text descriptor of the person, appended to prompts that use the character."},"imported":{"type":"boolean","description":"`true` for a character brought in with `POST /v1/characters/import`."},"views":{"type":"array","items":{"type":"object"},"description":"Body views (`id`, `label`, `url`, and for a cropped view `source_url` and `crop`): your own images of this person from other angles, added with `POST /v1/characters/{id}/views`. Empty while `under_review`."},"status":{"type":"string","description":"`active`, or `under_review` for an import a person has to approve first. Using an `under_review` character returns 409."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."}},"required":["id","name","sheet_url","source_urls","description","imported","views","status"]},"example":{"id":"chr_9f2a1c3b7d4e5a60","object":"character","name":"Mara","sheet_url":"https://cdn.spicyapi.com/outputs/a1b2/chr_9f2a1c3b7d4e5a60.png","views":[{"id":"cv_3b1f9a20c4d7","label":"close-up of her buttocks from behind","url":"https://cdn.spicyapi.com/outputs/a1b2/cv_3b1f9a20c4d7.png","source_url":"https://cdn.spicyapi.com/outputs/a1b2/sj_7c2e…-0.png","crop":{"x":0.15,"y":0.5,"width":0.7,"height":0.4}}],"status":"active","created":1758553200}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Character id from POST /v1/characters."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"image_url":{"type":"string","description":"One of your own generated images of this person (`GET /v1/images`)."},"label":{"type":"string","description":"What the image shows, 1 to 80 characters: the area and the angle, e.g. `close-up of her buttocks from behind`."},"crop":{"type":"object","description":"Optional `{ x, y, width, height }` in fractions of the image (0 to 1, from its top left corner): only that area becomes the view, stored as a new image in your library (`source_url` and `crop` on the view say where it came from). At least 256 pixels on each side. Use it to make a close-up of one area from a full-body image."}},"required":["image_url","label"]}}}}}},"/v1/characters/{id}/views/{view_id}":{"delete":{"operationId":"deleteCharacterView","summary":"Delete Character View","description":"Remove a body view from a character. The image stays in your library.","tags":["Characters"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Character","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Character id, `chr_...`."},"name":{"type":"string","description":"The label you gave."},"sheet_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The rendered reference sheet (also listed in `GET /v1/images` once the character is active). `null` while the character is `under_review`."},"source_urls":{"type":"array","items":{"type":"string"},"description":"The images it was built from. Empty while the character is `under_review`."},"description":{"type":"string","description":"Text descriptor of the person, appended to prompts that use the character."},"imported":{"type":"boolean","description":"`true` for a character brought in with `POST /v1/characters/import`."},"views":{"type":"array","items":{"type":"object"},"description":"Body views (`id`, `label`, `url`, and for a cropped view `source_url` and `crop`): your own images of this person from other angles, added with `POST /v1/characters/{id}/views`. Empty while `under_review`."},"status":{"type":"string","description":"`active`, or `under_review` for an import a person has to approve first. Using an `under_review` character returns 409."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."}},"required":["id","name","sheet_url","source_urls","description","imported","views","status"]},"example":{"id":"chr_9f2a1c3b7d4e5a60","object":"character","name":"Mara","views":[],"status":"active"}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Character id from POST /v1/characters."}]}},"/v1/audio/speech":{"post":{"operationId":"createSpeech","summary":"Create Speech","description":"Turn text into speech. Returns a durable audio URL you can pass straight to video generation as `audio_url`, or streams the audio as it is synthesized. The request follows the OpenAI speech shape (`model`, `input`, `voice`, `instructions`, `response_format`). By default the call is synchronous and the response is JSON with a `url`, not raw audio bytes: the clip is stored on the CDN (WAV 24 kHz 16-bit mono, or MP3 on Spicy Voice 2 and 3) and registered as one of your audio clips, so it shows up in `GET /v1/audio` with `source: \"speech\"`. Seven models. `spicy-voice-1` speaks 45 preset voices. `spicy-voice-1-expressive` speaks 21 of them and also takes `instructions` describing emotion, pace, pitch and delivery. `spicy-voice-1-custom` speaks only your own voices, the `vc_...` ids from `POST /v1/voices`. `spicy-voice-2` (voices `Lingxin`, `Lufeng`) and `spicy-voice-2-flash` (voices `Fengyue`, `Yuanfei`, `Lingxi`, `Xiaoxin`, `Huan`, `Chuanshu`, `Mary`, `Eva`, `John`) are the expressive generation: inline tags, `instructions`, up to 5,000 characters, MP3 output and streaming, in `Auto`, `English`, `Chinese`; custom voices stay on `spicy-voice-1-custom`. `spicy-voice-3` is the highest quality: it reads the emotion and pacing of the text itself, in 90+ languages (any `language` value), with free-form audio tags; `spicy-voice-3-flash` is its low-latency version for streaming. Both speak `Bella Bright`, `Roger`, `Sarah`, `Laura`, `Charlie`, `George`, `Callum`, `River`, `Harry`, `Liam`, `Alice`, `Matilda`, `Will`, `Jessica`, `Eric Smooth`, `Chris`, `Brian`, `Daniel`, `Lily`, `Adam`, `Bill`, take no `instructions` (put directions in tags instead), and output MP3 or WAV and stream. A custom voice on a preset model, a preset voice on the custom model, or `instructions` on a model without them is a 400 that names the right model. `GET /v1/voices` lists every voice with the models that speak it. Audio tags on Spicy Voice 3 are free-form directions in square brackets, performed rather than read out: `[whispers]`, `[sighs]`, `[laughs]`, `[softly, a little shy]`. Put one at each change of mood. Inline tags (Spicy Voice 2 models): write them in square brackets inside `input`. Control tags change the delivery until the next tag: `[sad]`, `[amazed]`, `[deep and loud shouting]`, `[trembling]`, `[angry]`, `[excited]`, `[sarcastic]`, `[curious]`, `[like dracula]`, `[bored]`, `[tired]`, `[scornful]`, `[shouting]`, `[asmr]`, `[panicked]`, `[mischievously]`, `[empathetic]`, `[whispers]`, `[reluctantly]`, `[crying]`, `[serious]`, `[very slowly]`, `[very fast]`. Sound tags insert a vocal sound at that point: `[gasp]`, `[sighing]`, `[clears throat]`, `[giggles]`, `[laughing]`, `[cough]`, `[snorts]`. Tags count as billed characters. Example: `[excited]Hey you, I've been waiting all night.[giggles] Come here.[whispers] Closer.` Streaming (Spicy Voice 2 and 3 models): with `stream: true` the response body is the raw audio as it is synthesized, the first audio arriving in about 1.5 seconds. `response_format` picks the container: `pcm` (default: 24 kHz, 16-bit, mono, little-endian, no header), `wav` or `mp3`. Headers: `x-spicyapi-audio-id` (the clip is stored afterwards as an audio asset under that id, so it appears in `GET /v1/audio`), `x-spicyapi-sample-rate: 24000` and `x-spicyapi-cost-usd` (the amount debited before the stream starts; settled to the billed count afterwards). If the stream fails before any audio arrives, the charge is refunded. Billed per 10,000 characters of `input`: $0.30 on `spicy-voice-1`, $0.30 on `spicy-voice-1-expressive`, $0.30 on `spicy-voice-1-custom`, $0.40 on `spicy-voice-2`, $0.30 on `spicy-voice-2-flash`, $0.80 on `spicy-voice-3`, $0.40 on `spicy-voice-3-flash`. CJK ideographs count as 2 characters (on Spicy Voice 3 they settle at about 1.6, as the voice service counts them). `instructions` are included in the debit; when the billed count comes back lower the difference is refunded, and `cost_usd` is the settled amount. Failed generations are refunded. The `input` and the `instructions` are screened like a prompt before anything is charged; a blocked request returns the same 422 as a blocked prompt. `dry_run: true` validates, screens and returns the price without generating. Sandbox keys get a fixture clip flagged `sandbox: true` with the `cost_usd` a live key would pay (one second of silence when streaming); nothing is billed. Make a character speak: pass the returned `url` byte-for-byte as `audio_url` on `POST /v1/videos/generations`. On `spicy-motion-2` and `spicy-video-1` it is driving audio: the clip becomes the soundtrack and the mouth follows the words, alongside a first frame where the model takes one. On `spicy-motion-3` and `spicy-motion-3-fast` it is reference audio: the model uses it for voice, tone and beat while the words come from the prompt, with a text prompt or references and never with a first frame (up to 5 clips, 15 seconds in total). Audio for video must be 2 to 30 seconds long, so split long text across requests.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"The JSON response. With `stream: true` the body is the audio itself (`audio/pcm; rate=24000`, `audio/wav` or `audio/mpeg`) and the id and cost are in the `x-spicyapi-audio-id` and `x-spicyapi-cost-usd` headers.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Clip id, `au_...`; the same id appears in `GET /v1/audio`."},"object":{"type":"string","description":"Always `audio.speech`."},"model":{"type":"string","description":"The model that spoke."},"url":{"type":"string","description":"CDN URL of the clip. Pass it byte-for-byte as `audio_url` (or in `audio_urls`) on `POST /v1/videos/generations`."},"duration_s":{"type":"number","description":"Length in seconds, to one decimal."},"voice":{"type":"string","description":"The preset name or `vc_...` id that was used."},"format":{"type":"string","description":"`wav` or `mp3`."},"cost_usd":{"type":"number","description":"What this request cost in US dollars, settled to the billed character count."}},"required":["id","object","model","url","duration_s","voice","format","cost_usd"]},"example":{"id":"au_7d2e9c1a4b6f8d0e2a","object":"audio.speech","model":"spicy-voice-1","url":"https://cdn.spicyapi.com/audio/a1b2c3d4e5f6a7b8/au_7d2e9c1a4b6f8d0e2a.wav","duration_s":3.4,"voice":"Cherry","format":"wav","cost_usd":0.00086}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"`spicy-voice-1`, `spicy-voice-1-expressive`, `spicy-voice-1-custom`, `spicy-voice-2`, `spicy-voice-2-flash`, `spicy-voice-3` or `spicy-voice-3-flash`."},"input":{"type":"string","description":"The text to speak: at most 600 characters on Spicy Voice 1 models, 5,000 on Spicy Voice 2 and 3 models (inline tags included). Longer text is a 400: split it into several requests."},"voice":{"type":"string","description":"A preset voice name the model speaks (case-insensitive, e.g. `Cherry` on Spicy Voice 1, `Lingxin` on `spicy-voice-2`, `Eva` on `spicy-voice-2-flash`, `Sarah` on Spicy Voice 3), or on `spicy-voice-1-custom` one of your `vc_...` voice ids. See `GET /v1/voices`."},"instructions":{"type":"string","description":"`spicy-voice-1-expressive`, `spicy-voice-2` and `spicy-voice-2-flash`: how to say it, in plain English or Chinese (emotion, pace, pitch, delivery), at most 1,600 characters. Counted in the debit, settled to the billed count. Spicy Voice 3 takes none: put directions in tags."},"language":{"type":"string","description":"One of `Auto`, `English`, `Chinese`, `German`, `Italian`, `Portuguese`, `Spanish`, `Japanese`, `Korean`, `French`, `Russian` on Spicy Voice 1 models, `Auto`, `English`, `Chinese` on Spicy Voice 2 models, any of the Spicy Voice 1 list on Spicy Voice 3 (case-insensitive; the model's `speech.languages`). Defaults to `Auto`, which detects the language as it goes."},"response_format":{"type":"string","description":"`spicy-voice-1` and `spicy-voice-1-expressive`: `wav` (default) or `mp3`. `spicy-voice-1-custom`: `wav`. Spicy Voice 2 and 3 models: `wav` (default) or `mp3`; with `stream: true`, `pcm` (default), `wav` or `mp3`."},"stream":{"type":"boolean","description":"Spicy Voice 2 and 3 models only: `true` returns the audio bytes as they are synthesized instead of JSON (see Streaming above). A 400 on Spicy Voice 1 models."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model","input","voice"]}}}}}},"/v1/audio/transcriptions":{"post":{"operationId":"createTranscription","summary":"Create Transcription","description":"Turn speech into text: voice notes, clips and call recordings. Synchronous. Send the audio as a multipart `file` (OpenAI SDK compatible) or as JSON with a public https `url`. Formats: wav, mp3, m4a and mp4, ogg and opus, flac, webm and more; up to 10 MB and 5 minutes per request. The audio is sent for recognition inline and never stored. Billed by the second of audio at $0.0042 per minute on `spicy-transcribe-1` (minimum $0.0001 per request), settled to the length the recognizer measured. Nothing is generated, so there is no content block; a file that cannot be decoded is a 400 and nothing is charged. When recognition is unavailable the call returns 503, nothing charged: retry in a few seconds. Sandbox keys get a fixture transcript and pay nothing.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Transcription","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Request id."},"object":{"type":"string","description":"Always `audio.transcription`."},"model":{"type":"string","description":"The model that transcribed."},"text":{"type":"string","description":"The transcript."},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The language detected (or the hint you sent), as a short code such as `en`."},"duration_s":{"type":"number","description":"Seconds of audio billed."},"cost_usd":{"type":"number","description":"What this request cost, in US dollars, already debited from your balance."}},"required":["id","object","model","text","language","duration_s","cost_usd"]},"example":{"id":"sj_4b8e21d7c3a9f0e1b2d4c6a8","object":"audio.transcription","model":"spicy-transcribe-1","text":"Hey, it's me. I just got home, call me when you're free tonight.","language":"en","duration_s":6,"cost_usd":0.00042}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"description":"Send exactly one of `file` (multipart/form-data) or `url` (an `application/json` body).","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The audio file, at most 10 MB and 5 minutes."},"url":{"type":"string","description":"Public https URL of the audio, fetched on your behalf (same limits; private, loopback and plain-IP hosts are refused)."},"model":{"type":"string","description":"`spicy-transcribe-1`, the default."},"language":{"type":"string","description":"Optional hint when you know the language: `English`, `Chinese`, `German`, `Italian`, `Portuguese`, `Spanish`, `Japanese`, `Korean`, `French` or `Russian`. Omit to detect it."}},"required":[]}}}}}},"/v1/voices":{"post":{"operationId":"createVoice","summary":"Create Voice","description":"Clone a voice from one of your clips with the speaker's consent (voice design is paused). Custom voices are spoken by `spicy-voice-1-custom`. Voice design is paused: since 10 October 2026 our provider offers it only in its Beijing region (mainland China), where its own content inspection cannot be turned off, so `description` returns 400 `voice_design_unavailable` and nothing is charged; cloning works. Send exactly one of `description` (design a voice) or `audio_url` with `consent: true` (clone a voice). Either way you get a voice id, `vc_...`, to pass as `voice` on `POST /v1/audio/speech` with `model: \"spicy-voice-1-custom\"`. Design: `description` says what the voice sounds like (gender, age range, pitch, pace, tone, accent). It must describe vocal qualities, not a person: a name or description that names, references or imitates a real person (\"sounds like\" someone, \"the voice of\" someone, impressions, celebrities) is a 400 with `code: \"real_person\"`, and the name, description and preview line are screened like a prompt as well (422 when blocked). A designed voice comes with `preview_url`, a WAV of `preview_text` in the new voice. Clone: `audio_url` must be one of your own clips from `POST /v1/audio` (browse them with `GET /v1/audio`), at most 10 MB; the clip was transcribed and screened when it was uploaded, and the voice name is screened here. `consent: true` is required: it is your attestation that the voice is your own, or that you hold the speaker's written consent to clone it and to generate speech with it. The attestation is stored with the voice, together with the time, the key and the `user` you send, and returned as `consent_attested_at`. Cloning someone's voice without that consent breaks the acceptable use policy. A noisy sample, or one that does not match its transcript, still makes a voice but returns a `warning`; a clean 10 to 20 second sample gives the closest match. Price: $0.02 per designed voice and $0.02 per cloned voice, debited once when the voice is created and refunded if creation fails. Speaking with it is billed like any other speech request. `dry_run: true` validates, screens and prices without creating. Sandbox keys get a fixture voice (`vc_sbx_...`) that sandbox speech accepts; nothing is billed.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Voice","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `voice`: the preset name, or `vc_...` for your own voices."},"object":{"type":"string","description":"Always `voice`."},"kind":{"type":"string","description":"`preset`, `designed` or `cloned`."},"name":{"type":"string","description":"Display name."},"models":{"type":"array","items":{"type":"string"},"description":"The speech models that speak this voice; `spicy-voice-1-custom` for your own."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Designed voices: the description they were made from."},"source_audio_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cloned voices: the clip (`au_...`) they were cloned from."},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Designed voices: a WAV of the preview line in the new voice."},"consent_attested_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Cloned voices: Unix timestamp of your consent attestation."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp. Custom voices only."},"warning":{"type":"string","description":"Clones only, when the sample was noisy or off-transcript and the clone may sound less like the speaker."},"cost_usd":{"type":"number","description":"The creation fee, already debited."}},"required":["id","object","kind","name","models","cost_usd"]},"example":{"id":"vc_4e1b7c9a2d5f8e0b3c","object":"voice","kind":"designed","name":"Mara","models":["spicy-voice-1-custom"],"description":"Woman in her thirties, low and warm, slightly husky, unhurried, a playful smile in the voice, neutral American accent.","source_audio_id":null,"preview_url":"https://cdn.spicyapi.com/audio/a1b2c3d4e5f6a7b8/vc_4e1b7c9a2d5f8e0b3c-preview.wav","consent_attested_at":null,"created":1758553200,"cost_usd":0.02}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A label for the voice, at most 60 characters."},"description":{"type":"string","description":"Design: the voice's qualities in English or Chinese, at most 500 characters. Omit when cloning."},"preview_text":{"type":"string","description":"Design: the line spoken in the preview clip, 1 to 200 characters, in the same language as `language`. Defaults to a short neutral line."},"audio_url":{"type":"string","description":"Clone: the `url` of one of your clips from `POST /v1/audio`, at most 10 MB. Omit when designing."},"consent":{"type":"boolean","description":"Clone: must be `true`, your attestation that the voice is your own or that the speaker gave written consent. Anything else is a 400."},"language":{"type":"string","description":"The voice's language: `English` (default), `Chinese`, `German`, `Italian`, `Portuguese`, `Spanish`, `Japanese`, `Korean`, `French` or `Russian`. Design takes only `English` or `Chinese` (the language of `description` and `preview_text`)."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["name"]}}}}},"get":{"operationId":"listVoices","summary":"List Voices","description":"Every preset voice with the models that speak it, then your designed and cloned voices, newest first. Preset entries use the voice name as `id` (for example `Cherry`) and list the models that speak it: every preset on `spicy-voice-1`, a subset on `spicy-voice-1-expressive`. Your own voices have a `vc_...` id and are spoken by `spicy-voice-1-custom`. Sandbox keys see the presets only.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `voice`: the preset name, or `vc_...` for your own voices."},"object":{"type":"string","description":"Always `voice`."},"kind":{"type":"string","description":"`preset`, `designed` or `cloned`."},"name":{"type":"string","description":"Display name."},"models":{"type":"array","items":{"type":"string"},"description":"The speech models that speak this voice; `spicy-voice-1-custom` for your own."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Designed voices: the description they were made from."},"source_audio_id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cloned voices: the clip (`au_...`) they were cloned from."},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Designed voices: a WAV of the preview line in the new voice."},"consent_attested_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Cloned voices: Unix timestamp of your consent attestation."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp. Custom voices only."}},"required":["id","object","kind","name","models"]},"description":"Preset voices, then yours."},"has_more":{"type":"boolean","description":"True when more of your voices follow: pass the last id as `after`."}},"required":["data","has_more"]},"example":{"object":"list","data":[{"id":"Cherry","object":"voice","kind":"preset","name":"Cherry","models":["spicy-voice-1","spicy-voice-1-expressive"]},{"id":"Jennifer","object":"voice","kind":"preset","name":"Jennifer","models":["spicy-voice-1"]},{"id":"vc_4e1b7c9a2d5f8e0b3c","object":"voice","kind":"cloned","name":"My voice","models":["spicy-voice-1-custom"],"description":null,"source_audio_id":"au_3c9f1e7a2b4d6f80a1c2","preview_url":null,"consent_attested_at":1758553200,"created":1758553200}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"limit","in":"query","required":false,"description":"How many of your own voices to return, 1 to 200. Defaults to 200.","schema":{"type":"integer","description":"How many of your own voices to return, 1 to 200. Defaults to 200."}},{"name":"after","in":"query","required":false,"description":"The `vc_...` id of the last voice on the previous page. Presets come on the first page only.","schema":{"type":"string","description":"The `vc_...` id of the last voice on the previous page. Presets come on the first page only."}}]}},"/v1/voices/{id}":{"delete":{"operationId":"deleteVoice","summary":"Delete Voice","description":"Delete one of your designed or cloned voices. The voice stops working at once. Clips already generated with it, and its preview clip, are unaffected.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The deleted voice id."},"object":{"type":"string","description":"Always `voice`."},"deleted":{"type":"boolean","description":"Always true. An unknown id returns 404."}},"required":["id","object","deleted"]},"example":{"id":"vc_4e1b7c9a2d5f8e0b3c","object":"voice","deleted":true}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Voice id (vc_...) from POST /v1/voices."}]}},"/v1/audio":{"post":{"operationId":"uploadAudio","summary":"Upload Audio","description":"Upload a short audio clip for lip-sync and audio-guided video. The clip is transcribed and screened before it can be used. The one place every account can send media from outside. Send a WAV or MP3 of 2 to 30 seconds, up to 15 MB, either as a multipart `file` or as a JSON `{ \"url\" }` we download from a public https address. The format and duration are read from the bytes; the declared content type is ignored. Every clip is transcribed and the transcript goes through the same screen as a prompt. Flat charge of $0.01 per upload for that screening. A clip that fails the screen is discarded and returns the same 422 shape as a blocked prompt (`code`, `outcome`, `categories`); the $0.01 still applies. When transcription or the screen is unreachable the upload returns 503, nothing is stored and the charge is refunded: retry in a few seconds. The returned `url` is what you pass as `audio_url` or in `audio_urls` on `POST /v1/videos/generations`. Clips uploaded by another account, or any external audio URL, are rejected there before you are charged. Sandbox keys get a fixture clip and pay nothing.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Audio","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Clip id, `au_...`."},"object":{"type":"string","description":"Always `audio`."},"url":{"type":"string","description":"CDN URL of the clip. Pass it byte-for-byte as `audio_url` (or in `audio_urls`) on `POST /v1/videos/generations`."},"duration_s":{"type":"number","description":"Length in seconds, to one decimal."},"mime":{"type":"string","description":"`audio/wav` or `audio/mpeg`, sniffed from the bytes."},"bytes":{"type":"integer","description":"File size."},"transcript":{"type":"string","description":"What the screen heard (first 500 characters)."},"source":{"type":"string","description":"`upload` for `POST /v1/audio`, `speech` for `POST /v1/audio/speech`."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."},"cost_usd":{"type":"number","description":"Always 0.01 (the screening charge); 0 on a sandbox key."}},"required":["id","object","url","duration_s","mime","bytes","transcript","source","cost_usd"]},"example":{"id":"au_3c9f1e7a2b4d6f80a1c2","object":"audio","url":"https://cdn.spicyapi.com/audio/a1b2c3d4e5f6a7b8/au_3c9f1e7a2b4d6f80a1c2.wav","duration_s":6.4,"mime":"audio/wav","bytes":1128492,"transcript":"Come here. I have been waiting all evening.","source":"upload","created":1758553200,"cost_usd":0.01}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"description":"Send exactly one of `file` (multipart/form-data) or `url` (an `application/json` body).","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The audio file. WAV or MP3, 2 to 30 seconds, at most 15 MB."},"url":{"type":"string","description":"Public https URL of a WAV or MP3 we fetch on your behalf (same size and length limits; private, loopback and plain-IP hosts are refused)."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":[]}}}}},"get":{"operationId":"listAudio","summary":"List Audio","description":"Your audio clips, uploaded and generated, newest first. Uploads from `POST /v1/audio` and speech from `POST /v1/audio/speech`. These are exactly the URLs accepted as `audio_url` and `audio_urls` on video generation.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Clip id, `au_...`."},"object":{"type":"string","description":"Always `audio`."},"url":{"type":"string","description":"CDN URL of the clip. Pass it byte-for-byte as `audio_url` (or in `audio_urls`) on `POST /v1/videos/generations`."},"duration_s":{"type":"number","description":"Length in seconds, to one decimal."},"mime":{"type":"string","description":"`audio/wav` or `audio/mpeg`, sniffed from the bytes."},"bytes":{"type":"integer","description":"File size."},"transcript":{"type":"string","description":"What the screen heard (first 500 characters)."},"source":{"type":"string","description":"`upload` for `POST /v1/audio`, `speech` for `POST /v1/audio/speech`."},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"Unix timestamp."}},"required":["id","object","url","duration_s","mime","bytes","transcript","source"]},"description":"Clips, newest first."},"has_more":{"type":"boolean","description":"True when the page came back full: pass the last id as `after`."}},"required":["data","has_more"]},"example":{"object":"list","data":[{"id":"au_3c9f1e7a2b4d6f80a1c2","object":"audio","url":"https://cdn.spicyapi.com/audio/a1b2c3d4e5f6a7b8/au_3c9f1e7a2b4d6f80a1c2.wav","duration_s":6.4,"mime":"audio/wav","bytes":1128492,"transcript":"Come here. I have been waiting all evening.","source":"upload","created":1758553200}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"limit","in":"query","required":false,"description":"How many to return, 1 to 100. Defaults to 50.","schema":{"type":"integer","description":"How many to return, 1 to 100. Defaults to 50."}},{"name":"after","in":"query","required":false,"description":"The id of the last clip on the previous page, to get the next page.","schema":{"type":"string","description":"The id of the last clip on the previous page, to get the next page."}}]}},"/v1/audio/{id}":{"delete":{"operationId":"deleteAudio","summary":"Delete Audio","description":"Forget a clip so it can no longer be used as a video input. Removes the clip from your library and from the provenance registry. Videos already made with it are unaffected.","tags":["Audio"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The deleted clip id."},"object":{"type":"string","description":"Always `audio`."},"deleted":{"type":"boolean","description":"Always true. An unknown id returns 404."}},"required":["id","object","deleted"]},"example":{"id":"au_3c9f1e7a2b4d6f80a1c2","object":"audio","deleted":true}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Clip id from POST /v1/audio."}]}},"/v1/videos/generations":{"post":{"operationId":"createVideo","summary":"Create Video","description":"Start a video generation. Returns a task to poll. Video is asynchronous: this call returns immediately with a task id, and you poll `GET /v1/videos/tasks/{id}` until it succeeds or fails. Image-to-video models need an `image_url` your account generated. Every image, clip and audio input must come from your own account: images from `GET /v1/images`, clips from your own finished tasks, audio from `POST /v1/audio` or `POST /v1/audio/speech`. Failed generations are refunded automatically. Model families. The Motion 2 generation: `spicy-motion-1`, `spicy-motion-2`, `spicy-video-1` and `spicy-character-video-1`. The Motion 3 generation: `spicy-motion-3` and `spicy-motion-3-fast`, with the wider parameter set noted per field below; an input `video_url` clip is billed per second on top of the output (`bills_input_video_seconds`), and they take `action`. The draft tier: `spicy-motion-draft-1`, silent image-to-video at a quarter of the price, for checking motion before rendering the keeper on Spicy Motion 2 or 3. The Cinema engine: `spicy-cinema-1` (text only), `spicy-cinema-1-image` (first frame) and `spicy-cinema-1-character` (a character or up to 9 reference images, named Image 1, Image 2 in the prompt), 3 to 15 seconds at 480P to 1080P with native audio always on, no negative prompt, no `enhance_prompt` and no audio inputs. Prices per output second: $0.20/s 720P, $0.30/s 1080P on the Motion 2 generation; $0.10/s 480P, $0.20/s 720P, $0.40/s 1080P on `spicy-motion-3`; $0.14/s 480P, $0.28/s 720P, $0.56/s 1080P on `spicy-motion-3-fast`; $0.05/s 720P, $0.075/s 1080P on `spicy-motion-draft-1`; $0.14/s 480P, $0.28/s 720P, $0.36/s 1080P on the Cinema models. To edit or restyle a finished clip, or to transfer its motion onto another person, use `POST /v1/videos/edits`. Input combinations. Which inputs may travel together is per model and comes from `inputs` on `GET /v1/models`; a request that mixes them wrongly is a 400 that names what you sent and quotes the valid combinations below. Audio comes in two modes: driving audio (`spicy-motion-2`, `spicy-video-1`) makes the clip the soundtrack of the output, the mouth follows it, and it goes alongside a first frame where the model takes one; reference audio (`spicy-motion-3`, `spicy-motion-3-fast`) leaves the sound to the model, which uses the clip for voice, tone and beat while the words come from the prompt, and only goes with a text prompt or references, never with a first frame. Send `audio_mode` to assert which one you expect. `spicy-motion-3`: reference audio: the model generates sound and uses the clip for voice, tone and beat, up to 5 clips and 15 seconds total. Text prompt only, with aspect_ratio; dialogue written in the prompt is spoken natively. First frame (image_url), optionally a last frame (last_frame_url); no references or audio in the same request. Reference images (up to 10, or a character) plus a text prompt: same person, framing chosen by the model. Reference images, a reference video (video_url, 15s max) and reference audio (up to 5 clips, 15s total) in any mix, with a text prompt; audio guides voice, tone and beat while the words come from the prompt. `spicy-motion-3-fast`: reference audio: the model generates sound and uses the clip for voice, tone and beat, up to 5 clips and 15 seconds total. Text prompt only, with aspect_ratio; dialogue written in the prompt is spoken natively. First frame (image_url), optionally a last frame (last_frame_url); no references or audio in the same request. Reference images (up to 10, or a character) plus a text prompt: same person, framing chosen by the model. Reference images, a reference video (video_url, 15s max) and reference audio (up to 5 clips, 15s total) in any mix, with a text prompt; audio guides voice, tone and beat while the words come from the prompt. `spicy-cinema-1-image`: no audio input. First frame (image_url) with a text prompt; the output keeps the image's shape. `spicy-motion-2`: driving audio: the clip is the soundtrack and the mouth follows it. First frame (image_url), optionally a last frame (last_frame_url). First frame plus driving audio (audio_url): the clip becomes the soundtrack and the mouth follows it. First frame, last frame and driving audio together. A clip to continue (video_url, 2 to 10s) instead of a first frame, optionally with a last frame. `spicy-character-video-1`: no audio input. A character (or reference images) plus a text prompt. A character plus a first frame (image_url) that opens the clip. `spicy-cinema-1-character`: no audio input. A character or reference images (up to 9) plus a text prompt that names them Image 1, Image 2.... `spicy-cinema-1`: no audio input. Text prompt only, with aspect_ratio; the clip has native audio. `spicy-video-1`: driving audio: the clip is the soundtrack and the mouth follows it. Text prompt only, with aspect_ratio. Text prompt plus driving audio (audio_url): the clip becomes the soundtrack and motion follows it. `spicy-motion-draft-1`: no audio input. First frame (image_url) with a text prompt; the clip is silent. `spicy-motion-1`: no audio input. First frame (image_url) with a text prompt.","tags":["Video"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"A task you can poll.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Task id. Pass it to `GET /v1/videos/tasks/{id}`."},"object":{"type":"string","description":"Always `task`."},"type":{"type":"string","description":"Always `video`."},"model":{"type":"string","description":"The model generating the clip."},"status":{"type":"string","description":"`queued`, `processing`, `finalizing`, `succeeded`, or `failed`."},"output":{"anyOf":[{"type":"object","properties":{"video_url":{"type":"string","description":"Durable CDN URL of the finished clip."}},"required":["video_url"]},{"type":"null"}],"description":"Null until the task succeeds."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Why the task failed, when it did."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`blocked` when the model declined the content (refunded); otherwise null."},"cost_usd":{"type":"number","description":"What was debited now, including any billed input clip seconds. With `duration: -1` this is the maximum; the task's `cost_usd` settles to the rendered length when it finishes."},"character_views":{"type":"array","items":{"type":"string"},"description":"The character's body views sent as references, when any were."},"poll_url":{"type":"string","description":"`/v1/videos/tasks/{id}`, relative to the base URL."}},"required":["id","object","type","model","status","cost_usd","poll_url"]},"example":{"id":"task_5d9e2f10","object":"task","type":"video","model":"spicy-motion-3","status":"queued","output":null,"error":null,"cost_usd":3.2,"poll_url":"/v1/videos/tasks/task_5d9e2f10"}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A video model id, e.g. `spicy-motion-3`, `spicy-motion-draft-1` or `spicy-cinema-1`."},"prompt":{"type":"string","description":"What should happen in the clip. Capped at the model's `limits.maxPromptChars` (1500 on `spicy-motion-1` and `spicy-motion-draft-1`, 5000 on the rest of the Motion 2 generation and the Cinema models, 20000 on `spicy-motion-3` and `spicy-motion-3-fast`); longer prompts are a 400. May be empty when `image_url` is set, or with `action`, where it describes the scene (setting, outfit, lighting) rather than the motion. On `spicy-cinema-1-character` refer to the references as Image 1, Image 2 and so on."},"image_url":{"type":"string","description":"First frame; must be one of your generated images. Required where `requires_image` is true (including `spicy-motion-draft-1` and `spicy-cinema-1-image`). Optional on `spicy-motion-3`, `spicy-motion-3-fast` and `spicy-character-video-1`, which generate from text alone when it is omitted. Rejected on `spicy-video-1`, `spicy-cinema-1` and `spicy-cinema-1-character`. Cannot be combined with `reference_image_urls`, nor with `video_url` on the Motion 2 generation. With `action` it is not the first frame but the woman who performs the act (identity reference)."},"last_frame_url":{"type":"string","description":"Closing frame, one of your generated images; the clip travels from `image_url` to it. Needs `image_url` (or a continuation `video_url`). Models with `inputs.last_frame`: `spicy-motion-2`, `spicy-motion-3` and `spicy-motion-3-fast`. See Input combinations above."},"reference_image_urls":{"type":"array","items":{"type":"string"},"description":"Up to `inputs.reference_images` of your generated images that guide identity and look without fixing the opening shot (a `character` counts as one; its sheet goes first as Image 1). `spicy-character-video-1`, `spicy-motion-3`, `spicy-motion-3-fast` (up to 10) and `spicy-cinema-1-character` (up to 9). Exclusive with `image_url`. See Input combinations above."},"character":{"type":"string","description":"A saved character id (`chr_...`) to keep the same person. Same models as `reference_image_urls`; on other models generate a first frame with the character and pass it as `image_url`."},"views":{"type":"array","items":{"type":"string"},"description":"With `character`: the body view ids to send (`cv_...` from the character's `views`), up to 2 (in the free reference slots), or `[]` for none. Omitted: the views that fit the prompt are picked for you (a back view for a shot from behind, a close-up for a close-up), listed in the response as `character_views`. Not with `action`. No extra charge."},"action":{"type":"string","description":"`spicy-motion-3` and `spicy-motion-3-fast` only: the id of a ready-made sex act from `GET /v1/videos/actions` (e.g. `pov-missionary`, `pov-blowjob-deepthroat`). The action's reference clip supplies camera angle, framing, pose and motion second for second; `image_url` (or `reference_image_urls`, or a `character`, one of which is required) supplies the woman; `prompt` is optional and sets the scene, and without it she is in a plain light bedroom. Not combinable with `video_url` or `last_frame_url`. `duration` defaults to 8 and `aspect_ratio` to the action's own. The reference clip is the first 4 seconds of the act (5 on two actions, see `duration_s`); the model follows it, then carries the same act on as one continuous take. Its seconds are billed as input seconds on top of the output seconds, so an 8 second 720P clip bills 12 seconds."},"style":{"type":"string","description":"An art style id from `GET /v1/styles` (`photorealistic`, `studio`, `anime`, `3d` or `cartoon`), on every video model. One sentence tells the model the whole video's art style, matched to the first frame or reference image. With `action` it keeps a stylised woman stylised: without it the photoreal reference clip pulls her toward realistic. Same model and price. Omit it for the model's natural look."},"video_url":{"type":"string","description":"One of your own finished clips (the `video_url` of a succeeded task; uploads and external videos are rejected). On `spicy-motion-2` it continues the clip: it takes the place of `image_url`, must be 2 to 10 seconds long, `duration` is the whole output including the clip (so set it longer than the clip) and you are billed for the whole `duration`. On `spicy-motion-3` and `spicy-motion-3-fast` it is a reference clip to extend or edit: at most 15 seconds, clip plus `duration` cannot exceed 30 seconds, and it cannot be combined with `image_url` (on those models a first frame pairs only with `last_frame_url`); with `duration: -1` the model fills what is left of the 30. Other models reject it. `inputs.continuation_clip` and `inputs.reference_video` say which; see Input combinations above."},"audio_url":{"type":"string","description":"A clip you uploaded with `POST /v1/audio` or generated with `POST /v1/audio/speech` (its `url`, byte-for-byte), 2 to 30 seconds long. What it does depends on the model's `inputs.audio.mode`: driving audio (`spicy-motion-2`, `spicy-video-1`) is the soundtrack and the mouth follows it, one clip, alongside `image_url` where the model takes one; reference audio (`spicy-motion-3`, `spicy-motion-3-fast`) guides voice, tone and beat while the model generates the sound, and never goes with `image_url` (a first frame pairs only with `last_frame_url` there). Rejected on `spicy-motion-1` and `spicy-character-video-1`. See Input combinations above."},"audio_urls":{"type":"array","items":{"type":"string"},"description":"Several of your clips, up to the model's `inputs.audio.max_clips` (5 on `spicy-motion-3` and `spicy-motion-3-fast`, 15 seconds total); other models take a single `audio_url`. See Input combinations above."},"audio_mode":{"type":"string","description":"Optional guard, `driving` or `reference`: what you expect the audio to do. It must equal the model's `inputs.audio.mode`, otherwise a 400 names a model that offers the requested mode (driving: `spicy-motion-2` or `spicy-video-1`; reference: `spicy-motion-3` or `spicy-motion-3-fast`). When omitted the model's mode applies."},"resolution":{"type":"string","description":"One of the model's `limits.resolutions` from `GET /v1/models`: `720P` or `1080P` on the Motion 2 generation and `spicy-motion-draft-1`; `480P`, `720P` or `1080P` on `spicy-motion-3`, `spicy-motion-3-fast` and the Cinema models. Defaults to `720P`."},"aspect_ratio":{"type":"string","description":"Text-to-video only. One of the model's `limits.aspectRatios`: `16:9`, `9:16`, `1:1`, `4:3`, `3:4` on `spicy-video-1` (default `16:9`); those plus `21:9` on `spicy-motion-3` and `spicy-motion-3-fast`, where omitting it lets the model pick a shape to suit the prompt; those plus `4:5`, `5:4`, `9:21` and `21:9` on `spicy-cinema-1` and `spicy-cinema-1-character`. Not accepted with `image_url` or `video_url`: the output follows the source. With `action` any of the model's ratios may be requested; the default is the action's own `aspect_ratio`."},"duration":{"type":"integer","description":"Seconds, between the model's `minDuration` and `maxDuration` (2 to 10 on `spicy-motion-1`, 2 to 15 on the rest of the Motion 2 generation and `spicy-motion-draft-1`, 3 to 15 on the Cinema models, 2 to 30 on `spicy-motion-3` and `spicy-motion-3-fast`). Defaults to 5, or 8 with `action` (the action's 8 seconds plus `duration` cannot exceed 30). On `spicy-motion-3` and `spicy-motion-3-fast`, `-1` lets the model choose the length: you are debited for the maximum (30 seconds, less any `video_url` clip) and the difference is refunded when the task finishes, with the task's `cost_usd` settling to the rendered length shown in `usage.output_seconds`."},"negative_prompt":{"type":"string","description":"What to avoid. Accepted and ignored on `spicy-motion-3`, `spicy-motion-3-fast` and the Cinema models, which have no negative prompt."},"enhance_prompt":{"type":"boolean","description":"Let the model expand the prompt with lighting, camera and detail cues before generating. Defaults to false: the prompt is sent exactly as written."},"audio":{"type":"boolean","description":"Generate audio where the model supports it. Ignored on `spicy-motion-draft-1` (always silent) and the Cinema models (native audio always on)."},"fps":{"type":"integer","description":"`30` (default) or `60`, on every video model. `60` frame-interpolates the finished clip to 60 fps for smoother motion, keeping the audio, for 20% of the video's own price (input and action clip seconds included), added to the task's `cost_usd`. The task takes about a minute longer. If the 60 fps step fails, the 30 fps clip is delivered and that fee refunded; the finished task's `usage.fps` says which. Other values are a 400, as is `60` at 1080P with `aspect_ratio` `21:9` or `9:21` (over the 1920x1080 pixel limit for 60 fps; use 720P); 503 when 60 fps is unavailable."},"seed":{"type":"integer","description":"Reproducibility seed."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model","prompt"]}}}}}},"/v1/videos/tasks/{id}":{"get":{"operationId":"getVideoTask","summary":"Get Video Task","description":"Check a video task and collect the finished clip. Poll every 10 to 15 seconds. When `status` is `succeeded`, `output.video_url` holds a durable CDN URL. Tasks from `POST /v1/videos/edits` (edits and motion transfer) are polled here too.","tags":["Video"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The task id."},"status":{"type":"string","description":"`queued`, `processing`, `finalizing`, `succeeded`, or `failed`."},"output":{"anyOf":[{"type":"object","properties":{"video_url":{"type":"string","description":"Durable CDN URL of the clip."}},"required":["video_url"]},{"type":"null"}],"description":"Present once finished."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Failure reason, if any. Failed tasks are refunded."},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`blocked` when the model declined the content; otherwise null."},"cost_usd":{"type":"number","description":"What the task cost. Equal to the amount debited at creation, except for `duration: -1` tasks, which settle to the rendered length once succeeded (the difference is refunded)."},"usage":{"anyOf":[{"type":"object","properties":{"output_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Length of the finished clip in seconds (what a smart-duration task is billed on)."},"input_seconds":{"type":"number","description":"Seconds of the input clip that were billed, on models that bill input video (Motion 3 with a `video_url` or an `action`, and video edits)."},"resolution":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`480P`, `720P` or `1080P`."},"ratio":{"type":"string","description":"The `aspect_ratio` requested, on text-to-video tasks that set one."},"fps":{"type":"integer","description":"On tasks created with `fps: 60`: `60` when the clip was interpolated, `30` when that step failed and its fee was refunded."}},"required":["output_seconds","resolution"]},{"type":"null"}],"description":"What was rendered. Null until the task succeeds."}},"required":["id","status","cost_usd"]},"example":{"id":"task_5d9e2f10","object":"task","type":"video","model":"spicy-motion-3","status":"succeeded","output":{"video_url":"https://cdn.spicyapi.com/outputs/a1b2/task_5d9e2f10.mp4"},"error":null,"error_code":null,"cost_usd":3.2,"usage":{"output_seconds":8,"input_seconds":8,"resolution":"720P"}}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Task id from POST /v1/videos/generations."}]}},"/v1/videos/edits":{"post":{"operationId":"createVideoEdit","summary":"Edit Video","description":"Edit a clip you generated from a prompt (outfit, style, lighting, props), or transfer a clip's motion onto the person in one of your images. Returns a task to poll. Instruction edits. `spicy-video-edit-1` ($0.20/s 720P, $0.30/s 1080P) takes 2 to 10 second clips, up to 4 reference images and an optional `aspect_ratio`; the output keeps the clip's length. `spicy-cinema-1-edit` ($0.28/s 720P, $0.48/s 1080P) takes 3 to 30 second clips and up to 5 reference images, and edits and returns the first 15 seconds. Both bill the input clip's seconds plus the output's: debited up front at the upper bound and settled to the seconds actually processed when the task finishes (the difference is refunded). Motion transfer. `spicy-animate-1` ($0.24/s standard, $0.36/s pro, per output second) makes the person in `image_url` perform the moves and expressions of a motion clip: pick one from `GET /v1/videos/motions` with `motion`, or pass one of your own clips of 2 to 30 seconds as `video_url`. The output is as long as the motion clip. Full-body images in the clip's framing work best. This model screens its inputs and outputs itself and refuses explicit or nude images and clips (the task fails with `error_code: \"blocked\"` and is refunded), so use a dressed performer. Every input must come from your own account: `video_url` is the `output.video_url` of one of your finished tasks, images are from `GET /v1/images`. The prompt is screened like any other before anything is charged. Poll `GET /v1/videos/tasks/{id}` (the task's `usage.input_seconds` shows billed input seconds) or receive the same webhooks as video generation. `GET /v1/models` describes each model under `video_edit`.","tags":["Video"],"security":[{"apiKey":[]}],"responses":{"202":{"description":"A task you poll with `GET /v1/videos/tasks/{id}`, the same shape as a video generation task.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Task id."},"object":{"type":"string","description":"Always `task`."},"type":{"type":"string","description":"Always `video`."},"model":{"type":"string","description":"The model doing the edit."},"status":{"type":"string","description":"`queued` at first."},"cost_usd":{"type":"number","description":"What was debited now: the upper bound for the clip. Settled to the processed seconds when the task finishes."},"poll_url":{"type":"string","description":"`/v1/videos/tasks/{id}`, relative to the base URL."}},"required":["id","object","type","model","status","cost_usd","poll_url"]},"example":{"id":"sj_7e1d4a9c2b6f8e0a3d5c7b9f","object":"task","type":"video","model":"spicy-video-edit-1","status":"queued","output":null,"error":null,"cost_usd":2,"poll_url":"/v1/videos/tasks/sj_7e1d4a9c2b6f8e0a3d5c7b9f"}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"`spicy-video-edit-1`, `spicy-cinema-1-edit` or `spicy-animate-1`."},"video_url":{"type":"string","description":"The clip: one of your own finished video outputs. Required on the instruction-edit models (2 to 10 seconds on `spicy-video-edit-1`, 3 to 30 on `spicy-cinema-1-edit`). On `spicy-animate-1`, the motion clip (2 to 30 seconds), or send `motion` instead."},"prompt":{"type":"string","description":"Required on the instruction-edit models: the change to make, up to 5,000 characters. Refer to reference images as Image 1, Image 2. Not used by `spicy-animate-1`."},"reference_image_urls":{"type":"array","items":{"type":"string"},"description":"Instruction edits: outfit, prop or style images from your own library, up to 4 on `spicy-video-edit-1` and 5 on `spicy-cinema-1-edit`. Not accepted on `spicy-animate-1`."},"resolution":{"type":"string","description":"Instruction edits: `720P` (default) or `1080P`."},"aspect_ratio":{"type":"string","description":"`spicy-video-edit-1` only: reshape the output to `16:9`, `9:16`, `1:1`, `4:3` or `3:4`. Omit to keep the clip's shape."},"keep_audio":{"type":"boolean","description":"Instruction edits: `true` keeps the clip's original soundtrack."},"negative_prompt":{"type":"string","description":"Instruction edits: what to avoid."},"seed":{"type":"integer","description":"Instruction edits: reproducibility seed."},"enhance_prompt":{"type":"boolean","description":"`spicy-video-edit-1` only: let the model expand the prompt before editing."},"image_url":{"type":"string","description":"`spicy-animate-1` only, required: the person who performs the motion, one of your generated images."},"motion":{"type":"string","description":"`spicy-animate-1` only: a motion id from `GET /v1/videos/motions` (`mo_slow_sway`, `mo_hip_dance`, `mo_hair_turn`, `mo_catwalk`, `mo_wave`), instead of `video_url`."},"quality":{"type":"string","description":"`spicy-animate-1` only: `standard` (default) or `pro`, priced separately."},"dry_run":{"type":"boolean","description":"When `true`, validates and screens the request and returns `{ object: \"dry_run\", cost_usd, moderation }` without generating or charging. A blocked prompt still returns the normal 422."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model"]}}}}}},"/v1/videos/motions":{"get":{"operationId":"listMotions","summary":"List Motions","description":"The curated motion clips for motion transfer on `spicy-animate-1`. No key needed. Every clip was generated with SpicyAPI video models (no real performer footage): full body, fixed camera, plain studio background, 9:16. Match your performer image to the clip's framing for the best result, then pass the `id` as `motion` on `POST /v1/videos/edits`.","tags":["Video"],"security":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `motion`, `mo_...`."},"object":{"type":"string","description":"Always `motion`."},"label":{"type":"string","description":"Display name."},"description":{"type":"string","description":"What the performer does."},"url":{"type":"string","description":"Preview of the clip."},"duration_s":{"type":"number","description":"Length, which is also the output length."},"aspect_ratio":{"type":"string","description":"Shape of the clip, `9:16`."}},"required":["id","object","label","description","url","duration_s","aspect_ratio"]},"description":"The library."}},"required":["data"]},"example":{"object":"list","data":[{"object":"motion","id":"mo_slow_sway","label":"Slow sway","description":"A slow, sensual side-to-side sway with the arms loose.","url":"https://cdn.spicyapi.com/examples/motions/mo_slow_sway.mp4","duration_s":6,"aspect_ratio":"9:16"},{"object":"motion","id":"mo_hip_dance","label":"Hip dance","description":"An upbeat dance led by the hips, arms moving with the beat.","url":"https://cdn.spicyapi.com/examples/motions/mo_hip_dance.mp4?v=2","duration_s":6,"aspect_ratio":"9:16"}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/videos/actions":{"get":{"operationId":"listActions","summary":"List Actions","description":"The ready-made sex acts for `action` on `spicy-motion-3` and `spicy-motion-3-fast`. No key needed. 64 reference clips, 4 seconds each (5 on two actions), in the categories sex, anal, oral, handjob, cumshot and solo. Every clip was generated with SpicyAPI video models (no real performer footage). Pass the `id` as `action` on `POST /v1/videos/generations` together with an `image_url` of the woman: the clip supplies camera angle, framing, pose and motion, your image supplies who she is, and an optional `prompt` sets the scene. The clip's seconds (`duration_s`) are billed as input seconds on top of the output seconds at the model's per-second rate; the output defaults to 8 seconds. `preview_url` is the full 8 second act for showing it, `poster_url` a still for pickers. Locked shape: sandbox keys, accounts still owing their regional age check, and calls without a key from age-gated regions (AWS us-east-1 and us-east-2 included: without a key, the caller's address decides) get blurred stills and no clips, marked `restricted: \"explicit_media\"` with a `note`. Send your API key to get the library for your account's region. Here that means `url` and `preview_url` are null and `poster_url` is blurred.","tags":["Video"],"security":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `action`."},"object":{"type":"string","description":"Always `action`."},"label":{"type":"string","description":"Display name."},"category":{"type":"string","description":"`sex`, `anal`, `oral`, `handjob`, `cumshot` or `solo`."},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preview of the reference clip. Null on the locked shape."},"poster_url":{"type":"string","description":"A still of the clip, blurred on the locked shape."},"duration_s":{"type":"number","description":"Length of the reference clip (`url`), billed as input seconds. The output defaults to 8 seconds."},"preview_url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The full 8 second act, for showing the action. Never sent upstream or billed. Null on the locked shape."},"aspect_ratio":{"type":"string","description":"Shape of the clip (`3:4`, `9:16` or `1:1`), the default output shape."}},"required":["id","object","label","category","url","poster_url","duration_s","preview_url","aspect_ratio"]},"description":"The library."},"restricted":{"type":"string","description":"Only on the locked shape: `explicit_media`."},"note":{"type":"string","description":"Only on the locked shape: why, and how to get the full library."}},"required":["data"]},"example":{"object":"list","data":[{"object":"action","id":"pov-missionary","label":"POV missionary","category":"sex","url":"https://cdn.spicyapi.com/actions/pov-missionary-ref4-cccec46a.mp4","preview_url":"https://cdn.spicyapi.com/actions/pov-missionary-88bb7889.mp4","poster_url":"https://cdn.spicyapi.com/actions/pov-missionary-3a8b5ca1.webp","duration_s":4,"aspect_ratio":"3:4"},{"object":"action","id":"pov-missionary-legs-wide","label":"POV missionary, legs wide","category":"sex","url":"https://cdn.spicyapi.com/actions/pov-missionary-legs-wide-ref4-88d9b0e7.mp4","preview_url":"https://cdn.spicyapi.com/actions/pov-missionary-legs-wide-b2020f7c.mp4","poster_url":"https://cdn.spicyapi.com/actions/pov-missionary-legs-wide-43aa1d53.webp","duration_s":4,"aspect_ratio":"9:16"}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/images/actions":{"get":{"operationId":"listImageActions","summary":"List Image Actions","description":"The ready-made explicit stills for `action` on `spicy-image-action-1`. No key needed. 68 stills in the categories sex, anal, oral, handjob, cumshot and solo; most share their id with a video action (`GET /v1/videos/actions`). Every still was generated with AI (no real performers). Pass the `id` as `action` on `POST /v1/images/generations` with `model: \"spicy-image-action-1\"` and an `image_url` of the woman: the still supplies the act, pose, framing, room and her body, your image her face, hair and skin tone. Billed per image at $0.15. The output keeps the still's shape at about 2 megapixels (`size`). Locked shape: sandbox keys, accounts still owing their regional age check, and calls without a key from age-gated regions (AWS us-east-1 and us-east-2 included: without a key, the caller's address decides) get blurred stills and no clips, marked `restricted: \"explicit_media\"` with a `note`. Send your API key to get the library for your account's region. Here that means `image_url`, `preview_url` and every style's URLs are blurred copies.","tags":["Images"],"security":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"What you pass as `action`."},"object":{"type":"string","description":"Always `image_action`."},"label":{"type":"string","description":"Display name."},"category":{"type":"string","description":"`sex`, `anal`, `oral`, `handjob`, `cumshot` or `solo`."},"image_url":{"type":"string","description":"The still."},"preview_url":{"type":"string","description":"A small copy for pickers."},"size":{"type":"string","description":"The output `width*height`."},"styles":{"type":"object","description":"The still redrawn per art style, keyed `anime`, `3d`, `cartoon`, each with `image_url` and `preview_url`. Pass the key as `style`; a missing key means that version is not offered for this action."}},"required":["id","object","label","category","image_url","preview_url","size","styles"]},"description":"The library."},"restricted":{"type":"string","description":"Only on the locked shape: `explicit_media`."},"note":{"type":"string","description":"Only on the locked shape: why, and how to get the full library."}},"required":["data"]},"example":{"object":"list","data":[{"object":"image_action","id":"pov-missionary","label":"POV missionary","category":"sex","image_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-7f462a19.webp","preview_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-thumb-5d29bb67.webp","size":"1248*1664","styles":{}},{"object":"image_action","id":"pov-missionary-legs-wide","label":"POV missionary, legs wide","category":"sex","image_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-f34caa35.webp","preview_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-thumb-67c45f2b.webp","size":"1088*1920","styles":{"anime":{"image_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-anime-559dfdeb.webp","preview_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-anime-thumb-aa680dc4.webp"},"3d":{"image_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-3d-983a68d9.webp","preview_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-3d-thumb-a8392c00.webp"},"cartoon":{"image_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-cartoon-9236560c.webp","preview_url":"https://cdn.spicyapi.com/image-actions/pov-missionary-legs-wide-cartoon-thumb-766afe90.webp"}}}]}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}}}},"/v1/chat/completions":{"post":{"operationId":"chatCompletions","summary":"Create Chat Completion","description":"OpenAI-compatible uncensored chat and role-play, with streaming. Drop-in compatible with the OpenAI SDK: point `baseURL` at `https://api.spicyapi.com/v1` and pass your SpicyAPI key. Billed per 1M prompt tokens and per 1M completion tokens, at the model's rates. Models. `spicy-chat-1` ($0.80 in, $2.40 out per 1M tokens) is the general uncensored chat model. `spicy-companion-1` ($1.00 in, $2.80 out per 1M tokens) is fine-tuned for role-play and AI companions: put the persona in the system message and it stays in character, writes explicit adult scenes, handles group scenes with several characters, and returns up to 4 alternative replies with `n`. It is the recommended model for explicit role-play, which `spicy-chat-1` may decline. `spicy-companion-1-flash` ($0.10 in, $0.80 out per 1M tokens) is the same role-play behaviour priced for high-volume traffic such as NPCs. All three take the same request body. Text only, with caps: input across all messages up to about 200,000 tokens on `spicy-chat-1`, 131,072 on `spicy-companion-1` and at least 144,000 (tested) on `spicy-companion-1-flash`, `max_tokens` up to 8192 (default 4096), `n` up to 4. Requests that set `enable_thinking`, `thinking_budget`, `enable_search` or `search_options`, ask for `response_format.type: \"json_schema\"`, or carry non-text content parts (images, audio) are rejected with a 400 before any charge. Unknown fields are dropped silently. Function calling: `tools`, `tool_choice` and `parallel_tool_calls` work as in the OpenAI API on `spicy-chat-1` and `spicy-companion-1` (not on `spicy-companion-1-flash`, which returns a 400). The reply carries `tool_calls`; send the result back as a `tool` message with its `tool_call_id`. Tool definitions and earlier tool calls are prompt tokens, billed at the model's input price. Billing: the balance (and any monthly spend limit) must cover the worst case before the call: the estimated prompt plus every completion at `max_tokens`, else a 402. You are then charged for the tokens actually used. Prompt tokens the provider serves from its cache (`usage.prompt_tokens_details.cached_tokens`, a repeated prefix of 1,024 tokens or more) bill at the model's cached input price (`pricing.usd_cached_input` in `GET /v1/models`): $0.16 per 1M on `spicy-chat-1`, $0.20 on `spicy-companion-1` and $0.02 on `spicy-companion-1-flash`, a fifth of their input prices. Safety signal: self-harm is not a blocked category. When the screen reads a risk of self-harm in the conversation, the response (streamed or not) carries the header `x-spicyapi-safety: self_harm` so your app can show crisis resources (in the US 988, in the UK and Ireland Samaritans on 116 123, elsewhere findahelpline.com). The header is absent otherwise. Companion laws: New York (GBL Art 47) and California (SB 243) ask companion apps to tell users they are talking to an AI (in New York at the start of each session and at least every 3 hours) and to refer users at risk to crisis services. If your app offers companions, send `safety_mode: \"companion\"` to have SpicyAPI add both to the reply text.","tags":["Chat"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Completion id."},"choices":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","description":"Position in the list."},"message":{"type":"object","properties":{"role":{"type":"string","description":"Always `assistant`."},"content":{"type":"string","description":"Reply text."}},"required":["role","content"],"description":"The reply."},"finish_reason":{"type":"string","description":"Why generation stopped."}},"required":["index","message"]},"description":"Generated replies."},"usage":{"type":"object","properties":{"prompt_tokens":{"type":"integer","description":"Tokens in the request."},"completion_tokens":{"type":"integer","description":"Tokens generated."},"total_tokens":{"type":"integer","description":"Sum of the two. Prompt and completion tokens are billed at their own rates."}},"required":["prompt_tokens","completion_tokens","total_tokens"],"description":"Token counts."},"cost_usd":{"type":"number","description":"What this request cost, in US dollars, already debited from your balance."}},"required":["id","choices","cost_usd"]},"example":{"id":"chatcmpl_7b3f","object":"chat.completion","model":"spicy-chat-1","choices":[{"index":0,"message":{"role":"assistant","content":"<string>"},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":84,"total_tokens":96},"cost_usd":0.001}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A chat model id: `spicy-chat-1`, `spicy-companion-1` or `spicy-companion-1-flash`."},"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","description":"`system`, `user`, or `assistant`."},"content":{"type":"string","description":"Message text. An array of `{ type: \"text\", text }` parts is also accepted; any other part type is a 400."}},"required":["role","content"]},"description":"The conversation so far. The whole array, system prompt included, counts toward the 200,000-token input cap and is screened together. On the companion models the system message is the persona."},"temperature":{"type":"number","description":"Sampling temperature, 0 to 2."},"max_tokens":{"type":"integer","description":"Cap on generated tokens. Defaults to 4096; at most 8192."},"n":{"type":"integer","description":"How many replies to generate, 1 to 4. Every reply's tokens are billed."},"stream":{"type":"boolean","description":"Stream back server-sent events. Usage arrives in the final chunk."},"top_p":{"type":"number","description":"Nucleus sampling threshold. Forwarded as is."},"top_k":{"type":"integer","description":"Sample from the k most likely tokens. Forwarded as is."},"presence_penalty":{"type":"number","description":"Penalise tokens already present in the text. Forwarded as is."},"frequency_penalty":{"type":"number","description":"Penalise tokens by how often they have appeared. Forwarded as is."},"repetition_penalty":{"type":"number","description":"Multiplicative repetition penalty (1.0 is off). Forwarded as is."},"seed":{"type":"integer","description":"Best-effort reproducibility seed."},"stop":{"type":"array","items":{"type":"string"},"description":"Stop sequence or sequences."},"logit_bias":{"type":"object","description":"Token id to bias map, as in the OpenAI API."},"response_format":{"type":"object","description":"`{ \"type\": \"text\" }` (default) or `{ \"type\": \"json_object\" }`. For JSON mode the word \"JSON\" must appear in the system or user prompt, or the model returns an error; `json_schema` is not available."},"tools":{"type":"array","items":{"type":"object"},"description":"`spicy-chat-1` and `spicy-companion-1` only: up to 64 functions, each `{ \"type\": \"function\", \"function\": { \"name\", \"description\", \"parameters\" } }` (JSON Schema parameters), as in the OpenAI API."},"tool_choice":{"type":"object","description":"`auto` (default with `tools`), `none`, `required`, or `{ \"type\": \"function\", \"function\": { \"name\" } }` to force one function."},"parallel_tool_calls":{"type":"boolean","description":"Whether the model may call several functions in one reply."},"safety_mode":{"type":"string","description":"Off unless set. `companion` puts a bracketed line in front of every reply choice (streamed or not, never billed): an AI notice (\"You are talking to an AI, not a person...\") until an `assistant` message in the conversation carries it (keep it in the history; a scripted greeting does not count) and whenever this `user` last got it 3 or more hours ago, and crisis resources on any turn flagged `self_harm`. Send `user` so the 3-hour clock runs per person; without it the clock is per account. Any other value is a 400."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":["model","messages"]}}}}}},"/v1/embeddings":{"post":{"operationId":"createEmbeddings","summary":"Create Embeddings","description":"Turn text, or images from your own library, into vectors for memory, search, recommendations and near-duplicate detection. OpenAI-compatible. `spicy-embed-1` embeds text ($0.14 per 1M tokens): companion memory, prompt search, recommendations. 1024 dimensions by default, 64 to 2048 with `dimensions`. Works with the OpenAI SDK's `client.embeddings.create`. `spicy-embed-vision-1` puts images and text in one 768-dimension space (text $0.18, images $0.06 per 1M tokens): \"more like this\" search over your generations, tagging, dedupe. Every image must be an output of your own account (the same provenance rule as every image input); a text query and an image land in the same space, so you can search images with words. Up to 10 inputs per request, each up to 8,192 tokens. Billed on the tokens used (minimum $0.00001 per request). Nothing is generated, so inputs are not screened. Sandbox keys get deterministic fixture vectors and pay nothing.","tags":["Embeddings"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","description":"Always `list`."},"model":{"type":"string","description":"The model used."},"data":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string","description":"Always `embedding`."},"index":{"type":"integer","description":"Position of the input."},"embedding":{"type":"array","items":{"type":"object"},"description":"The vector, or a base64 string with `encoding_format: \"base64\"`."}},"required":["object","index","embedding"]},"description":"One vector per input, in order."},"usage":{"type":"object","description":"Token counts, as in the OpenAI API."},"cost_usd":{"type":"number","description":"What this request cost, in US dollars, already debited from your balance."}},"required":["object","model","data","usage","cost_usd"]},"example":{"object":"list","model":"spicy-embed-1","data":[{"object":"embedding","index":0,"embedding":[0.0213,-0.0447,0.0132,"..."]},{"object":"embedding","index":1,"embedding":[-0.0081,0.0356,0.0274,"..."]}],"usage":{"prompt_tokens":22,"total_tokens":22},"cost_usd":0.00001}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"`spicy-embed-1` or `spicy-embed-vision-1`."},"input":{"type":"string","description":"A string or an array of up to 10 items. On `spicy-embed-1` every item is a string. On `spicy-embed-vision-1` an item is a string, `{ \"text\": \"...\" }` or `{ \"image\": \"https://...\" }` where the image is one of your generated images."},"dimensions":{"type":"integer","description":"`spicy-embed-1` only: `64`, `128`, `256`, `512`, `768`, `1024` (default), `1536` or `2048`. `spicy-embed-vision-1` always returns 768 and rejects it."},"encoding_format":{"type":"string","description":"`float` (default) or `base64` (little-endian float32)."}},"required":["model","input"]}}}}}},"/v1/realtime/sessions":{"post":{"operationId":"createRealtimeSession","summary":"Create Live Call Session","description":"Start a live voice call with a companion: returns a one-time WebSocket URL your client (a browser too) connects to, without ever seeing your API key. `spicy-live-1`: the caller talks, the persona answers in a natural voice, and interruptions are handled, over one WebSocket (`wss://api.spicyapi.com/v1/realtime`, see Live Call WebSocket). Set the persona and voice here; the call itself follows the OpenAI Realtime event shape. `spicy-live-2` is the highest-quality live call: the role-play model `spicy-companion-1` writes every reply (it stays in the scene and moves it forward), spoken by Spicy Voice 3 voices that act the emotion (`Bella Bright`, `Roger`, `Sarah`, `Laura`, `Charlie`, `George`, `Callum`, `River`, `Harry`, `Liam`, `Alice`, `Matilda`, `Will`, `Jessica`, `Eric Smooth`, `Chris`, `Brian`, `Daniel`, `Lily`, `Adam`, `Bill`, default `Sarah`), with turn-taking and interruptions handled for you. Same WebSocket events and audio formats as `spicy-live-1`. It takes `language` (`en`, `zh`, `de`, `it`, `pt`, `es`, `ja`, `ko`, `fr`, `ru`, default English) and no `turn_detection`. Billed at $0.08 per minute of call time (by the second) plus the reply tokens at $1.00 in, $2.80 out per 1M tokens; each charge arrives as a `spicy.usage` event. Opening a session needs a balance of at least $0.10. The ticket in `client_secret.value` works once, within 60 seconds. Create the session on your server with your key and hand only the returned `url` to the browser. A call lasts up to 780 seconds per session; open a new session to continue. Billed per turn from the tokens each reply used, per 1M tokens: text in $0.46, audio in $1.86, text out $1.40, audio out $3.74. Audio counts about 7 tokens per second in and 12.5 out, and the conversation so far is billed again as input on each turn, which comes to about 1 to 1.5 cents per minute of steady back-and-forth (measured on real calls, Oct 2026; less when the line is quiet). Opening a session needs a balance of at least $0.05; when the balance or your spend limit runs out mid-call, the call ends with a `payment_required` error event. The persona in `instructions` is screened like a prompt before the session is created (422 when blocked). During the call both sides' transcripts are screened; a violation ends the call with a `content_blocked` error event. No custom or cloned voices, no camera or image input, no tools or web search. Sandbox keys get a session whose socket answers with canned events and bills nothing. Calls appear in the request log as type `realtime` with seconds, turns and total cost.","tags":["Live calls"],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Session","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Session id, `rt_...`."},"object":{"type":"string","description":"Always `realtime.session`."},"model":{"type":"string","description":"The model."},"voice":{"type":"string","description":"The voice that answers."},"turn_detection":{"anyOf":[{"type":"object"},{"type":"null"}],"description":"The turn detection in effect."},"max_seconds":{"type":"integer","description":"Longest call on this session."},"input_audio":{"type":"string","description":"What to send: PCM16 mono 16 kHz, base64, in `input_audio_buffer.append`."},"output_audio":{"type":"string","description":"What comes back: PCM16 mono 24 kHz, base64, in `response.audio.delta`."},"client_secret":{"type":"object","properties":{"value":{"type":"string","description":"`rts_...`, valid once."},"expires_at":{"type":"integer","description":"Unix timestamp, 60 seconds after creation."}},"required":["value","expires_at"],"description":"The one-time ticket."},"url":{"type":"string","description":"The WebSocket URL with the ticket, ready to open."}},"required":["id","object","model","voice","turn_detection","max_seconds","input_audio","output_audio","client_secret","url"]},"example":{"id":"rt_5c2e8a1f9d3b7e4a","object":"realtime.session","model":"spicy-live-1","voice":"Serena","turn_detection":{"type":"server_vad","silence_duration_ms":600},"max_seconds":780,"input_audio":"pcm16 mono 16 kHz, base64 in input_audio_buffer.append","output_audio":"pcm16 mono 24 kHz, base64 in response.audio.delta","client_secret":{"value":"rts_9f1b3d5e7a2c4e6f8a0b","expires_at":1758553260},"url":"wss://api.spicyapi.com/v1/realtime?session=rts_9f1b3d5e7a2c4e6f8a0b"}}}},"400":{"description":"Invalid parameters. Body: {error: {message, type: 'invalid_request_error'}}."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance. Top up in the dashboard or through the get_topup_link tool; nothing was charged."},"422":{"description":"Prompt blocked by moderation (acceptable use policy). Never charged."},"429":{"description":"Rate limited or model at capacity; retry after the Retry-After header."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"`spicy-live-1` (the default) or `spicy-live-2` (highest quality)."},"voice":{"type":"string","description":"The voice that answers (case-insensitive), default `Tina`: `Tina`, `Cindy`, `Liora Mira`, `Serena`, `Maia`, `Mia`, `Katerina`, `Jennifer`, `Sonrisa`, `Hana`, `Griet`, `Sigga`, `Bea`, `Chloe`, `Kiki`, `Sohee`, `Zane`, `Ryan`, `Raymond`, `Theo Calm`, `Aiden`, `Andre`, `Dolce`, `Bodega`, `Jakub`, `Alek`, `Emilien`, `Evan`."},"instructions":{"type":"string","description":"The persona and how to behave on the call, up to 8,000 characters. Defaults to a warm, playful adult companion who keeps replies short."},"language":{"type":"string","description":"`spicy-live-2` only: the language of the call, a two-letter code (`en`, `zh`, `de`, `it`, `pt`, `es`, `ja`, `ko`, `fr`, `ru`). Defaults to English. A 400 on `spicy-live-1`, which detects the language itself."},"turn_detection":{"anyOf":[{"type":"object"},{"type":"null"}],"description":"`spicy-live-1` only (a 400 on `spicy-live-2`, which takes turns by itself). When the persona replies. Default `{ \"type\": \"server_vad\" }`: it answers when the caller stops talking. `semantic_vad` waits for a finished thought. Optional `threshold` (-1 to 1) and `silence_duration_ms` (200 to 6000). `null` is push-to-talk: send `input_audio_buffer.commit` and `response.create` yourself."},"user":{"type":"string","description":"Your own id for the end user making this request (up to 128 characters; send an opaque id, not an email). Send it if your product serves many people: declined-prompt history, strikes and suspensions are then kept per end user, so one person's behaviour never affects another's requests or your account. After 10 severe violations that user gets `403 end_user_suspended`; the id is echoed back as `user` in every screening error so you can act on it. It is stored with the requests it was sent on, so `DELETE /v1/end-users/{user}` can erase that person's data."}},"required":[]}}}}}},"/v1/realtime":{"get":{"operationId":"realtimeWebsocket","summary":"Live Call WebSocket","description":"The call itself: a WebSocket (wss://) opened with the session ticket. Send microphone audio, receive the persona's voice and transcripts as JSON events. Open `wss://api.spicyapi.com/v1/realtime?session=<ticket>`, the `url` from `POST /v1/realtime/sessions`, within 60 seconds of creating it. No `Authorization` header: the ticket is the credential, so a browser can connect directly. An unknown, used or expired ticket is a 401 before the upgrade. Events follow the OpenAI Realtime API shape. Send JSON text frames (up to 256 KB). Accepted client events: `input_audio_buffer.append` (`audio`: base64 PCM16 mono 16 kHz, about 100 ms per chunk), `input_audio_buffer.commit`, `input_audio_buffer.clear`, `response.create`, `response.cancel`, `conversation.item.create` (a user message with `input_text` parts only, screened before it is sent), and `session.update` with only `session.turn_detection`. Persona and voice are fixed by the session. Anything else returns an `error` event. On `spicy-live-2` turn-taking is automatic: `input_audio_buffer.commit`, `.clear`, `response.create` and `response.cancel` are accepted and ignored, and `session.update` returns `unsupported_field`. Server events: `session.created`, `session.updated`, `input_audio_buffer.speech_started`, `input_audio_buffer.speech_stopped`, `input_audio_buffer.committed`, `conversation.item.input_audio_transcription.delta` and `.completed` (what the caller said), `response.created`, `response.audio.delta` (`delta`: base64 PCM16 mono 24 kHz), `response.audio_transcript.delta` and `.done` (what the persona said), `response.done` and `error`. SpicyAPI adds `spicy.usage` (`{ turn, cost_usd, total_cost_usd }`) after each billed turn and `spicy.session_expired` when the call reaches its time limit, after which the socket closes. Error events carry `error.code`: `payment_required` (balance or spend limit reached; the call ends), `content_blocked` (a transcript failed screening; the call ends), `moderation_unavailable` (screening unreachable; the call ends, retry shortly), `upstream_unavailable` (the voice service failed), `unsupported_event`, `unsupported_item`, `unsupported_field`, `invalid_json` and `invalid_event` (the call continues).","tags":["Live calls"],"security":[],"responses":{"101":{"description":"The socket, carrying JSON events in both directions. The first server event is `session.created`."},"401":{"description":"Unknown, used or expired session ticket."}},"x-websocket":{"url":"wss://api.spicyapi.com/v1/realtime?session={ticket}","protocol":"OpenAI Realtime event shape, JSON text frames"},"parameters":[{"name":"session","in":"query","required":true,"description":"The ticket, `client_secret.value` from `POST /v1/realtime/sessions`.","schema":{"type":"string","description":"The ticket, `client_secret.value` from `POST /v1/realtime/sessions`."}}]}},"/api/v1/tools":{"get":{"operationId":"listTools","summary":"List every agent tool with its argument schema, whether it is guarded (needs approval_token) and whether it works with no balance.","tags":["Agent tools"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"responses":{"200":{"description":"Tool catalog","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/v1/tools/list_models":{"post":{"operationId":"tool_list_models","summary":"List models","description":"Every model with its kind (image, image-edit, video, video-edit, chat, speech, realtime, transcription, embedding), USD price (per image, per second by resolution or quality, per 1M tokens, per 10,000 characters of speech input, or per minute of audio), limits (image sizes, outputs per call, resolutions, clip lengths, aspect ratios, prompt character caps), whether it needs or accepts an input image or references, and example clips. Video rows carry `inputs`: which of first frame, last frame, reference images, clip and audio the model takes, `inputs.audio.mode` (driving: the clip is the soundtrack and the mouth follows it; reference: the model generates sound and uses the clip for voice, tone and beat; none) and `inputs.combinations`, the valid input combinations in plain sentences. Read those before generate_video. `silent: true` marks a video model that renders without sound; `bills_input_video_seconds: true` one that also bills the seconds of an input clip. Video-edit rows carry `video_edit` (instruction or motion mode, clip lengths, reference images, whether input seconds are billed) for edit_video. Speech rows carry `speech`: preset voices, whether `instructions` are accepted, custom voices, languages, inline `tags` and voice creation fees. Realtime rows (spicy-live-1) are live voice calls over a WebSocket: a server or app opens a session with POST /v1/realtime/sessions and connects the returned ticket URL; they cannot be driven from a tool call. Prices here are exactly what the API bills. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"description":"Filter by kind.","type":"string","enum":["image","image-edit","video","video-edit","chat","speech","realtime","transcription","embedding"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/estimate_cost":{"post":{"operationId":"tool_estimate_cost","summary":"Estimate cost","description":"USD cost of a request before making it, from the same price table the gateway bills: images by count, video by seconds and resolution (plus the input clip or action clip on models that bill it), video edits by clip length (input plus output seconds for instruction edits, output seconds for motion transfer), chat by tokens, speech by characters of input (plus instructions), transcription by seconds of audio, embeddings by tokens, live calls by minutes. Use it to quote the user and to check against get_account before a spend. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A model id from list_models, e.g. spicy-image-1, spicy-image-1-pro, spicy-image-photo-1."},"n":{"description":"Images per call (image models), clamped to the model's maximum.","type":"integer","minimum":1,"maximum":6},"resolution":{"description":"Video or video-edit resolution, e.g. 720P (default) or 1080P.","type":"string"},"quality":{"description":"Motion transfer tier on spicy-animate-1, default standard.","type":"string","enum":["standard","pro"]},"duration":{"description":"Video seconds, default 5; -1 for a model-chosen length where supported.","type":"integer","minimum":-1,"maximum":60},"input_seconds":{"description":"Length of the input clip in seconds: the clip to edit or motion clip on video-edit models (default 5), or a video_url on video models that bill input seconds.","type":"number","minimum":1,"maximum":60},"motion":{"description":"A library motion id (list_motions) on spicy-animate-1; its length sets the output length.","type":"string"},"action":{"description":"An action id (list_actions) on spicy-motion-3 and -fast: its reference clip seconds (4 on most) are billed as input, and duration defaults to 8.","type":"string"},"fps":{"description":"Video frame rate: 30 (default) or 60. 60 smooths motion with frame interpolation for 20% on top of the video price (input or action clip seconds included); audio is kept. If that step fails the 30 fps clip is delivered and the fee refunded.","anyOf":[{"type":"number","const":30},{"type":"number","const":60}]},"tokens":{"description":"Chat: total tokens, default 1000. Embeddings: text tokens, default 1000.","type":"integer","minimum":0,"maximum":9007199254740991},"image_tokens":{"description":"Embeddings on spicy-embed-vision-1: image tokens, default 0.","type":"integer","minimum":0,"maximum":9007199254740991},"characters":{"description":"Speech models: characters of input plus instructions (CJK ideographs and inline tags count too), default 100.","type":"integer","minimum":1,"maximum":9007199254740991},"audio_seconds":{"description":"Transcription: seconds of audio, default 60.","type":"number","minimum":1,"maximum":300},"minutes":{"description":"Live calls (spicy-live-1): minutes of conversation, default 1.","type":"number","minimum":0.1,"maximum":13},"count":{"description":"How many such requests, default 1.","type":"integer","minimum":1,"maximum":100000}},"required":["model"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/get_account":{"post":{"operationId":"tool_get_account","summary":"Get account","description":"Balance in USD, spend this calendar month, the monthly spend limit if set, whether approvals are skipped for agents, whether the account has ever topped up, the webhook URL, and whether this key is a sandbox key. Call it first and before any spend. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_images":{"post":{"operationId":"tool_list_images","summary":"List images","description":"The account's generated images, newest first. These are the only URLs accepted as image inputs by edit_image, generate_video, edit_video and create_embeddings (no uploads, no third-party URLs). Use it to pick a first frame to animate or a performer for motion transfer. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"description":"Default 20.","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_characters":{"post":{"operationId":"tool_list_characters","summary":"List characters","description":"The account's saved characters: reusable identities built from its own generated images. Pass a character id as `character` to generate_image, edit_image or generate_video to get the same person again across images, edits and video. No uploads are involved; a character can only be made from images this account generated. The exception is `imported: true` characters: a registered business can apply in the dashboard (Settings, Character import: https://www.spicyapi.com/dashboard/import) to bring its own platform's existing AI-generated characters over with POST /v1/characters/import. There is no tool for importing, and an agent cannot apply for the user. One with `status: \"under_review\"` cannot be used until a person approves it. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/create_character":{"post":{"operationId":"tool_create_character","summary":"Create character","description":"Save a reusable identity from 1 to 3 of the account's own generated images of the same person (list_images or a generate_image result). Renders one neutral reference sheet (billed as one image edit) and returns the character id to pass as `character` elsewhere. The sheet shows the face and upper body; add_character_view keeps the rest of the body the same from other angles. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"description":"A label, e.g. Mara."},"image_urls":{"minItems":1,"maxItems":3,"type":"array","items":{"type":"string","format":"uri"},"description":"Generated images of the same person, from this account."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["name","image_urls"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/add_character_view":{"post":{"operationId":"tool_add_character_view","summary":"Add character view","description":"Add one of the account's own generated images of a character's person from another angle (from behind, a pose, a close-up), with a label saying what it shows. The character's sheet only shows the face and upper body; with views, generate_image, edit_image and generate_video keep the rest of the body the same too (proportions, skin, moles, intimate anatomy), sending the views that fit each prompt (or the ones named in `views`). Free, up to 8 per character. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"A character id from list_characters."},"image_url":{"type":"string","format":"uri","description":"One of this account's own images of the same person (list_images)."},"label":{"type":"string","minLength":1,"maxLength":80,"description":"What it shows, e.g. close-up of her buttocks from behind, close-up between her legs. Close-ups of one area work best; a full-body view also pulls the pose."},"crop":{"description":"Only this box of the image becomes the view, in fractions from its top left corner (at least 256 px a side): the way to make a close-up from a full-body image.","type":"object","properties":{"x":{"type":"number","minimum":0,"maximum":1},"y":{"type":"number","minimum":0,"maximum":1},"width":{"type":"number","exclusiveMinimum":0,"maximum":1},"height":{"type":"number","exclusiveMinimum":0,"maximum":1}},"required":["x","y","width","height"],"additionalProperties":false}},"required":["id","image_url","label"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/delete_character":{"post":{"operationId":"tool_delete_character","summary":"Delete character","description":"Remove a saved character. Its reference sheet stays in the image library. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"A character id from list_characters."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/generate_image":{"post":{"operationId":"tool_generate_image","summary":"Generate image","description":"Text to image, synchronous (10 to 30 seconds), returns durable CDN URLs reusable as image_url for edits and video. Or an image action: model spicy-image-action-1 with an action from list_image_actions and image_url of the woman (one of list_images) puts her face, hair and skin tone into that ready-made explicit scene, no prompt (a minute or two); style studio gives the finished picture a warm glamour colour grade, and anime, 3d or cartoon put her into the drawn version of the scene, for a drawn woman. Or a reference: model spicy-image-reference-1 with reference_image (a picture the user supplies, https or data URL) describes it (faces left out) and draws a brand new image from that description, new people with the same look, and returns the description to reuse as a prompt (30 to 60 seconds, one image). One to six images per call, billed per image at the model's rate; the exact amount is cost_usd in the result. Prompts are screened first and outputs after (withheld images are refunded and counted in `withheld`); blocked prompts cost nothing. Every request depicts adults only. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"An image model id: spicy-image-1 (fast), spicy-image-1-pro (best prompt adherence), spicy-image-photo-1 (candid natural-light photo look), spicy-image-action-1 (image actions only) or spicy-image-reference-1 (reference_image only; businesses approved for character import)."},"prompt":{"description":"What to generate, up to 4000 characters. Required, except with action or reference_image where it is not accepted.","type":"string","minLength":1,"maxLength":4000},"reference_image":{"description":"On spicy-image-reference-1 only, and required there: the picture to recreate as new people, a public https URL or a data URL (JPEG, PNG or WebP). No prompt, size, style, character or enhance_prompt; n is 1. Pictures of anyone who may be under 18 or of known people are refused at no charge.","type":"string"},"action":{"description":"An image action id from list_image_actions, on spicy-image-action-1 only, e.g. pov-missionary: the still sets the act, pose, room and shape; image_url (or character) supplies the woman's face, hair and skin tone. No prompt, negative_prompt, size or enhance_prompt. Her body stays the still's. style studio adds a warm glamour colour grade to the result; anime, 3d or cartoon picks the drawn version of the still (see styles in list_image_actions); match it to her image.","type":"string"},"image_url":{"description":"With action: the woman, one of this account's own generated images (list_images). Photo women take no style (or studio); anime, 3D or cartoon women take the matching style.","type":"string","format":"uri"},"negative_prompt":{"type":"string","maxLength":2000},"size":{"description":"width*height from the model's limits.sizes, one price per model whatever the size: 1024*1024, 832*1216, 1216*832, 1280*1280, 1440*1440, 1024*1536, 1536*1024, 1080*1920, 1920*1080, 1152*2048, 2048*1152. Default 1024*1024.","type":"string"},"n":{"description":"Images to return, 1 to 6, default 1. Billed per image delivered.","type":"integer","minimum":1,"maximum":6},"seed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"enhance_prompt":{"description":"Let the model expand the prompt with lighting, camera and detail cues. Default false: the prompt goes as written.","type":"boolean"},"enhance_mode":{"description":"With enhance_prompt: direct (default) rewrites once, agent plans the shot then writes.","type":"string","enum":["direct","agent"]},"character":{"description":"A saved character id (list_characters): the same person is rendered in the new scene, billed at the image-edit rate.","type":"string"},"views":{"description":"With character: body view ids (cv_...) to send, or [] for none; omitted, the views that fit the prompt are picked.","maxItems":2,"type":"array","items":{"type":"string"}},"style":{"description":"Art style (list_styles): photorealistic, studio (glossy glamour), anime, 3d (animated-film CGI) or cartoon (2D adult cartoon). Prompt text only, same price.","type":"string","enum":["photorealistic","studio","anime","3d","cartoon"]},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/edit_image":{"post":{"operationId":"tool_edit_image","summary":"Edit image","description":"Prompt-driven edit of one to three images the account generated (list_images), for outfit, pose and scene changes. Synchronous; returns one to six durable CDN URLs, billed per output image (withheld outputs are refunded). Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"An image-edit model id, e.g. spicy-image-edit-1."},"prompt":{"type":"string","minLength":1,"maxLength":4000,"description":"The change to make, up to 4000 characters."},"image_urls":{"description":"Source images, all from this account's own generations. Optional when `character` is given (the character sheet is Image 1; these follow as Image 2 and 3).","maxItems":3,"type":"array","items":{"type":"string","format":"uri"}},"negative_prompt":{"type":"string","maxLength":2000},"size":{"description":"Output width*height from the model's limits (same eleven sizes as generate_image). Omit to let the model choose.","type":"string"},"n":{"description":"Variations to return, 1 to 6, default 1. Billed per image.","type":"integer","minimum":1,"maximum":6},"seed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"enhance_prompt":{"description":"Let the model expand the prompt with lighting, camera and detail cues. Default false.","type":"boolean"},"character":{"description":"A saved character id (list_characters) to keep the same person.","type":"string"},"views":{"description":"With character: body view ids (cv_...) to send, or [] for none; omitted, the views that fit the prompt are picked.","maxItems":2,"type":"array","items":{"type":"string"}},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model","prompt"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/generate_video":{"post":{"operationId":"tool_generate_video","summary":"Generate video","description":"Image to video, text to video or references to video. Asynchronous: returns a task id at once with the price already debited; poll get_job every 10 to 15 seconds until succeeded (output.video_url, usage.output_seconds) or failed (refunded). Every input must be the account's own: images from list_images, clips from its own finished tasks (video_url), audio from upload_audio or generate_speech. Price = seconds x the per-second rate for the resolution; models with bills_input_video_seconds (spicy-motion-3, -fast) also bill the seconds of a video_url clip or of an action clip. duration -1 on spicy-motion-3 lets the model pick the length, debited at the maximum and refunded down to what is rendered. Text to video: spicy-motion-3, -fast, spicy-video-1, spicy-cinema-1. Image to video: spicy-motion-2, spicy-cinema-1-image, spicy-motion-3. Character or references to video: spicy-character-video-1, spicy-cinema-1-character (up to 9 references named Image 1, Image 2 in the prompt), spicy-motion-3. Cheap previews: spicy-motion-draft-1 is a silent draft tier from a first frame; render the keeper on spicy-motion-2 or 3. The spicy-cinema-1 family runs 3 to 15 seconds at 480P to 1080P with native audio always on, no negative_prompt, no enhance_prompt and no audio inputs. Which inputs may travel together differs per model: read list_models inputs.combinations first. Audio comes in two modes (inputs.audio.mode): driving (spicy-motion-2, spicy-video-1: the clip is the soundtrack, the mouth follows it, works with a first frame) and reference (spicy-motion-3, spicy-motion-3-fast: the model generates sound and uses the clip for voice, tone and beat; only with a text prompt or references, never with a first frame). An invalid combination is rejected here before any spend with the valid combinations spelled out. Actions (spicy-motion-3, -fast): pass an action id from list_actions and its reference clip of that sex act supplies the camera, pose and motion second for second while image_url supplies the woman, e.g. {model: \"spicy-motion-3\", action: \"pov-missionary\", image_url: <one of list_images>, prompt: \"hotel room at night, red lingerie\"}. To change a finished clip or transfer motion onto a person, use edit_video. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A video model id, e.g. spicy-motion-2, spicy-motion-3, spicy-cinema-1, spicy-motion-draft-1."},"prompt":{"description":"What should happen in the clip; required unless image_url or action is sent. With action it is only the scene (setting, outfit, lighting, extra direction). Capped per model (limits.maxPromptChars: 1500 on spicy-motion-1 and spicy-motion-draft-1, 20000 on spicy-motion-3 and -fast, 5000 on the rest).","type":"string","minLength":1,"maxLength":20000},"image_url":{"description":"First frame, from list_images or a generate_image result. list_models inputs.first_frame says whether it is required, optional or not taken. Not with reference_image_urls; on spicy-motion-3 a first frame pairs only with last_frame_url.","type":"string","format":"uri"},"last_frame_url":{"description":"Closing frame (one of the account's images); the clip travels from image_url to it. Models with inputs.last_frame true; needs image_url (or a continuation video_url).","type":"string","format":"uri"},"reference_image_urls":{"description":"Up to inputs.reference_images of the account's images guiding identity and look without fixing the first shot (a character counts as one). Exclusive with image_url. See inputs.combinations.","maxItems":10,"type":"array","items":{"type":"string","format":"uri"}},"video_url":{"description":"output.video_url of one of the account's own finished tasks. inputs.continuation_clip (spicy-motion-2): continues the clip in place of image_url (2 to 10 s in; duration is the whole output incl. the clip, billed in full). inputs.reference_video (spicy-motion-3, -fast): reference clip to extend or edit (15 s max; clip plus duration within 30 s), never with image_url.","type":"string","format":"uri"},"audio_url":{"description":"A clip from upload_audio or generate_speech (its url, exact; 2 to 30 seconds). What it does depends on inputs.audio.mode: driving (spicy-motion-2, spicy-video-1) makes it the soundtrack with the mouth following it, and works with a first frame; reference (spicy-motion-3, -fast) guides voice, tone and beat while the model generates the sound, and only goes with a text prompt or references, never with image_url. Rejected on models with mode none (spicy-motion-1, spicy-character-video-1).","type":"string","format":"uri"},"audio_urls":{"description":"Several upload_audio clips, up to inputs.audio.max_clips (5 on spicy-motion-3 and -fast, 15 s total); other models take a single audio_url.","maxItems":5,"type":"array","items":{"type":"string","format":"uri"}},"audio_mode":{"description":"Optional guard: what you expect the audio to do. Must equal the model's inputs.audio.mode or the call is rejected naming a model that offers it. Omit to use the model's mode.","type":"string","enum":["driving","reference"]},"resolution":{"description":"480P, 720P (default) or 1080P, within the model's limits.resolutions.","type":"string"},"duration":{"description":"Seconds, default 5 (8 with action), within the model's limits (2 to 30; 3 to 15 on the spicy-cinema-1 family). -1 where inputs.smart_duration is true (spicy-motion-3, -fast): the model chooses; debited at the maximum, settled to the rendered length.","type":"integer","minimum":-1,"maximum":30},"aspect_ratio":{"description":"Text-to-video, references and action only (no first frame or video_url), where inputs.aspect_ratio is true; values in limits.aspectRatios. 16:9, 9:16, 1:1, 4:3, 3:4 on spicy-video-1 (default 16:9); plus 21:9 on spicy-motion-3 and -fast, where omitting it lets the model choose; plus 4:5, 5:4 and 9:21 on spicy-cinema-1 and spicy-cinema-1-character.","type":"string"},"negative_prompt":{"description":"What to avoid. Ignored on spicy-motion-3, spicy-motion-3-fast and the spicy-cinema-1 family.","type":"string","maxLength":2000},"enhance_prompt":{"description":"Let the model expand the prompt with lighting, camera and detail cues. Default false. Not on the spicy-cinema-1 family.","type":"boolean"},"audio":{"description":"Generate audio where the model supports it. spicy-motion-draft-1 is always silent; the spicy-cinema-1 family always has audio.","type":"boolean"},"seed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"character":{"description":"A saved character id (list_characters). Only on models that take references: spicy-character-video-1 (no first frame needed), spicy-cinema-1-character, spicy-motion-3, spicy-motion-3-fast.","type":"string"},"views":{"description":"With character: body view ids (cv_...) to send, or [] for none; omitted, the views that fit the prompt are picked.","maxItems":2,"type":"array","items":{"type":"string"}},"action":{"description":"An action id from list_actions, e.g. pov-missionary. Runs on spicy-motion-3 and spicy-motion-3-fast: the action's reference clip sets camera, pose and motion; image_url becomes the woman (her identity, not a first frame) and prompt is optional, only the scene. Not with video_url or last_frame_url. duration defaults to 8 and aspect_ratio to the action's own (any supported ratio may be requested); clip plus duration within 30 s. The clip's seconds are billed as input seconds on top of the output.","type":"string"},"style":{"description":"Art style (list_styles): photorealistic, studio (glossy glamour), anime, 3d (animated-film CGI) or cartoon (2D adult cartoon). Prompt text only, same price.","type":"string","enum":["photorealistic","studio","anime","3d","cartoon"]},"fps":{"description":"Video frame rate: 30 (default) or 60. 60 smooths motion with frame interpolation for 20% on top of the video price (input or action clip seconds included); audio is kept. If that step fails the 30 fps clip is delivered and the fee refunded.","anyOf":[{"type":"number","const":30},{"type":"number","const":60}]},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/edit_video":{"post":{"operationId":"tool_edit_video","summary":"Edit video","description":"Change a finished clip, or make a person perform a motion. Asynchronous like generate_video: returns a task with the price debited; poll get_job every 10 to 15 seconds (refunded if it fails). Instruction edits (spicy-video-edit-1: 2 to 10 second clips, output the clip's length; spicy-cinema-1-edit: 3 to 30 second clips, the first 15 seconds are edited and returned): video_url (one of the account's finished video outputs) plus a prompt describing the change, optionally reference_image_urls for an outfit, prop or style (the account's own images), keep_audio to keep the soundtrack. Billed per second of input plus output at the resolution's rate, debited at the clip's length and settled to what is rendered. Motion transfer (spicy-animate-1): image_url (the performer, one of the account's images, full body and dressed works best) plus a motion id from list_motions or a video_url of the account's own clip (2 to 30 s); the output is as long as the motion clip, billed per output second by quality. Motion transfer refuses explicit or nude images and clips (refunded, error_code blocked). Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A video-edit model id: spicy-video-edit-1, spicy-cinema-1-edit or spicy-animate-1."},"video_url":{"description":"The clip: output.video_url of one of the account's finished video tasks. Required for instruction edits; on spicy-animate-1 an alternative to motion.","type":"string","format":"uri"},"prompt":{"description":"Instruction edits (required): the change to make, up to 5000 characters. Not needed for motion transfer.","type":"string","maxLength":5000},"reference_image_urls":{"description":"Instruction edits: outfit, prop or style images from list_images, up to 4 on spicy-video-edit-1 and 5 on spicy-cinema-1-edit.","maxItems":5,"type":"array","items":{"type":"string","format":"uri"}},"resolution":{"description":"Instruction edits: 720P (default) or 1080P.","type":"string","enum":["720P","1080P"]},"aspect_ratio":{"description":"spicy-video-edit-1 only: reshape to 16:9, 9:16, 1:1, 4:3 or 3:4.","type":"string"},"keep_audio":{"description":"Instruction edits: true keeps the clip's original soundtrack.","type":"boolean"},"negative_prompt":{"type":"string","maxLength":2000},"seed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"enhance_prompt":{"description":"spicy-video-edit-1 only: let the model expand the prompt.","type":"boolean"},"image_url":{"description":"spicy-animate-1 (required): the person who performs the motion, one of the account's images.","type":"string","format":"uri"},"motion":{"description":"spicy-animate-1: a library motion id from list_motions, e.g. mo_hip_dance. Send this or video_url.","type":"string"},"quality":{"description":"spicy-animate-1: standard (default) or pro, priced separately.","type":"string","enum":["standard","pro"]},"user":{"description":"Your own id for the end user making this request.","type":"string","maxLength":128},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_motions":{"post":{"operationId":"tool_list_motions","summary":"List motions","description":"The motion library for motion transfer on edit_video (spicy-animate-1): id, label, description, preview url, duration_s and aspect_ratio of each clip. Every clip is 9:16, full body, fixed camera and was generated with SpicyAPI video models (no real performer footage). The output is as long as the clip; match the performer image to its framing for best results. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_actions":{"post":{"operationId":"tool_list_actions","summary":"List actions","description":"The action library for generate_video with action (spicy-motion-3 and spicy-motion-3-fast): id, label, category (sex, anal, oral, handjob, cumshot, solo, trans), url (the reference clip, 4 to 6 seconds), preview_url (the full 8 second action), poster_url, duration_s (the reference clip's length) and aspect_ratio. The clip supplies the camera, pose and motion of one sex act; the output runs 8 seconds by default and carries the act on past the clip. The caller's image supplies the woman. Every clip was generated with SpicyAPI video models (no real performer footage). Its seconds are billed as input seconds. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_image_actions":{"post":{"operationId":"tool_list_image_actions","summary":"List image actions","description":"The image action library for generate_image on spicy-image-action-1: id, label, category (sex, anal, oral, handjob, cumshot, solo, trans), image_url (the still), preview_url (a small copy), size (the output width*height) and styles (the still redrawn as anime, 3d and cartoon, for style on a drawn woman). 68 ready-made explicit stills; most share their id with a video action (list_actions). The still supplies the act, pose, framing, room and her body; the caller's image supplies her face, hair and skin tone. Every still was generated with AI (no real performers). Billed per image at the model's rate. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_styles":{"post":{"operationId":"tool_list_styles","summary":"List styles","description":"The art styles for `style` on generate_image and generate_video: id, label, description and preview_url (a sample image). photorealistic, studio (glossy glamour), anime, 3d (animated-film CGI) and cartoon (2D adult cartoon). A style only adds prompt text, so the model, seed and price are unchanged. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/upload_audio":{"post":{"operationId":"tool_upload_audio","summary":"Upload audio","description":"Bring in a WAV or MP3 (2 to 30 seconds, 15 MB max) from a public https URL for lip-sync and audio-driven video. The clip is transcribed and the transcript screened like a prompt; flat $0.01 for that screening. A clip that fails the screen is discarded with the same 422 shape as a blocked prompt; a 503 means screening was unavailable and nothing was charged. The returned url is what audio_url and audio_urls on generate_video accept; external audio URLs are rejected there. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Public https URL of the WAV or MP3 to fetch (private and plain-IP hosts are refused)."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["url"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_audio":{"post":{"operationId":"tool_list_audio","summary":"List audio","description":"The account's audio clips, newest first: uploads and generated speech (source upload or speech), with id, url, duration_s, mime and transcript. These urls are the only values generate_video accepts as audio_url or in audio_urls. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"description":"Default 20.","type":"integer","minimum":1,"maximum":100}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/delete_audio":{"post":{"operationId":"tool_delete_audio","summary":"Delete audio","description":"Forget an uploaded clip so it can no longer be used as a video input. Videos already made with it are unaffected. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"A clip id from list_audio (au_...)."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/transcribe_audio":{"post":{"operationId":"tool_transcribe_audio","summary":"Transcribe audio","description":"Speech to text from a public https URL of an audio file (wav, mp3, m4a/mp4, ogg/opus, flac, webm and more; up to 10 MB and 5 minutes). Synchronous; returns {id, text, language, duration_s, cost_usd}. Billed by the second at the model's per-minute rate (minimum charge applies); the audio is not stored and nothing is screened, since nothing is generated. Use it for voice notes in a companion app or to read back a clip. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Public https URL of the audio file (private and plain-IP hosts are refused)."},"model":{"description":"A transcription model id, default spicy-transcribe-1.","type":"string"},"language":{"description":"Optional hint: English, Chinese, German, Italian, Portuguese, Spanish, Japanese, Korean, French or Russian. Omit to detect.","type":"string"},"user":{"description":"Your own id for the end user making this request.","type":"string","maxLength":128},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["url"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/generate_speech":{"post":{"operationId":"tool_generate_speech","summary":"Generate speech","description":"Text to speech, synchronous. Returns {id, url, duration_s, voice, format, cost_usd}: a durable clip (WAV 24 kHz mono, or MP3 on the spicy-voice-2 and spicy-voice-3 families) that is also one of the account's audio clips, so its url goes straight into generate_video as audio_url (driving audio on spicy-motion-2: the mouth follows the words, with a first frame; reference audio on spicy-motion-3: voice, tone and beat, no first frame). Video audio must be 2 to 30 seconds. Models: spicy-voice-1 (preset voices), spicy-voice-1-expressive (preset voices plus instructions for emotion, pace and delivery), spicy-voice-1-custom (the account's own vc_... voices from create_voice), spicy-voice-2 and spicy-voice-2-flash (their own preset voices, instructions, languages Auto, English and Chinese, up to 5,000 characters, and inline tags inside input: control tags switch delivery until the next tag, [sad] [amazed] [deep and loud shouting] [trembling] [angry] [excited] [sarcastic] [curious] [like dracula] [bored] [tired] [scornful] [shouting] [asmr] [panicked] [mischievously] [empathetic] [whispers] [reluctantly] [crying] [serious] [very slowly] [very fast]; sound tags insert a sound, [gasp] [sighing] [clears throat] [giggles] [laughing] [cough] [snorts]; e.g. \"[excited]Hey you.[giggles] Come here.[whispers] Closer.\"), spicy-voice-3 (the highest quality: acts the emotion and pacing of the text, 21 voices, any listed language, free-form tags such as [whispers], [sighs] or [softly, a little shy], no instructions) and spicy-voice-3-flash (the same, lower latency, half the price). Tags count as billed characters. Input caps: 600 characters on the spicy-voice-1 family, 5,000 on spicy-voice-2. Billed per 10,000 characters of input plus instructions (CJK ideographs count 2), settled down to the billed count. Streaming (stream: true) is a REST feature for apps (POST /v1/audio/speech); this tool always returns a stored clip. The text is screened like a prompt; blocked text costs nothing. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"A speech model id: spicy-voice-1, spicy-voice-1-expressive, spicy-voice-1-custom, spicy-voice-2, spicy-voice-2-flash, spicy-voice-3 (highest quality) or spicy-voice-3-flash (low latency)."},"input":{"type":"string","minLength":1,"maxLength":5000,"description":"The text to speak: up to 600 characters on the spicy-voice-1 family, 5,000 on the spicy-voice-2 and spicy-voice-3 families, where inline tags like [whispers] or [giggles] are performed."},"voice":{"type":"string","minLength":1,"description":"A preset voice name the model speaks (list_voices), e.g. Cherry on spicy-voice-1, Lingxin on spicy-voice-2, Eva on spicy-voice-2-flash, Sarah on spicy-voice-3, or a vc_... id on spicy-voice-1-custom."},"instructions":{"description":"spicy-voice-1-expressive and the spicy-voice-2 family: how to say it (emotion, pace, pitch, delivery) in plain English or Chinese. Not on spicy-voice-3: use tags.","type":"string","maxLength":1600},"language":{"description":"Auto (default), English, Chinese, German, Italian, Portuguese, Spanish, Japanese, Korean, French or Russian. The spicy-voice-2 family takes Auto, English or Chinese; spicy-voice-3 takes all of them.","type":"string"},"response_format":{"description":"wav (default) on every model; mp3 on the spicy-voice-2 and spicy-voice-3 families.","type":"string","enum":["wav","mp3"]},"user":{"description":"Your own id for the end user making this request.","type":"string","maxLength":128},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model","input","voice"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_voices":{"post":{"operationId":"tool_list_voices","summary":"List voices","description":"Voices for generate_speech: every preset voice with the models that speak it (spicy-voice-1 has them all, spicy-voice-1-expressive a subset), then the account's designed and cloned voices (vc_... ids, spoken by spicy-voice-1-custom), newest first. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/create_voice":{"post":{"operationId":"tool_create_voice","summary":"Create voice","description":"Create a custom voice for spicy-voice-1-custom, returned as a vc_... id. Design: name plus a description of vocal qualities (gender, age range, pitch, pace, tone, accent); a description that names, references or imitates a real person is refused. Clone: name plus audio_url (one of the account's clips from list_audio or upload_audio, 10 MB max) plus consent: true, the user's attestation that the voice is their own or that they hold the speaker's written consent; it is stored with the voice. Never set consent without the user confirming it. One-off fee per voice, refunded if creation fails. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"description":"A label for the voice, e.g. Mara."},"description":{"description":"Design: the voice's qualities. Omit when cloning.","type":"string","maxLength":2048},"preview_text":{"description":"Design: a line spoken in the preview clip.","type":"string","maxLength":1024},"audio_url":{"description":"Clone: a clip url from list_audio. Omit when designing.","type":"string","format":"uri"},"consent":{"description":"Clone: must be true, the user's attestation that the voice is theirs or that the speaker gave written consent.","type":"boolean"},"language":{"description":"English (default), Chinese, German, Italian, Portuguese, Spanish, Japanese, Korean, French or Russian.","type":"string"},"user":{"description":"Your own id for the end user making this request.","type":"string","maxLength":128},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/delete_voice":{"post":{"operationId":"tool_delete_voice","summary":"Delete voice","description":"Delete a designed or cloned voice (vc_...) so it can no longer be spoken. Clips already generated with it are unaffected. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"A vc_... id from list_voices."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/create_embeddings":{"post":{"operationId":"tool_create_embeddings","summary":"Create embeddings","description":"Embedding vectors for search, memory and recommendations, OpenAI-compatible. spicy-embed-1: text, input a string or up to 10 strings (8,192 tokens each), dimensions 64, 128, 256, 512, 768, 1024 (default), 1536 or 2048. spicy-embed-vision-1: text and images in one 768-dimension space, input items are strings, {text} or {image} where every image is one of the account's own generated images (list_images); use it for \"more like this\" over the account's outputs, tagging and near-duplicate detection. Returns {data: [{index, embedding}], usage, cost_usd}; billed per token (minimum charge applies). Vectors are long: ask for small dimensions (256) when you only need to compare a few items here. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string","description":"An embedding model id: spicy-embed-1 (text) or spicy-embed-vision-1 (text and images)."},"input":{"anyOf":[{"type":"string","minLength":1},{"minItems":1,"maxItems":10,"type":"array","items":{"anyOf":[{"type":"string","minLength":1},{"type":"object","properties":{"text":{"type":"string","minLength":1}},"required":["text"],"additionalProperties":false},{"type":"object","properties":{"image":{"type":"string","format":"uri"}},"required":["image"],"additionalProperties":false}]}}],"description":"A string, or up to 10 items. Objects ({text} or {image}) only on spicy-embed-vision-1."},"dimensions":{"description":"spicy-embed-1 only: 64, 128, 256, 512, 768, 1024 (default), 1536 or 2048.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"encoding_format":{"description":"float (default) or base64 (little-endian float32).","type":"string","enum":["float","base64"]},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["model","input"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/get_job":{"post":{"operationId":"tool_get_job","summary":"Get job","description":"Status of a video task from generate_video or edit_video (queued, processing, finalizing, succeeded with output.video_url and usage, or failed and refunded) or of a logged image, chat, speech, transcription or embedding request by id. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Task or request id, e.g. sj_..."}},"required":["id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/list_jobs":{"post":{"operationId":"tool_list_jobs","summary":"List jobs","description":"Recent requests on the account (images, edits, video tasks including edits and motion transfer, chat, speech, voice creation, transcription, embeddings, live calls), newest first: id, type, model, status, cost_usd, output URL when finished, error when failed, and whether it came from the playground, the API or an agent. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"description":"Default 20.","type":"integer","minimum":1,"maximum":100},"type":{"type":"string","enum":["image","image-edit","video","chat","speech","voice","transcription","embedding","realtime"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/get_usage":{"post":{"operationId":"tool_get_usage","summary":"Get usage","description":"Spend and request counts over the last N days, totalled and broken down by model, type and source (playground, api, agent), with the error count. Reads the account's request log. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"description":"Default 7.","type":"integer","minimum":1,"maximum":90}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/create_api_key":{"post":{"operationId":"tool_create_api_key","summary":"Create API key","description":"Mint a new key for this account (plaintext returned once, stored hashed; at most 500 active keys). sandbox: true makes a key that answers everything from fixtures and bills nothing. Keys made with a sandbox key are always sandbox keys. Keep keys in environment variables, never in chat. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"description":"A label, e.g. Production or Agent."},"sandbox":{"description":"Fixture-only key, nothing billed. Default false.","type":"boolean"},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/revoke_api_key":{"post":{"operationId":"tool_revoke_api_key","summary":"Revoke API key","description":"Permanently revoke a key by id (the id is returned by create_api_key and shown on the API keys page). Applications using it stop working at once. Guarded: without approval_token it returns {error: \"approval_required\", summary, approval_token}; show the summary (it carries the cost), get the user's yes, call again with the token. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Key id (sha256 hash)."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/get_topup_link":{"post":{"operationId":"tool_get_topup_link","summary":"Get top-up link","description":"Checkout URL to add funds by card (hosted checkout) or crypto for a preset amount ($50, $100, $250, $500, $1000; minimum $50). The USER opens it and pays in the browser; never enter payment details yourself. The balance is credited by the payment webhook; call get_account afterwards. If it returns commitment_required, the user must first sign the one-page Customer Commitment Letter on the Billing page; an agent cannot sign it. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount_usd":{"description":"One of 50, 100, 250, 500, 1000. Default 50.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"method":{"description":"Default card.","type":"string","enum":["card","crypto"]}},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/set_spend_limit":{"post":{"operationId":"tool_set_spend_limit","summary":"Set spend limit","description":"Cap what this account can spend per calendar month (UTC), enforced at debit time on every surface: requests past the cap return 402 without charging. Lowering or setting a limit never needs approval; raising or removing one does. null removes the limit. GUARDED: without approval_token it returns {error: 'approval_required', summary, approval_token}; show the summary to the user, get a yes, call again with the token (or the account turns approvals off in the dashboard).","tags":["Agent tools","guarded"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"monthly_usd":{"anyOf":[{"type":"number","minimum":1,"maximum":1000000},{"type":"null"}],"description":"Monthly cap in USD, or null to remove it."},"approval_token":{"description":"Approval token from a previous approval_required response, after the user said yes.","type":"string"}},"required":["monthly_usd"],"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}},"/api/v1/tools/read_acceptable_use":{"post":{"operationId":"tool_read_acceptable_use","summary":"Read acceptable use policy","description":"The acceptable use policy as markdown: prohibited content (minors in any form, images or video of real people with or without consent, non-consensual scenarios and the rest), age-verification duties for the customer's product, enforcement. Read it before the first generation and tell the user what their product must do. Works with a $0 balance. Works with a $0 balance.","tags":["Agent tools","default"],"security":[{"apiKey":[]}],"servers":[{"url":"https://www.spicyapi.com"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"The tool's JSON result (same payload the MCP tool returns)."},"400":{"description":"Arguments failed validation; body lists the issues."},"401":{"description":"Missing or invalid API key."},"402":{"description":"Insufficient balance."},"403":{"description":"Guarded tool without a valid approval_token (body carries a fresh one)."},"429":{"description":"Rate limited (60 requests per minute per key)."}}}}}}