diff --git a/api.oas3.yaml b/api.oas3.yaml
index 58390a8..c59aa4c 100644
--- a/api.oas3.yaml
+++ b/api.oas3.yaml
@@ -92,6 +92,12 @@ paths:
/probe/{url}:
$ref: "./paths/probe.yaml"
+ /generate:
+ $ref: "./paths/generate.yaml"
+
+ /generate/{id}:
+ $ref: "./paths/generateid.yaml"
+
/assets/{id}:
$ref: "./paths/assetsid.yaml"
diff --git a/paths/generate.yaml b/paths/generate.yaml
new file mode 100644
index 0000000..e33a531
--- /dev/null
+++ b/paths/generate.yaml
@@ -0,0 +1,55 @@
+ post:
+ responses:
+ "200":
+ description: The generated asset was already cached and is immediately available.
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/responses/generationresponse.yaml#/GenerationResponse"
+ "202":
+ description: The generation job has been queued. Poll the status endpoint.
+ headers:
+ Location:
+ description: The relative URL to poll for job status.
+ schema:
+ type: string
+ Retry-After:
+ description: Suggested seconds to wait before polling.
+ schema:
+ type: integer
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/responses/generationresponse.yaml#/GenerationResponse"
+ description: |
+ Generate a single image, video or audio asset from a text prompt without
+ rendering a full edit. Submit a prompt-bearing asset; the response is
+ immediate when an identical asset has been generated before (results are
+ cached by prompt, model and options), otherwise the job is queued and can
+ be polled via the status endpoint.
+
+ Generation is billed in credits per asset. Identical repeat requests
+ resolve from the cache at no charge.
+
+ **Base URL:** https://api.shotstack.io/edit/{version}
+ summary: Generate Asset
+ operationId: postGenerate
+ requestBody:
+ description: >-
+ A prompt-bearing image, video or audio asset to generate.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ asset:
+ oneOf:
+ - $ref: "../schemas/imageasset.yaml#/ImageAsset"
+ - $ref: "../schemas/videoasset.yaml#/VideoAsset"
+ - $ref: "../schemas/audioasset.yaml#/AudioAsset"
+ required:
+ - asset
+ security:
+ - DeveloperKey: []
+ tags:
+ - Edit
diff --git a/paths/generateid.yaml b/paths/generateid.yaml
new file mode 100644
index 0000000..9aaeef2
--- /dev/null
+++ b/paths/generateid.yaml
@@ -0,0 +1,32 @@
+ get:
+ responses:
+ "200":
+ description: The generation job has finished (done or failed).
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/responses/generationresponse.yaml#/GenerationResponse"
+ "202":
+ description: The generation job is still processing.
+ content:
+ application/json:
+ schema:
+ $ref: "../schemas/responses/generationresponse.yaml#/GenerationResponse"
+ description: |
+ Get the status of an on-demand asset generation job created with the
+ generate endpoint. Jobs are owner-scoped.
+
+ **Base URL:** https://api.shotstack.io/edit/{version}
+ summary: Get Generation Status
+ operationId: getGenerate
+ security:
+ - DeveloperKey: []
+ tags:
+ - Edit
+ parameters:
+ - in: path
+ name: id
+ required: true
+ schema:
+ type: string
+ description: The generation job id returned by the generate endpoint.
diff --git a/schemas/audioasset.yaml b/schemas/audioasset.yaml
index 1a3a879..178ce26 100644
--- a/schemas/audioasset.yaml
+++ b/schemas/audioasset.yaml
@@ -1,15 +1,19 @@
AudioAsset:
description: |
The AudioAsset adds audio to a Clip. The audio can be sourced from a URL
- (`src`) or generated from a text prompt (`prompt`). Exactly one of `src` or
- `prompt` must be provided.
+ (`src`), generated from a text prompt (`prompt`), or both. At least one of
+ `src` or `prompt` must be provided.
- **Source URL:** set `src` to a publicly accessible audio URL (e.g. mp3).
- - **Generated speech:** set `prompt` to the spoken text and `voice` to a voice
- identifier (text-to-speech). Optionally set `language`/`newscaster`.
- - **Generated music or SFX:** set `prompt` describing the sound; omit `voice`.
- - Use `model` to choose the generator. The generated `src` is filled in
- automatically.
+ - **Generated speech:** set `prompt` to the spoken text and choose a
+ text-to-speech `model`; set the voice via `options`.
+ - **Generated music or SFX:** set `prompt` describing the sound and choose
+ a music generation `model`.
+ - **Both:** `src` acts as a preview placeholder while `prompt` drives
+ generation — the audio is regenerated from the prompt at render time.
+ Unchanged prompts and options resolve from the generation cache.
+ - Use `model` to choose the generator and `options` to configure it. The
+ generated `src` is filled in automatically.
type: object
properties:
type:
@@ -20,65 +24,40 @@
src:
description: >-
The audio source URL. The URL must be publicly accessible or include
- credentials. Provide either `src` or `prompt`, not both.
+ credentials. When `prompt` is also set, `src` serves as a preview
+ placeholder and the audio is regenerated from the prompt at render time.
type: string
minLength: 1
pattern: '\S'
example: https://s3-ap-northeast-1.amazonaws.com/my-bucket/sound.mp3
prompt:
description: >-
- A text prompt. When `voice` is set, the prompt is the spoken text
- (text-to-speech). Without `voice`, the prompt describes generated music
- or sound effects. The generated `src` is filled in automatically.
+ A text prompt. For text-to-speech models the prompt is the spoken
+ text; for music models it describes the sound to generate. The
+ generated `src` is filled in automatically; an existing `src` is
+ treated as a preview placeholder and replaced.
type: string
maxLength: 4000
example: Welcome to today's broadcast.
- voice:
- description: >-
- Voice identifier for text-to-speech generation (e.g. `Matthew`, `Joanna`).
- Only meaningful when `prompt` is set.
- type: string
- example: Matthew
- language:
- description: >-
- Optional BCP-47 language code (e.g. `en-US`) for text-to-speech.
- Only meaningful when `prompt` and `voice` are set.
- type: string
- example: en-US
- newscaster:
- description: >-
- Set to `true` to use the voice's newscaster mode when supported.
- Only meaningful when `prompt` and `voice` are set.
- type: boolean
- default: false
model:
description: >-
The generation model to use when `prompt` is set (e.g. `polly-neural`,
- `fal/elevenlabs-tts`, `fal/minimax-speech`, `fal/elevenlabs-music`).
- Defaults to the platform's preferred generator if omitted.
+ `elevenlabs-tts`, `elevenlabs-music`). Defaults to
+ `elevenlabs-tts` (with a default voice) if omitted. Each model's available options are defined
+ by the model registry.
type: string
example: polly-neural
- musicLengthMs:
+ options:
description: >-
- Target duration in milliseconds for generated music (3,000–600,000).
- Only meaningful when `prompt` is set and the model is a music generator
- (e.g. `fal/elevenlabs-music`).
- type: number
- minimum: 3000
- maximum: 600000
- example: 30000
- forceInstrumental:
- description: >-
- Set to `true` to force instrumental music (no vocals). Only meaningful
- for music generation models.
- type: boolean
- default: false
- compositionPlan:
- description: >-
- An optional structured composition plan for music generation. Only
- meaningful for music generation models (e.g. `fal/elevenlabs-music`).
- type: string
- maxLength: 4000
+ Model-specific generation settings. Valid keys and values depend on
+ the chosen `model` and are defined by the model registry. Omitted
+ options use the model's defaults. Unknown or invalid options are
+ rejected.
+ type: object
+ additionalProperties: true
+ example:
+ voice: Matthew
+ language: en-US
trim:
description: >-
The start trim point of the audio clip, in seconds (defaults to 0). Audio
diff --git a/schemas/imageasset.yaml b/schemas/imageasset.yaml
index a26018c..5ed108a 100644
--- a/schemas/imageasset.yaml
+++ b/schemas/imageasset.yaml
@@ -1,12 +1,16 @@
ImageAsset:
description: |
The ImageAsset adds an image to a Clip. The image can be sourced from a URL
- (`src`) or generated from a text prompt (`prompt`). Exactly one of `src` or
- `prompt` must be provided.
+ (`src`), generated from a text prompt (`prompt`), or both. At least one of
+ `src` or `prompt` must be provided.
- **Source URL:** set `src` to the publicly accessible URL of a jpg or png file.
- - **Generated:** set `prompt` to describe the image; the engine generates it
- using the provider chosen by `model` and fills `src` in automatically.
+ - **Generated:** set `prompt` to describe the image. Choose a generator with
+ `model` and configure it with model-specific `options`; the engine fills
+ `src` in automatically.
+ - **Both:** `src` acts as a preview placeholder while `prompt` drives
+ generation — the image is regenerated from the prompt at render time.
+ Unchanged prompts and options resolve from the generation cache.
type: object
properties:
type:
@@ -17,50 +21,39 @@
src:
description: >-
The image source URL. The URL must be publicly accessible or include
- credentials. Provide either `src` or `prompt`, not both.
+ credentials. When `prompt` is also set, `src` serves as a preview
+ placeholder and the image is regenerated from the prompt at render time.
type: string
minLength: 1
pattern: '\S'
example: https://s3-ap-northeast-1.amazonaws.com/my-bucket/image.jpg
prompt:
description: >-
- A text prompt to generate the image from. When set without `src`, the
- engine generates an image and fills `src` automatically. Use `model` to
- choose the generator.
+ A text prompt to generate the image from. The engine generates an image
+ at render time and fills `src` automatically; an existing `src` is
+ treated as a preview placeholder and replaced. Use `model` to choose
+ the generator and `options` to configure it.
type: string
maxLength: 4000
example: A serene landscape with a crystal-clear mountain lake at sunrise.
model:
description: >-
The generation model to use when `prompt` is set (e.g. `flux-schnell`,
- `fal/flux-schnell`, `fal/nano-banana-2`). Defaults to the platform's
- preferred generator if omitted.
+ `nano-banana-2`). Defaults to `nano-banana-2` if omitted. Each
+ model's available options are defined by the model registry.
type: string
example: flux-schnell
- resolution:
+ options:
description: >-
- Output resolution tier for supported image generation models.
- `1K` (default), `2K`, or `4K`. Only meaningful when `prompt` is set.
- enum:
- - '1K'
- - '2K'
- - '4K'
- type: string
- example: 1K
- aspectRatio:
- description: >-
- Aspect ratio for the generated image. Only meaningful when `prompt`
- is set and the model supports it.
- enum:
- - '1:1'
- - '16:9'
- - '9:16'
- - '4:3'
- - '3:4'
- - '21:9'
- - '9:21'
- type: string
- example: '1:1'
+ Model-specific generation settings. Valid keys and values depend on
+ the chosen `model` and are defined by the model registry. Omitted
+ options use the model's defaults. Unknown or invalid options are
+ rejected.
+ type: object
+ additionalProperties: true
+ example:
+ resolution: 1K
+ aspectRatio: '16:9'
crop:
$ref: "./crop.yaml#/Crop"
additionalProperties: false
diff --git a/schemas/responses/generationresponse.yaml b/schemas/responses/generationresponse.yaml
new file mode 100644
index 0000000..05837bd
--- /dev/null
+++ b/schemas/responses/generationresponse.yaml
@@ -0,0 +1,36 @@
+ GenerationResponse:
+ description: >-
+ The status of an on-demand asset generation job. Completed jobs include
+ the public URL of the generated asset.
+ type: object
+ properties:
+ id:
+ description: >-
+ The generation job id. Deterministic for a given owner and asset
+ payload (or idempotency key), so identical requests return the same
+ job and cached result.
+ type: string
+ example: 8a1f2c3d-4e5b-5a6c-9d7e-1f2a3b4c5d6e
+ status:
+ description: The status of the generation job.
+ type: string
+ enum:
+ - queued
+ - processing
+ - done
+ - failed
+ example: done
+ url:
+ description: >-
+ The public URL of the generated asset. Present only when `status`
+ is `done`.
+ type: string
+ example: https://shotstack-api-v1-output.s3-ap-southeast-2.amazonaws.com/owner/8a1f2c3d.png
+ error:
+ description: >-
+ A human readable error message. Present only when `status` is
+ `failed`.
+ type: string
+ required:
+ - id
+ - status
diff --git a/schemas/videoasset.yaml b/schemas/videoasset.yaml
index 72df2e5..1bb5505 100644
--- a/schemas/videoasset.yaml
+++ b/schemas/videoasset.yaml
@@ -1,14 +1,17 @@
VideoAsset:
description: |
The VideoAsset adds a video to a Clip. The video can be sourced from a URL
- (`src`) or generated from a text prompt (`prompt`), optionally from a
- starting image (`inputSrc`). Exactly one of `src` or `prompt` must be provided.
+ (`src`), generated from a text prompt (`prompt`), or both. At least one of
+ `src` or `prompt` must be provided.
- **Source URL:** set `src` to the URL of an mp4 (or compatible) video file.
- - **Generated:** set `prompt` to describe the motion. Optionally set `inputSrc`
- to a starting image URL (image-to-video). Use `model` to choose the generator
- (e.g. `luma-ray-3`, `runpod-itv-mini`). The generated `src` is filled in
+ - **Generated:** set `prompt` to describe the motion. Choose a generator
+ with `model` and configure it with model-specific `options` (including a
+ starting image for image-to-video). The generated `src` is filled in
automatically.
+ - **Both:** `src` acts as a preview placeholder while `prompt` drives
+ generation — the video is regenerated from the prompt at render time.
+ Unchanged prompts and options resolve from the generation cache.
type: object
properties:
type:
@@ -19,87 +22,42 @@
src:
description: >-
The video source URL. The URL must be publicly accessible or include
- credentials. Provide either `src` or `prompt`, not both.
+ credentials. When `prompt` is also set, `src` serves as a preview
+ placeholder and the video is regenerated from the prompt at render time.
type: string
minLength: 1
pattern: '\S'
example: https://s3-ap-northeast-1.amazonaws.com/my-bucket/video.mp4
prompt:
description: >-
- A text prompt to generate the video from. When set without `src`, the
- engine generates a video and fills `src` automatically. Optionally pair
- with `inputSrc` for image-to-video. Use `model` to choose the generator.
+ A text prompt to generate the video from. The engine generates a video
+ at render time and fills `src` automatically; an existing `src` is
+ treated as a preview placeholder and replaced. Use `model` to choose
+ the generator and `options` to configure it (including a starting
+ image for image-to-video).
type: string
maxLength: 4000
example: Slowly zoom out and orbit left around the object.
- inputSrc:
- description: >-
- Input image URL for image-to-video generation. The image is used as the
- starting frame; `prompt` describes the motion. Has no effect unless
- `prompt` is set.
- type: string
- minLength: 1
- example: https://s3-ap-northeast-1.amazonaws.com/my-bucket/input-image.jpg
model:
description: >-
- The generation model to use when `prompt` is set (e.g. `luma-ray-3`,
- `runpod-itv-mini`, `fal/seedance-2.0`). Defaults to the platform's
- preferred generator if omitted.
+ The generation model to use when `prompt` is set (e.g.
+ `shotstack-itv-mini`, `ray-flash-2`, `seedance-2.0`). Defaults to
+ `seedance-2.0` if omitted. Each model's available options are
+ defined by the model registry.
type: string
- example: luma-ray-3
- resolution:
+ example: seedance-2.0
+ options:
description: >-
- Output resolution for video generation. Only meaningful when `prompt`
- is set and the model supports it (e.g. `fal/seedance-2.0`).
- enum:
- - '480p'
- - '720p'
- - '1080p'
- type: string
- example: '720p'
- duration:
- description: >-
- Target video duration in seconds for generation models that accept
- it. `"auto"` lets the model decide. Only meaningful when `prompt`
- is set.
- enum:
- - 'auto'
- - '4'
- - '5'
- - '6'
- - '7'
- - '8'
- - '9'
- - '10'
- - '11'
- - '12'
- - '13'
- - '14'
- - '15'
- type: string
- default: auto
- example: '5'
- aspectRatio:
- description: >-
- Aspect ratio for the generated video. Only meaningful when `prompt`
- is set and the model supports it.
- enum:
- - '1:1'
- - '16:9'
- - '9:16'
- - '4:3'
- - '3:4'
- - '21:9'
- - '9:21'
- type: string
- example: '16:9'
- generateAudio:
- description: >-
- Set to `true` to request audio generation alongside the video. Only
- meaningful for video generation models that support it (e.g.
- `fal/seedance-2.0`).
- type: boolean
- default: false
+ Model-specific generation settings. Valid keys and values depend on
+ the chosen `model` and are defined by the model registry. Omitted
+ options use the model's defaults. Unknown or invalid options are
+ rejected.
+ type: object
+ additionalProperties: true
+ example:
+ resolution: 720p
+ duration: '8'
+ generateAudio: true
transcode:
type: boolean
description: >-
diff --git a/tests/smoke.cjs b/tests/smoke.cjs
index ce5c651..118a02c 100644
--- a/tests/smoke.cjs
+++ b/tests/smoke.cjs
@@ -198,27 +198,27 @@ async function run() {
assert.strictEqual(result.model, "flux-schnell");
});
- check("Parse videoAsset with prompt + inputSrc", () => {
+ check("Parse videoAsset with prompt + options.inputSrc", () => {
const result = zodCjs.videoAssetSchema.parse({
type: "video",
prompt: "Slowly zoom out and orbit left around the object",
- inputSrc: "https://example.com/input-image.jpg",
+ options: { inputSrc: "https://example.com/input-image.jpg" },
});
assert.strictEqual(result.prompt, "Slowly zoom out and orbit left around the object");
- assert.strictEqual(result.inputSrc, "https://example.com/input-image.jpg");
+ assert.strictEqual(result.options.inputSrc, "https://example.com/input-image.jpg");
});
- check("Parse videoAsset with prompt + inputSrc + model", () => {
+ check("Parse videoAsset with prompt + model + options", () => {
const result = zodCjs.videoAssetSchema.parse({
type: "video",
prompt: "Camera pans right",
- inputSrc: "https://example.com/input.jpg",
- model: "luma-ray-3",
+ model: "seedance-2.0",
+ options: { inputSrc: "https://example.com/input.jpg" },
});
- assert.strictEqual(result.model, "luma-ray-3");
+ assert.strictEqual(result.model, "seedance-2.0");
});
- check("REJECT videoAsset with removed `seed` field (use inputSrc instead)", () => {
+ check("REJECT videoAsset with removed `seed` field (generation inputs live in options)", () => {
assert.throws(() =>
zodCjs.videoAssetSchema.parse({
type: "video",
@@ -228,26 +228,24 @@ async function run() {
);
});
- check("Parse audioAsset with prompt + voice", () => {
+ check("Parse audioAsset with prompt + options.voice", () => {
const result = zodCjs.audioAssetSchema.parse({
type: "audio",
prompt: "This is a text-to-speech example",
- voice: "Matthew",
+ options: { voice: "Matthew" },
});
assert.strictEqual(result.prompt, "This is a text-to-speech example");
- assert.strictEqual(result.voice, "Matthew");
+ assert.strictEqual(result.options.voice, "Matthew");
});
- check("Parse audioAsset with prompt + voice + language + newscaster", () => {
+ check("Parse audioAsset with speech options (voice, language, newscaster)", () => {
const result = zodCjs.audioAssetSchema.parse({
type: "audio",
prompt: "Breaking news from around the world",
- voice: "Matthew",
- language: "en-US",
- newscaster: true,
+ options: { voice: "Matthew", language: "en-US", newscaster: true },
});
- assert.strictEqual(result.language, "en-US");
- assert.strictEqual(result.newscaster, true);
+ assert.strictEqual(result.options.language, "en-US");
+ assert.strictEqual(result.options.newscaster, true);
});
check("Parse audioAsset with prompt only (music/SFX generator)", () => {
@@ -289,14 +287,15 @@ async function run() {
console.log("\n--- Unified asset: src-or-prompt rule (ADR 0001) ---\n");
- // At-least-one: src OR prompt required on image/video/audio. Both allowed.
- // Neither → rejected. inputSrc/voice are modifiers and never satisfy the rule.
+ // At-least-one: src OR prompt required on image/video/audio. Both allowed:
+ // src is a preview placeholder, prompt regenerates at render.
+ // Neither → rejected. The options object never satisfies the rule.
check("image with src only is valid", () => {
zodCjs.imageAssetSchema.parse({ type: "image", src: "https://example.com/a.jpg" });
});
- check("image with both src and prompt is valid (src wins downstream)", () => {
+ check("image with both src and prompt is valid (src previews; prompt regenerates at render)", () => {
zodCjs.imageAssetSchema.parse({
type: "image",
src: "https://example.com/a.jpg",
@@ -304,6 +303,40 @@ async function run() {
});
});
+ check("image accepts model-scoped options object", () => {
+ zodCjs.imageAssetSchema.parse({
+ type: "image",
+ prompt: "a serene lake",
+ model: "flux-schnell",
+ options: { resolution: "2K", aspectRatio: "16:9" },
+ });
+ });
+
+ check("video accepts model-scoped options object", () => {
+ zodCjs.videoAssetSchema.parse({
+ type: "video",
+ prompt: "slow orbit",
+ options: { duration: "8", generateAudio: true },
+ });
+ });
+
+ check("REJECT removed flat generation field on video", () => {
+ assert.throws(() => zodCjs.videoAssetSchema.parse({
+ type: "video",
+ prompt: "slow orbit",
+ resolution: "720p",
+ }));
+ });
+
+ check("audio accepts speech options", () => {
+ zodCjs.audioAssetSchema.parse({
+ type: "audio",
+ prompt: "Welcome to the show",
+ model: "polly-neural",
+ options: { voice: "Matthew", language: "en-US" },
+ });
+ });
+
check("REJECT image with neither src nor prompt", () => {
assert.throws(() => zodCjs.imageAssetSchema.parse({ type: "image" }));
});
@@ -316,9 +349,9 @@ async function run() {
assert.throws(() => zodCjs.videoAssetSchema.parse({ type: "video" }));
});
- check("REJECT video with inputSrc but no src and no prompt (inputSrc is a modifier)", () => {
+ check("REJECT video with only options and no src/prompt (options never satisfies the rule)", () => {
assert.throws(() =>
- zodCjs.videoAssetSchema.parse({ type: "video", inputSrc: "https://example.com/input.jpg" })
+ zodCjs.videoAssetSchema.parse({ type: "video", options: { inputSrc: "https://example.com/input.jpg" } })
);
});
@@ -328,12 +361,12 @@ async function run() {
);
});
- check("REJECT audio with voice but no src and no prompt (voice is a modifier)", () => {
- assert.throws(() => zodCjs.audioAssetSchema.parse({ type: "audio", voice: "Matthew" }));
+ check("REJECT audio with only options and no src/prompt (options never satisfies the rule)", () => {
+ assert.throws(() => zodCjs.audioAssetSchema.parse({ type: "audio", options: { voice: "Matthew" } }));
});
- check("audio with prompt + voice is valid (text-to-speech shape)", () => {
- zodCjs.audioAssetSchema.parse({ type: "audio", prompt: "hello", voice: "Matthew" });
+ check("audio with prompt + options voice is valid (text-to-speech shape)", () => {
+ zodCjs.audioAssetSchema.parse({ type: "audio", prompt: "hello", options: { voice: "Matthew" } });
});
console.log("\n--- Deprecation markers in bundled spec ---\n");