xW 2.7 S
Image-to-video: animate a still from a prompt, with optional audio.
xW 2.7 S turns a single input image into a short video guided by a text prompt. The image you send is where the video starts, so it is the model for animating a frame you already have rather than inventing one.
- Starts from your image — an input image is required, not optional
- Optional audio track to drive the motion, and a negative prompt to hold things out
- Billed per second of output
Generation is asynchronous. POST answers `202` with a `requestId` and a `pollUrl`; the result arrives by polling that URL or through an HMAC-signed webhook when you pass `webhookUrl`. Signed output URLs stay valid for 23 hours.
Authentication. Add Authorization: Bearer xm_live_… to every request.
Account responsibility. Every request must include the real email of YOUR end-user via endUserEmail. You are accountable for what they generate — monitor activity and act on abuse, or your account may be suspended.
Quick example
Send a generation in 5 lines. Pick your language below.
curl -X POST https://api.xmode.ai/v1/videos \
-H "Authorization: Bearer xm_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "alice@your-product.com",
"model": "xW2.7S",
"prompt": "A girl dancing gracefully in a flower field, camera slowly pushing in",
"image": "https://example.com/input-image.png"
}'Request parameters
The fields you can include in the request body. Anything not listed is ignored.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
endUserEmail | string (email) | yes | — | REQUIRED. Email of YOUR real end-user inside your product — the person who actually triggered this video. We use it to attribute every request and give you a chain of responsibility: you can list, audit, and delete that user's videos. Do NOT pass a fake address, a shared placeholder, or someone else's email — that's a policy violation. Example: |
model | string | yes | — | Public model alias. Required — pass the alias of the video model you want to use (e.g. xW2.7S). Example: |
prompt | string | yes | — | English text prompt describing the motion, action, and mood you want from the input image. Up to 8000 characters. Example: |
image | string (https URL) | yes | — | Input image to animate (publicly fetchable HTTPS URL). Supported formats JPEG, PNG (no transparency), BMP, WEBP. Width and height 240–8000 px, aspect ratio between 1:8 and 8:1, max 20 MB. Example: |
negative_prompt | string | no | — | Elements to exclude from the video. Example: |
audio_url | string (https URL) | no | — | Optional audio track (HTTPS URL) to drive the video. Supported formats WAV, MP3. Duration 2–30 seconds, max 15 MB. Example: |
resolution | "720p" | "1080p" | no | "1080p" | Output video resolution. "720p" or "1080p". Example: |
duration | integer | no | 5 | Video duration in seconds (2–15). Billed per second of output. Example: |
prompt_extend | boolean | no | true | When enabled, the system expands and optimizes your prompt before generation. Free. On by default for this model, and worth knowing when you pass a `seed`: the rewrite differs between runs, so the same seed does not guarantee the same video unless you also send `prompt_extend: false`. |
seed | integer | null | no | null (auto) | Determinism seed (0–2147483647). Pass null or omit for an auto-generated seed. Note that `prompt_extend` is ON by default for this model and rewrites your prompt differently on each run, so a fixed seed only holds the result steady when you also send `prompt_extend: false`. Example: |
webhookUrl | string (https URL) | no | — | Optional public HTTPS URL we will POST the final record to once the video is succeeded or failed. The body is identical to what GET /v1/videos/:requestId returns. Each delivery is HMAC-signed (`X-XMode-Signature: v1=<hex>`) and timestamped (`X-XMode-Timestamp`); reject anything older than 5 minutes. Private/loopback IPs are refused (SSRF guard). Example: |
Response
The POST answers 202 Accepted with this shape — the job is queued, not finished. Read the result from pollUrl, which returns the same record with the fields below filled in. Output URLs are signed and valid for 23 hours.
{
"requestId": "req_8sV2kQ1aJpL9wDvE",
"model": "xW2.7S",
"status": "queued",
"prompt": "A girl dancing gracefully in a flower field, camera slowly pushing in",
"endUserEmail": "alice@your-product.com",
"createdAt": "2026-06-09T19:45:15.204Z",
"pollUrl": "https://api.xmode.ai/v1/videos/req_8sV2kQ1aJpL9wDvE"
}| Name | Type | Required | Default | Description |
|---|---|---|---|---|
requestId | string | yes | — | Unique id of the video request. Use it with GET /v1/videos/:requestId to poll. |
model | string | yes | — | Public alias of the model that will produce the video. |
status | "queued" | "processing" | "succeeded" | "failed" | "expired" | yes | — | Lifecycle marker. POST always returns `queued`. GET returns the current state. `expired` means the request succeeded more than 23 hours ago and signed URLs no longer work — re-run if you need the video again. |
pollUrl | string | no | — | Present on `queued` responses only. Absolute URL to GET for status. Recommended polling cadence: every 5 seconds until terminal. |
videos | array | no | — | Present only when status=`succeeded`. Each item has `id`, `expiresAt`, optional `duration`/`resolution`, and EITHER `url` (signed, valid 23 h) OR `error: { code: "storage_failed" }` if our storage layer dropped that video. If every video of the request is dropped there is nothing to deliver, so the request comes back `failed` and refunded instead. The URL is re-signed on every read, so re-reading this record replaces a link that went stale on your side — but that never moves `expiresAt`, which is fixed when the generation completed. Once it passes, the record returns status `expired` with no URL and the bytes are gone. Download inside the window or re-generate. |
error | object | no | — | Present only when status=`failed`. `{ code, message }`. Same code values as the top-level error format (validation_error, content_policy, provider_error, provider_timeout, internal_error). |
cost | object | no | — | Present only when status=`succeeded`. `{ xTokens: number }`. Debited at request time; refunded automatically on `failed`. |
finishedAt | string (ISO 8601) | no | — | Present on terminal statuses (`succeeded`, `failed`, `expired`). |
endUserEmail | string | yes | — | Echoed back so you can confirm the grouping. Always lowercase. |
createdAt | string (ISO 8601) | yes | — | When the request was accepted (POST time). |
Use cases
Common patterns. Copy any block, replace the API key, and you have a runnable request.
Basic image-to-video
Animate an input image from a text prompt. Defaults to 1080p and 5 seconds.
curl -X POST https://api.xmode.ai/v1/videos \
-H "Authorization: Bearer xm_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "alice@your-product.com",
"model": "xW2.7S",
"prompt": "A girl dancing gracefully in a flower field, camera slowly pushing in",
"image": "https://example.com/input-image.png"
}'Higher resolution + custom duration
Set `resolution` and `duration`. A 10-second 1080p product spin.
curl -X POST https://api.xmode.ai/v1/videos \
-H "Authorization: Bearer xm_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "alice@your-product.com",
"model": "xW2.7S",
"prompt": "A product rotating slowly with dramatic lighting",
"image": "https://example.com/product-photo.jpg",
"resolution": "1080p",
"duration": 10
}'Audio-driven + negative prompt
Pass `audio_url` to drive the motion and `negative_prompt` to exclude artefacts.
curl -X POST https://api.xmode.ai/v1/videos \
-H "Authorization: Bearer xm_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "alice@your-product.com",
"model": "xW2.7S",
"prompt": "A character speaking and gesturing naturally",
"image": "https://example.com/character.png",
"audio_url": "https://example.com/speech.mp3",
"negative_prompt": "blurry, distorted face, extra limbs",
"resolution": "720p",
"duration": 8,
"seed": 42
}'Skip polling — receive a webhook
Pass `webhookUrl` and we POST the final record there once the video is done. The body is the same shape as GET /v1/videos/:requestId. Verify `X-XMode-Signature: v1=<hex>` against your account webhook secret (HMAC-SHA256 of `"<timestamp>.<rawBody>"`).
curl -X POST https://api.xmode.ai/v1/videos \
-H "Authorization: Bearer xm_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"endUserEmail": "alice@your-product.com",
"model": "xW2.7S",
"prompt": "Cinematic slow zoom on a lighthouse at golden hour",
"image": "https://example.com/lighthouse.jpg",
"webhookUrl": "https://your-app.example.com/hooks/xmode"
}'Limits
- Resolutions: 720p / 1080p
- Duration: 2–15 seconds
- Max input size: 20 MB input image
- Max audio size: 15 MB audio
- Prompt max length: 8000 characters