Reference
All endpoints are under https://clipwave.ai/api/v1/.
All requests require Authorization: Bearer cw_live_.... See auth.md.
GET /api/v1/modelsList all video and image models exposed via the API, with their parameters and credit costs.
Required scope: models:read (default on all keys)
200 OK{
"models": [
{
"id": "seedance-2.0",
"type": "video",
"name": "Seedance 2.0",
"provider": "bytedance",
"description": "ByteDance unified audio-video, lip-sync, cinematic quality",
"capabilities": ["image-to-video", "text-to-video"],
"fal_endpoints": { ... },
"params": { ... },
"credits": { "base_per_second": 227, "per_second_by_resolution": { "480p": 101, "720p": 227 }, "audio_surcharge_per_second": 0, "example_5s_with_audio": 1135 }
},
{ "id": "seedance-2.0-fast", ... },
{ "id": "nano-banana-2", ... }
]
}
POST /api/v1/video/seedance-2/generateSubmit a Seedance 2.0 video generation. Async — returns a job_id immediately. Poll /api/v1/jobs/:id for status + result.
Required scope: video:write
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
prompt | string | yes | — | 1-4000 chars. See Seedance 2.0 prompt grammar below. |
image_url | string (https URL) | no | — | If provided, runs image-to-video. Mutually exclusive with fast: true. |
fast | boolean | no | false | Use Seedance 2.0 Fast (text-to-video only, ~50% cost). |
duration_seconds | integer | no | 5 | One of: 4, 5, 6, 8, 10, 15. |
aspect_ratio | string | no | "16:9" | One of: 16:9, 9:16, 1:1, 4:3, 3:4, 21:9. |
resolution | string | no | "720p" | One of: 480p, 720p. |
generate_audio | boolean | no | true | Native SFX/ambient generation. |
seed | integer | no | random | For reproducibility. |
202 Accepted{
"job_id": "ab12cdef",
"status": "processing",
"model": "seedance-2.0",
"credits_consumed": 75,
"credits_remaining": 1925,
"poll_url": "/api/v1/jobs/ab12cdef"
}
| Status | Cause |
|---|---|
400 | Validation error (missing prompt, invalid duration, etc.) |
401 | Missing/invalid API key |
402 | Insufficient credits — body includes credits_required and credits_remaining |
403 | Missing scope video:write |
429 | Rate limit exceeded |
502 | Provider (fal.ai) submission error — credits automatically refunded |
POST /api/v1/image/nano-banana/generateGenerate or edit images with Nano Banana 2. Async, same lifecycle as video.
Required scope: image:write
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
prompt | string | yes | — | 1-4000 chars |
image_urls | string[] | no | [] | 1-9 reference URLs. If non-empty, runs Nano Banana 2 Edit. |
num_images | integer | no | 1 | 1-4 |
aspect_ratio | string | no | "1:1" | One of: 1:1, 16:9, 9:16, 4:3, 3:4 |
seed | integer | no | random |
202 Accepted{
"job_id": "ef34gh56",
"status": "processing",
"model": "nano-banana-2",
"credits_consumed": 50,
"credits_remaining": 1950,
"poll_url": "/api/v1/jobs/ef34gh56"
}
Cost: 50 credits × num_images.
GET /api/v1/jobs/:idGet the status and result of a generation job.
Required scope: any (key must own the job — checked against api_key_id → user_id)
200 OKWhile processing:
{
"job_id": "ab12cdef",
"status": "processing",
"model": "Seedance 2.0",
"type": "video",
"credits_consumed": 75,
"created_at": "2026-05-07T15:42:11Z",
"completed_at": null,
"result": null,
"error": null
}
When completed (video):
{
"job_id": "ab12cdef",
"status": "completed",
"model": "Seedance 2.0",
"type": "video",
"credits_consumed": 75,
"created_at": "2026-05-07T15:42:11Z",
"completed_at": "2026-05-07T15:42:51Z",
"result": { "video": "https://v3.fal.media/files/.../result.mp4" },
"error": null
}
When completed (image):
{
"result": { "images": ["https://...", "https://..."] }
}
When failed (credits already refunded):
{
"status": "failed",
"error": "Provider timeout after 600000ms",
"result": null
}
| Status | Cause |
|---|---|
404 | Job not found (or doesn't belong to your key's user) |
DELETE /api/v1/jobs/:idCancel an in-progress job. Refunds credits.
Required scope: any (key must own the job)
200 OK{
"job_id": "ab12cdef",
"status": "cancelled",
"refunded_credits": 75
}
| Status | Cause |
|---|---|
404 | Job not found |
409 | Job already completed/failed/cancelled (cannot cancel) |
Project-scoped, chat-driven video editor. A project owns a media library + context + N videos. Each video has its own multi-track timeline (video / voice / sfx / music) and chat thread, all sharing the project's library + context.
Required scopes:
video-editor:read — list projects, fetch statevideo-editor:write — create projects, drive the agent, exportThe agent runs synchronously server-side, one step per call (at most one media generation per request). After each call, the agent's reply usually ends with a confirmation question; send the user's answer (or "yes, continue") as the next instruction. Drive in a loop until the timeline is the way you want it, then call export.
POST /api/v1/media-studio/projectsCreate a new editor project. A default video named "Main video" is created automatically.
Body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
name | string | no | "Untitled project" | 1-120 chars |
description | string | no | — | Up to 1000 chars |
aspect_ratio | string | no | "9:16" | One of: 9:16, 1:1, 4:5, 16:9, 4:3 |
Response — 200 OK
{
"project": {
"id": "vp_abc123",
"name": "Cozy candle UGC",
"aspect_ratio": "9:16",
"context_md": "",
"created_at": "2026-05-31T...",
"updated_at": "2026-05-31T..."
}
}
GET /api/v1/media-studio/projectsList every editor project owned by this API key's user.
Response — 200 OK
{
"projects": [
{ "id": "vp_abc123", "name": "...", "aspect_ratio": "9:16", "updated_at": "..." }
]
}
GET /api/v1/media-studio/projects/:idFull project state — library + every video + every clip on every timeline.
Query
| Param | Notes |
|---|---|
video_id | Optional — narrow clips to a single video. Without it, clips for every video in the project are returned. |
Response — 200 OK
{
"project": { "id": "vp_abc123", "name": "...", "aspect_ratio": "9:16", "context_md": "..." },
"videos": [
{ "id": "vv_abc123", "name": "Main video", "aspect_ratio": "9:16", "created_at": "..." }
],
"assets": [
{ "id": "va_...", "kind": "image", "source": "agent_generate", "url": "https://...", "metadata": { "prompt": "...", "model": "nano-banana-2" } }
],
"clips": [
{ "id": "vc_...", "video_id": "vv_abc123", "track": "video", "idx": 0, "asset_id": "va_..." }
]
}
POST /api/v1/media-studio/projects/:id/instructDrive the editor agent for ONE turn. The agent decides whether to plan, ask a question, generate media, or add to the timeline — but does at most one media-generation per call.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
instruction | string | yes¹ | 1-8000 chars. Plain-English ("Plan a 3-scene 9:16 ad for cozy candles, start with scene 1's keyframe.") or a reply ("yes, continue"). |
image_urls | string[] | no | Up to 10 reference image URLs for this turn (the agent sees them as Claude vision blocks). |
video_id | string | no | Target a specific video inside the project. Defaults to the project's first/default video. |
¹ instruction or image_urls must be non-empty.
Response — 200 OK
{
"assistant_text": "Here's the plan…\n\nReady to generate scene 1's keyframe?",
"assets_created": [
{ "id": "va_...", "kind": "image", "url": "https://...", "metadata": { ... } }
],
"clips_created": [
{ "id": "vc_...", "track": "video", "idx": 0, "asset_id": "va_..." }
],
"context_appended": false,
"aspect_ratio_changed": null,
"media_gen_done": true,
"agent_events": [ ... ]
}
Each agent turn costs 5 credits (charged per turn, no refund — the turn itself consumes LLM compute) plus any media-generation costs. Media-generation charges are refunded automatically if the generation is not delivered (provider submit/poll failure or empty result). Latency is typically 5-180s (image gen ~15s, video gen up to ~5 min).
Errors
| Status | Cause |
|---|---|
400 | Invalid JSON / empty instruction |
402 | Insufficient credits for the agent or its tool calls |
502 | Agent runtime error (Anthropic API down, fal.ai failure, etc.) |
503 | ANTHROPIC_API_KEY not configured on the server |
POST /api/v1/media-studio/projects/:id/exportStitch the project's multi-track timeline (video + voice + sfx + music) into a single mp4 via ffmpeg. Synchronous — blocks until the encode + S3 upload complete (typically 30s-3min depending on clip count + duration).
Body
| Field | Type | Required | Notes |
|---|---|---|---|
video_id | string | no | Export a specific video inside the project. Defaults to the project's first/default video. |
fit | "fit" | "fill" | no | How to handle clips whose aspect doesn't match the project. "fit" (default) letterboxes/pillarboxes with black bars. "fill" scales-and-crops to fill the frame. |
Response — 200 OK
{
"asset_id": "va_...",
"video_url": "https://clipwave.ai/api/media-studio/asset/.../final.mp4",
"duration_sec": 14.5,
"video_clip_count": 3,
"audio_overlay_count": 2
}
The video_url is a permanent Clipwave-hosted proxy URL — downloadable, embeddable, and visible in the dashboard Media tab as a new "exported" asset.
Errors
| Status | Cause |
|---|---|
400 | No video clips on the timeline |
404 | Project not found / not owned by this API key |
500 | ffmpeg failure or S3 upload failure |
The same five operations are exposed as MCP tools via the @clipwave/mcp package:
| MCP tool | REST endpoint |
|---|---|
video_editor_create_project | POST /projects |
video_editor_list_projects | GET /projects |
video_editor_get_state | GET /projects/:id |
video_editor_instruct | POST /projects/:id/instruct |
video_editor_export | POST /projects/:id/export |
See mcp-server/README.md for the typical end-to-end flow from Claude Code / Cursor.
Configure a webhook URL per API key in /dashboard/api-keys → Webhook. Clipwave POSTs to that URL whenever a job submitted with the key transitions to completed, failed, or cancelled. No need to poll /api/v1/jobs/:id.
Request
POST <your_webhook_url>
Content-Type: application/json
User-Agent: Clipwave-Webhooks/1.0
X-Clipwave-Event: job.completed | job.failed
X-Clipwave-Timestamp: 1717012345
X-Clipwave-Signature: t=1717012345,v1=<hex hmac sha256>
Body
{
"job_id": "ab12cdef",
"status": "completed",
"model": "fal-ai/bytedance/seedance/v2.0/image-to-video",
"type": "video",
"credits_consumed": 75,
"created_at": "2026-05-31T...",
"completed_at": "2026-05-31T...",
"result": { "video": "https://v3.fal.media/..." },
"error": null
}
Verifying the signature (recommended, prevents replay + tampering):
import crypto from 'crypto';
const sig = req.headers['x-clipwave-signature'] as string; // "t=...,v1=..."
const ts = req.headers['x-clipwave-timestamp'] as string;
const body = await readRawBody(req); // pre-JSON-parse
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(`${ts}.${body}`).digest('hex');
const provided = sig.split(',').find(p => p.startsWith('v1='))?.slice(3) ?? '';
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided))) {
return res.status(401).end();
}
Retries: any non-2xx response (and 408/429) triggers retries after 30s, 2min, 10min. 4xx (other than 408/429) gives up after one attempt — fix your endpoint and re-trigger by re-submitting the job. Every attempt is logged to api_requests (visible under your key's usage panel).
Signing secret: generated on first enablement, shown ONCE in the dashboard. Rotate by clearing the URL → save → re-entering the URL → save.
The model rewards stage directions, not adjectives. Use this template:
Total: <duration>s / <shot count> shot(s) / <aspect ratio>
Shot 1: <camera position> <camera move> <subject action>. <Lens/lighting note>.
Shot 2: <…>
Audio: <SFX list> | <Ambient> | <Music intent or "NO MUSIC">
Camera moves (one per shot, never combine):
Audio block is mandatory for best results. Format: Audio: SFX <list>, ambient <description>. Music: <intent | NO MUSIC>.
Prefer not to hand-build prompts at all? Connect your AI assistant to the hosted MCP server — it self-briefs on connect (prompting rules included) and exposes generation plus the full Media Studio editor as native tools.