API Documentation
Generate videos, static image ads, and downloadable audio (MP3) programmatically via REST API, or connect Wavemaker as an MCP tool in Cursor, Claude Desktop, or any MCP-compatible client.
Platform API & MCP (Comfy-native)
A Comfy-native product surface for chat-first workflows on Modal GPUs — Discover
catalog, dual-meter runs (GPU + partner APIs), and a focused MCP tool surface over
POST /mcp on the platform-api Worker. This is the
live integration path. The managed video API (/api/v1/videos)
and OAuth MCP at
https://api.wavemaker.io/mcp are retired (HTTP 410).
Hosts (do not mix)
- Managed media —
https://api.wavemaker.io→/api/v1/*+ OAuth/mcp(sections below). - Platform Comfy — platform-api Worker (
$PLATFORM_API_URL): localhttp://127.0.0.1:8790; stagingPUBLIC_PLATFORM_API_URL(workers.dev until custom host). Paths:/discover,/runs, BearerPOST /mcp.
Contracts
-
OpenAPI is the machine-readable source of truth (
docs/platform/api/openapi.yaml); publicGET /openapi.jsonon this host (no admin paths). The human reference (reference.md) and MCP tool inventory (mcp-reference.md) are generated from it and drift-gated in CI. -
Deeper guides live under
docs/platform/guides/api-mcp.mdanddocs/platform/billing-rates.md. - Product UI: /platform/marketing · App shell: /platform
REST happy path
-
GET /discoverorGET /discover/collections/{id}?resolve=1 -
GET /workflows/{id}orGET /discover/workflows/{slug}→apiGraph+ promoted inputs -
POST /runs/preflight— GPU + partner line items -
POST /runs→ pollGET /runs/{id} -
Outputs:
GET /files/{id}+/content
CI executes this sequence as
pnpm platform:quickstart (fixture mode by default; optional live
test org with
PLATFORM_QUICKSTART_LIVE=1 +
$PLATFORM_API_URL /
$PLATFORM_API_TOKEN). See
docs/platform/guides/api-mcp.md § CI-executed REST quickstart.
curl -X POST $PLATFORM_API_URL/runs \
-H "Authorization: Bearer <platform_jwt>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"workflowId": "<uuid>",
"graph": { ... },
"inputs": { "prompt": "editorial lookbook" },
"gpuClass": "l40s"
}' curl -X POST $PLATFORM_API_URL/runs/preflight \
-H "Authorization: Bearer <platform_jwt>" \
-H "Content-Type: application/json" \
-d '{ "graph": { ... }, "gpuClass": "l40s" }' curl "$PLATFORM_API_URL/discover?q=fashion&limit=5" \
-H "Authorization: Bearer <platform_jwt>"
Platform MCP (POST $PLATFORM_API_URL/mcp)
JSON-RPC 2.0 over HTTPS with a Bearer org JWT against the platform-api host — never
the managed OAuth URL. Session-bound tools accept
sessionId in arguments or
X-Platform-Session-Id. Methods:
initialize, tools/list,
tools/call.
curl -X POST $PLATFORM_API_URL/mcp \
-H "Authorization: Bearer <platform_jwt>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}' Connect your client
Mint a wm_ API key in the app (Account → Keys), then paste one of
these. Unlike raw generation APIs, every deliverable your agent lists here already passed the platform's
QC gates — you browse reviewed deliverables, not raw generations.
claude mcp add wavemaker-platform \
--transport http $PLATFORM_API_URL/mcp \
--header "Authorization: Bearer wm_<your_api_key>" {
"mcpServers": {
"wavemaker-platform": {
"url": "$PLATFORM_API_URL/mcp",
"headers": { "Authorization": "Bearer wm_<your_api_key>" }
}
}
} // .cursor/mcp.json
{
"mcpServers": {
"wavemaker-platform": {
"url": "$PLATFORM_API_URL/mcp",
"headers": { "Authorization": "Bearer wm_<your_api_key>" }
}
}
} ChatGPT → Settings → Connectors → Developer mode →
Add server: $PLATFORM_API_URL/mcp
Auth: custom header — Authorization: Bearer wm_<your_api_key> | Tool | Purpose |
|---|---|
search_workflows | Hybrid catalog search |
get_workflow | Detail by workflowId or slug |
apply_workflow | Load graph into a session |
set_promoted_inputs | Fill guided-run inputs |
edit_graph | Typed graph ops (never raw JSON patch) |
compose_workflows | Multi-workflow splice plan |
run_workflow | Cost preflight + submit |
get_run / list_runs / cancel_run | Run lifecycle |
list_run_groups / get_run_group | Segmented progress (directed video, long-form film) |
list_creations | Browse reviewed deliverables (succeeded runs + files) |
list_flows / run_flow | Org-saved Flows — one-call re-runs of proven settings |
generate | Prompt → top catalog hit (selection) |
list_models / list_partner_nodes | Registry + Wavemaker* nodes |
list_collections / get_collection | Curated Discover sets |
get_file / get_wallet | Outputs + credit balances |
get_session / list_sessions | Compact graph index (no full SessionDO dump) |
iterate_resolve / iterate_execute | Gallery verbs; face_swap needs likenessConsent assertion |
remix_workflow / save_flow | Remix a public workflow; save a version-pinned Flow |
list_creative_packs / engage_creative_pack | Creative packs (toolset=packs) |
engage_campaign / get_campaign | DCO: feed × template, 24-variant cap (toolset=campaigns) |
reassemble_run_group / adopt_run_group_candidate | Reorder/concat a terminal group; adopt a QC candidate |
mint_file_upload / get_generation_details | Presigned PUT + curl; library provenance |
Billing (dual meter)
Reserve = GPU estimate + partner call plan × headroom; settle = actual GPU-seconds + priced
partner ledger lines from the runner. generate (selection) is free;
run_workflow / POST /runs
bill. Details in docs/platform/billing-rates.md and on the
pricing page.
Catalog Video (DCO)
Feed file × template → up to 24 variants on one
dco-campaign FanOut × SubWorkflow (campaignId = runGroupId). Dashboard:
/platform/campaigns
(status + download). Always mounted — no dcoEnabled
kill. Retired /api/v1/dco/* returns
HTTP 410. Help:
Catalog Video docs.
Body: feedFileId,
template
(baseSlug static-ads |
ads-catalog-video,
prompt with
{{title}} slots, optional
formatIds /
aspectRatio /
styleId), optional
rowIds /
sessionId. Feed validation + image ingest run before reserve. Over 24 variants →
422 campaign_variant_cap_exceeded. MCP twin: engage_campaign.
Org campaign list (dashboard). Not an MCP tool — agents use
get_campaign after engage.
Run-group projection plus SKU-keyed variant statuses/outputs. MCP:
get_campaign. Files via
GET /files/{id}.
api.wavemaker.io /api/v1/* + OAuth /mcp).
These endpoints have been retired and now return HTTP 410 Gone — the reference below is kept for historical context only. Build on the
Platform API & MCP
above ($PLATFORM_API_URL origin-rooted paths, key prefix
wm_, tools-only MCP at /mcp), which is the only supported integration surface.
Authentication
All REST endpoints require an API key passed as a Bearer token. Create and manage keys
from your
dashboard API Keys page. The MCP endpoint at /mcp uses OAuth 2.1 instead —
see
MCP Integration.
API Key Format
Keys are prefixed with mcp_ and are at least 32 characters
long. Store them securely — they cannot be retrieved after creation. Only the last
4 characters remain visible in the dashboard (e.g. mcp_...a1b2). API key creation requires a paid plan (Pro or higher) with api_access.
Rate Limits
30 requests per minute per API key on
/api/v1/* and /mcp
(rolling 60-second window). Rate-limited responses include
X-RateLimit-Remaining and
X-RateLimit-Reset (Unix seconds);
429 adds a Retry-After header.
/assets/sign is auth-protected but not rate-limited.
Authorization: Bearer mcp_your_api_key_here
Bind an API key to an organization
Every generation / refinement / workspace-tool / MCP generate dispatch requires a
billing organization. A key with neither
user_id nor
organization_id is rejected before dispatch with
402 org_required. New keys minted from
Settings → API keys while an org is active carry
both. User-scoped keys without
organization_id resolve the user's default org at dispatch; if that
is also missing you get the same 402 with
code: "NO_BILLING_ORG" — sign in once to initialize the workspace.
Fully unscoped ops keys must be re-issued under an org by an admin.
Quick Start (legacy — retired)
These /api/v1 calls now return
HTTP 410 Gone. For a working quickstart, submit a run against the Platform API (POST $PLATFORM_API_URL/runs with a
wm_ key, then poll
GET /runs/{runId}). The three steps below are
retained only to show the shape of the retired managed flow.
curl -X POST https://api.wavemaker.io/api/v1/videos \
-H "Authorization: Bearer mcp_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Create a 30-second ad for a coffee shop in Brooklyn",
"aspect_ratio": "16:9",
"duration": 30
}' curl https://api.wavemaker.io/api/v1/videos/wf_abc123def456 \ -H "Authorization: Bearer mcp_your_api_key"
curl https://api.wavemaker.io/api/v1/videos/wf_abc123def456/composition \ -H "Authorization: Bearer mcp_your_api_key"
Or use webhooks to get notified automatically when the video is ready.
Videos
Start a new video generation job. The AI analyzes your prompt, plans scenes, generates visuals, voiceover, and music, then composes a final video. Costs credits based on complexity.
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | Required | Description of the video to generate. Min 3 characters. May include a URL to scrape brand assets and content from. |
| aspect_ratio | string | Optional | 16:9 (default), 9:16, 1:1, 4:5,
or 2:3 |
| duration | number | Optional | Target duration in seconds (e.g. 30, 60). Drives credit reserve and is injected into the planner's system prompt via a
non-negotiable duration directive so the final composition lands within
±1 s of the target. |
| platform | string | Optional | Target platform hint for the planner (e.g. youtube, instagram, tiktok, tv_ctv). Influences pacing, format, and
production elements. |
| style | string | Optional | Visual style preference passed to the planner (e.g. cinematic, playful animated 2D). |
| webhook_url | string | Optional | HTTPS URL to receive a POST when the job completes, is cancelled, or fails. See Webhooks. Private IPs, credentials in the URL, and non-HTTPS schemes are rejected with 400. |
| webhook_secret | string | Optional | Secret used to sign every webhook delivery for this job via HMAC-SHA256. The
receiver verifies the X-Webhook-Signature: v1,<hex-hmac> header against <X-Webhook-ID>.<X-Webhook-Timestamp>.<raw body>. |
202 Job accepted (Retry-After: 10)
{
"job_id": "wf_abc123def456789",
"status": "running",
"message": "Video generation started. Poll GET /api/v1/videos/{job_id} for status, or stream GET /api/v1/videos/{job_id}/events for real-time progress.",
"webhook_url": "https://your-server.com/webhook"
} job_id is prefixed with wf_ followed by a 32-character hex string. Keep it; you'll need it for all subsequent calls.
400 Invalid JSON, missing/short prompt,
unknown aspect_ratio, or invalid webhook_url
402 Insufficient credits, CREDITS_OWED from a prior settlement upcharge, or NO_BILLING_ORG (the
key's account has no organization yet — sign in to the dashboard once to initialize
it). Response includes code and details.
429 Rate limit or monthly quota exceeded
(Retry-After on rate limit only)
503 Inngest dispatch unavailable (Retry-After: 60)
Check the status and progress of a video generation job. Returns an
ETag header for conditional requests, and
Retry-After while the job is still running (3s when progress_pct > 70,
8s otherwise).
200 Job status
{
"job_id": "wf_abc123def456789",
"status": "running",
"phase": "generate_video_clip",
"progress_pct": 65,
"composition_id": "comp_xyz"
} composition_id, output, and
error are only present when populated.
phase is the name of the most recently-started tool or
phase event (e.g. generate_image, compose_scenes).
| Status | Description |
|---|---|
| running | Accepted or actively generating. Check phase and
progress_pct. |
| complete | Done. composition_id and output are populated. |
| errored | Failed. See error field. May still expose a partial
composition_id if emergency-save fired. |
Stream real-time progress as Server-Sent Events. The stream emits one event per workflow event (with the workflow's event_type
as the SSE event: field) plus periodic : keepalive
comments, and terminates with a final complete,
errored, or timeout event.
Maximum stream duration is 30 minutes; reconnect with ?last_event_id= to resume.
| Event | Description |
|---|---|
| phase | Workflow phase label (e.g. planning, generating media) |
| tool_start | A tool started. detail.sceneId is set for per-scene
work. |
| tool_complete | A tool finished. detail.assetType = image / video / voiceover / music. |
| tool_error | A tool failed. message contains the user-facing
reason. |
| thinking | Orchestrator reasoning chunk (when extended-thinking is on). |
| node_link | Lineage link between a parent tool call and its media child (used by the canvas UI). |
| complete | Final event — job succeeded. data.composition_id is the ID to fetch. |
| errored | Final event — job failed. data.error is the
reason. |
| Parameter | Type | Description |
|---|---|---|
| last_event_id | integer | Resume after this event ID (use the numeric id: from a previous SSE frame). |
SSE text/event-stream
id: 12
event: phase
data: {"id":12,"event_type":"phase","message":"Generating media","created_at":"2026-04-01T12:01:00Z","progress":35}
id: 13
event: tool_start
data: {"id":13,"event_type":"tool_start","tool_name":"generate_image","message":"generate_image","detail":{"sceneId":"scene-3"},"progress":40}
id: 14
event: tool_complete
data: {"id":14,"event_type":"tool_complete","tool_name":"generate_image","detail":{"sceneId":"scene-3","assetType":"image"},"progress":52}
: keepalive
event: complete
data: {"status":"complete","composition_id":"comp_xyz","output":{"compositionId":"comp_xyz"}}
Retrieve the full video composition JSON. :id accepts
either the wf_* job ID returned by
POST /api/v1/videos or a
composition_id (UUID) from a previous status response.
Contains scene definitions, style spec, audio tracks, and R2 asset keys (resolve via
POST /assets/sign).
200 Composition data
{
"composition_id": "comp_xyz",
"composition": {
"id": "comp_xyz",
"title": "...",
"aspectRatio": "16:9",
"totalDurationFrames": 900,
"fps": 30,
"scenes": [...],
"style": {
"colorPalette": {...},
"typography": {...},
"motionLanguage": "cinematic",
"visualMedium": "photorealistic"
},
"audio": {
"voiceover": {...},
"music": {...}
}
}
}
Generate signed public URLs for composition asset keys. Use this to resolve asset
references from the composition JSON into downloadable URLs. Up to 50 keys per request. For each image key, the response also includes _400w.jpg and
_800w.jpg variants when available; for each video key,
the
_web.mp4 and _preview.mp4
variants. Keys that don't exist in storage are silently omitted from the response (no 404).
{
"keys": ["assets/scene-1/image.png", "assets/scene-2/video.mp4"]
} 200
{
"urls": {
"assets/scene-1/image.png": "https://api.wavemaker.io/assets/...",
"assets/scene-1/image_400w.jpg": "https://api.wavemaker.io/assets/...",
"assets/scene-1/image_800w.jpg": "https://api.wavemaker.io/assets/...",
"assets/scene-2/video.mp4": "https://api.wavemaker.io/assets/...",
"assets/scene-2/video_web.mp4": "https://api.wavemaker.io/assets/...",
"assets/scene-2/video_preview.mp4": "https://api.wavemaker.io/assets/..."
}
} Static Image Ads
Generate platform-ready static image ads — or plain AI images. The default
purpose: "ad" plans the headline, CTA, and visual concept from
your brief (or a site URL's scraped brand facts), renders the ad with baked copy, quality-checks
it (auto-fixing failures once), and delivers the exact platform pixel size. purpose: "image" renders your prompt
as-is at the requested size — no ad copy, branding, or advertising rules
(a plain-image quality check still runs: prompt adherence + technical defects, one auto-fix).
Adult prompts are supported for purpose: "image" on entitled
accounts (paid plan + adult content enabled in the profile) — they auto-route to the adult
engines and the image_model pin is ignored; the ad purpose
is standard-content only.
One deliverable per call — make one call per ad/image you want, or set
carousel_cards for a narrative carousel. The same run also
works through the workspace tool rails (POST /api/v1/workspaces/:id/tools/generate_static_ad) and the MCP generate_static_ad tool.
Dispatch one static ad generation (or an N-card carousel). Charges a flat
8 credits per ad (carousel: 8 × cards). Async: poll the status URL,
or pass webhook_url for a signed
tool_run.completed push.
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | Required* | The creative brief. *Required unless site_url is
set. |
| site_url | string | Required* | Brand site URL — scraped for real brand facts (name, colors, logo).
*Required unless prompt is set. |
| purpose | string | Optional | ad (default — planned copy, branding, ad QC)
or image (plain image — the prompt renders
as-is, no ad framing). |
| image_model | string | Optional | Image model pin: gemini-image (Nano Banana, default),
seedream (Seedream 4.0), or xai (Grok Imagine). |
| platform | string | Optional | meta, google,
reddit, linkedin, x, tiktok,
pinterest, or universal (default). Drives per-platform compliance rules and defaults. Ad purpose only. |
| format | string | Optional | Ad format id from GET /api/v1/ad-formats
(e.g. square, story_916, landscape_191). Default: resolved from
aspect_ratio, else square. |
| aspect_ratio | string | Optional | Alternative to format: a ratio like "1:1", "9:16", "1.91:1". |
| style_id | string | Optional | Pin a visual style id from GET /api/v1/image-styles (GET /api/v1/ad-styles is a deprecated alias);
omit for AI selection. Must be an "ad"-capable
style when purpose is "ad", or a "plain"-capable style when
purpose is "image". |
| focus_hint | string | Optional | Extra creative direction (high priority). |
| prompt_mode | string | Optional | styled (default) uses the style-template planner;
free_form skips it. |
| carousel_cards | number | Optional | 2–6: generate a narrative carousel of N same-format cards (hook → problem → solution → proof → benefit → offer) instead of one ad. Priced per card. |
| webhook_url | string | Optional | HTTPS URL for a signed tool_run.completed /
tool_run.failed POST on terminal status. See Webhooks. |
| webhook_secret | string | Optional | Secret for the HMAC-SHA256 X-Webhook-Signature header
(same scheme as video webhooks). |
curl -X POST https://api.wavemaker.io/api/v1/static-ads \
-H "Authorization: Bearer mcp_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Bold launch ad for our espresso subscription",
"site_url": "https://example-coffee.com",
"platform": "meta",
"format": "square"
}' 202 Run accepted
{
"run_id": "wtr_abc123def456",
"credits_charged": 8,
"status": "pending",
"status_url": "/api/v1/static-ads/wtr_abc123def456"
} 400 Invalid JSON or input (neither
prompt nor site_url)
402 Insufficient credits
503 Dispatch infrastructure temporarily unreachable
Poll a static-ad run. While in flight the response is { run_id, status } (pending / running). A failed run returns 502
with status: "error" and an error reason. On completion each delivered variant carries the final image URL, exact pixel
size, ad copy, and its QC report. Single-ad runs also surface the one variant as
variant; carousels return all cards in
variants.
{
"run_id": "wtr_abc123def456",
"status": "complete",
"variant": {
"id": "adv_123",
"url": "https://api.wavemaker.io/assets/...",
"width": 1080,
"height": 1080,
"aspect_ratio": "1:1",
"format": "square",
"platforms": ["meta"],
"headline": "Espresso, Delivered.",
"cta": "Start Your Trial",
"qc": { "status": "passed", "issues": [] }
},
"variants": [ ... ]
}
Free catalog of ad formats and platform packs: each format's exact delivery pixels,
ratio, and the platforms it serves, plus each platform's recommended format pack. Use
a format id (or a platform) in POST /api/v1/static-ads.
Deprecated — superseded by GET /api/v1/image-styles below (the response carries a Link: rel="successor-version" header). Free catalog of ad-capable visual styles only (dark authority, testimonial spotlight,
minimalist hero, meme format, ...): id, name, use-case guidance, and platform compatibility.
Pass a style id as style_id to pin the look; omit for AI
selection. Both endpoints project off the same catalog, so a caller mid-migration never
sees drift between the two responses.
Free catalog of every Image Style (~110 seeded looks) — the successor to
/api/v1/ad-styles. Each row is { id, name, category, kind, applicable_purposes, use_case_guidance, platform_compatibility }. applicable_purposes governs where a
style_id is valid: an "ad"-capable style (kind: "ad_layout") pins
style_id on POST /api/v1/static-ads when purpose is the default "ad"; a "plain"-capable style (kind: "look") pins style_id when purpose: "image" (no ad copy). A style may declare both purposes. The MCP equivalent is the
list_image_styles tool.
Edit a delivered image (the site's iteration loop). Body:
action
(edit /
regenerate /
change_style /
adapt_ratio),
instruction (required for
edit),
style_id (for
change_style — from
GET /api/v1/image-styles),
target_format_id (for
adapt_ratio — from
/ad-formats), optional webhooks. Credits: edit 3; others 5.
Versions are append-only. MCP: edit_static_ad. Returns 202
with
run_id, credits_charged, and
status_url.
Set-level edit (action defaults to
set_edit): apply one
instruction across every non-approved image of the set (≤12
images; 5 credits × image). Returns 202 with
run_id, credits_charged, and
status_url.
Creative Packs
One brief → a coordinated multi-master deliverable. Shared brand-identity prep runs once,
then every master gets a full independent Plan+Produce run. Kinds:
multi_ar_pmax (4 aspect ratios for Performance Max / Demand Gen),
video_ad_sequence (YouTube bumper 6s + non-skippable 15s + CTV 30s),
hook_variant (one 15s body + 3–8 swapped 3s hooks), and
photo_set (10–20 stills + ZIP; standard content only). MCP:
creative_pack_generate /
creative_pack_status. Kill switch
creativePacksEnabled (503 when OFF). Full reference:
docs/public-api.md § Creative Packs.
Dispatch one pack. Idempotency-Key header required (unique per org) — identical
body replay returns the original run (200, Idempotent-Replayed); a different body under a reused key 409s. Body:
kind, brief (required),
hook_count (hook_variant, 3–8, default 5),
still_count / style_id (photo_set),
webhook_url. Whole-pack credits held up front and settled
per-master.
photo_set 403s NSFW-entitled callers toward
generate_static_ad.
curl -X POST https://api.wavemaker.io/api/v1/creative-packs \
-H "Authorization: Bearer mcp_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pack-order-8812" \
-d '{
"kind": "multi_ar_pmax",
"brief": "Performance Max pack for https://acme.com — free shipping CTA"
}' {
"pack_run": {
"pack_run_id": "cpr_…",
"kind": "multi_ar_pmax",
"status": "pending",
"credits_held": 120,
"master_count": 4,
"status_url": "/api/v1/creative-packs/cpr_…"
}
} Free catalog of the 4 pack kinds with their masters (role, aspect ratio, treatment id, duration / still count).
Poll. Overall status
(pending /
running /
succeeded /
partial /
failed /
cancelled;
credits_held /
credits_settled;
zip_url when a photo_set ZIP is built; each master's own status
+
result_urls.
Cancel an in-flight pack. Cancels child kernel runs, refunds unused hold (completed masters
keep their reserved share), marks the pack cancelled. No-op
once terminal.
Audio
First-class downloadable MP3 sets — not video soundtrack stems. Four purposes:
music, voice,
spot (radio/podcast ads with duration packs
:06/:15/:30/:60), and podcast
(topic episodes, 3–15 min, up to 2 hosts). Same rails as chat Audio mode. Also callable
through workspace tools (POST /api/v1/workspaces/:id/tools/generate_audio / edit_audio_set) and MCP. Kill switch
audioOutputModeEnabled — when OFF, generate/edit return
503 audio_output_mode_disabled; style/treatment catalogs stay
available. Full operator reference:
Audio generation (site guide) /
docs/audio-generation.md.
Dispatch one audio set. Credits are reserved from purpose × duration × variants
(see credits_charged). Async: poll the status URL, or pass
webhook_url for a signed
tool_run.completed push.
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | Required* | Creative brief. *Required unless brief is set. |
| brief | string | Optional | Alias for prompt. |
| purpose | string | Optional | music (default), voice, spot, or podcast. |
| duration_ids | string[] | Optional | Spot only: spot_6 /
spot_15 / spot_30 /
spot_60 (or bare 6 /
15 / …). Default spot_30. |
| variants_per_duration | number | Optional | A/B takes per Spot duration or Music take (1–3, default 1). |
| script_verbatim | boolean | Optional | When true, spoken words must not be rewritten (Voice/Spot/Podcast). |
| script_text | string | Optional | Spoken script / dialogue text. |
| site_url | string | Optional | Spot brand URL — grounded for name/CTA facts (no image refs). |
| target_duration_ms | number | Optional | Music / Voice / Podcast target length in ms. |
| speakers | object[] | Optional | Up to 2 speakers: { id, name, voice_id?, voice_description? }. |
| style_id | string | Optional | Pin from GET /api/v1/audio-styles /
list_audio_styles. |
| treatment_id | string | Optional | Pin from GET /api/v1/audio-treatments /
list_audio_treatments. |
| force_instrumental | boolean | Optional | Music: force instrumental (no vocals). |
| webhook_url | string | Optional | Signed tool_run.completed /
tool_run.failed delivery. |
| webhook_secret | string | Optional | HMAC secret for webhook verification. |
Example curl -X POST https://api.wavemaker.io/api/v1/audio \
-H "Authorization: Bearer mcp_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "30-second radio ad for https://acme.com — free shipping CTA",
"purpose": "spot",
"duration_ids": ["spot_15", "spot_30"],
"variants_per_duration": 2
}' Response 202
{
"run_id": "wtr_audio123",
"credits_charged": 39,
"status": "pending",
"status_url": "/api/v1/audio/wtr_audio123"
} GET /api/v1/audio/:id API Key
Poll a generate or edit run. In flight: { run_id, status }. Complete: audio_set_id +
variants[] (each with
url, mime_type: "audio/mpeg", duration, QC). Failed: 502.
Complete response {
"run_id": "wtr_audio123",
"status": "complete",
"audio_set_id": "auset_…",
"variants": [
{
"id": "auvar_…",
"url": "https://…/spot-15-a.mp3",
"mime_type": "audio/mpeg",
"duration_ms": 15000,
"duration_id": "spot_15",
"purpose": "spot",
"variant_index": 0,
"qc": { "status": "passed" }
}
]
} POST /api/v1/audio/sets/:set_id/actions API Key
Edit a delivered set via edit_audio_set. Actions:
regenerate (default),
change_style (requires
style_id),
change_voice,
rewrite_script,
remix_bed,
more_variants. Also
POST /api/v1/audio/variants/:variant_id/actions (path supplies
the variant). Distinct from video soundtrack tool
edit_audio.
Example curl -X POST https://api.wavemaker.io/api/v1/audio/sets/auset_…/actions \
-H "Authorization: Bearer mcp_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"action": "change_style",
"style_id": "aus_lofi",
"instruction": "softer bed, keep the VO"
}' GET /api/v1/audio-styles API Key
Free catalog of aus_* styles. Optional
?purpose=music|voice|spot|podcast. Available even when the
Audio kill switch is OFF. Sibling: GET /api/v1/audio-treatments.
Retired /api/v1/dco/* (sources, produce, manifest, VAST) returns
HTTP 410. Live DCO is
Catalog Video on platform-api (POST /campaigns/engage).
Video Upscaling
Super-resolve any external video to a higher resolution with audio preserved, via the
upscale_video tool on the workspace tool-run surface. Point
it at a public HTTPS URL of any duration — the system internally segments,
upscales, concatenates, and remuxes, so you make ONE call regardless of length. Async: poll
the run or set a
webhook_url. (Part of the retired managed API — see the
Platform API for the supported equivalent.)
Create a workspace once (POST /api/v1/workspaces),
then dispatch this tool. Returns a run_id + the dynamically-computed
credits_charged; poll GET /api/v1/tool-runs/:run_id
for result.output.download_url.
{ "wait": false, "input": { … } })
| Parameter | Type | Required | Description |
|---|---|---|---|
| video_url | string | Required | Public HTTPS URL of the source video (any duration; SSRF-validated). Max 2 h / 5 GB. |
| target_short_side | number | Optional | Output short side in px, 240–2160 (default 1080). Aspect ratio preserved. |
| preserve_audio | boolean | Optional | Mux the original audio into the output (default true). |
| quality | string | Optional | standard (default, Real-ESRGAN, cheapest), pro (Topaz, pro-grade, standard content only), or premium (SeedVR2, best for heavily degraded sources). |
| quality | ≤720 | ≤1080 | ≤2160 (4K) | + base |
|---|---|---|---|---|
| standard | 8 | 17 | 70 | +3 |
| pro | 45 | 45 | 140 | +4 |
| premium | 32 | 70 | 220 | +5 |
202 { "run_id": "wtr_…", "status": "pending", "credits_charged": N }
402 Insufficient credits
413 Source too long (>2 h) / too large (>5 GB)
422 Undecodable / undeterminable-duration media
Workflows as Endpoints
Every published workflow — yours or one from
Discover
— is a REST endpoint (POST /runs) and an MCP tool.
Retired POST /w/{slug}/runs
returns 410. Product landing:
/workflow-api.
Platform API & MCP section above.
Submit a run. Get an itemized quote first with POST /api/v1/workflows/:id/estimate (per-node credits, BYOK fees, and any creator premium). Poll
GET /api/v1/kernel-runs/:run_id for per-node progress,
costs, and artifact ids.
| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | object | Required | The workflow's declared inputs (typed; validated against the published spec). |
| version | number | Optional | Pin a specific published version (default: latest published). |
| run_to_node | string | Optional | Partial execution — run only up to this node. |
| binding_overrides | object | Optional | Per-run capability overrides (e.g. route a step to your own BYOK credential where the creator allows it). |
| metadata | object | Optional | Your opaque attribution data (≤16 string entries; keys ≤64 / values ≤256 chars). Echoed on every read and in webhook payloads. |
| webhook_url | string | Optional | HTTPS URL for signed lifecycle webhooks (kernel_run.completed|failed|cancelled|awaiting_approval) — same signature scheme as all Wavemaker webhooks. |
| webhook_secret | string | Optional | HMAC secret for the X-Webhook-Signature header (requires
webhook_url). |
Training Studio
Train your own model assets — image/video LoRAs and voice clones — from datasets you
curate, then bind them into workflows. The full loop is REST (and MCP: create_dataset, add_dataset_items, pick_checkpoint, cancel_training_job). Product guide:
Training Studio. Training
requires a paid plan; failed jobs auto-refund.
Start a training job on a ready dataset. Types: image_lora (60 cr,
8–400 images), video_lora
(200 cr, 6–200 clips), wan_pair
(300 cr), voice (30 cr, 1–25 samples). Accepts
webhook_url / webhook_secret — you'll receive training_job.awaiting_checkpoint (with
the selectable epochs) when sample grids are ready, and training_job.failed on failure.
POST /api/v1/datasets — create a dataset (image | video | voice; voice requires the consent record)
POST /api/v1/datasets/:id/uploads?filename=… — stream one
media file (raw body, not multipart)
POST /api/v1/datasets/:id/items — register items: uploaded
r2_keys, artifact ids from your runs, or HTTPS
url items (fetched server-side with size caps and a fail-closed
moderation scan; per-item rejections)
PATCH /api/v1/datasets/:id/items — bulk caption edits
GET /api/v1/training-jobs/:id — status + live
progress (phase, percent) + checkpoints when done
POST /api/v1/training-jobs/:id/pick-checkpoint — finalize
the winning epoch into a versioned model asset
POST /api/v1/training-jobs/:id/cancel — cancel (settles
to actual; deliberately fires no webhook)
GET /api/v1/training-prices — the live type/price registry
Webhooks
Instead of polling, receive an HTTPS POST to your server when a video generation or
render job completes or fails. Pass webhook_url (and optionally
webhook_secret) when creating the job.
Delivery
POST with JSON body. Your endpoint must respond
2xx within 25 seconds. URL must be HTTPS; private IPs,
localhost, and cloud-metadata addresses are rejected at job-creation time. Up to 3 delivery attempts with exponential backoff starting at 5 seconds.
Verification
When webhook_secret is set, each request carries an
X-Webhook-Signature: v1,<hmac> header. The signed
content is the concatenation
<webhookId>.<timestamp>.<body>
(HMAC-SHA256, hex-encoded). Use X-Webhook-ID for idempotent
processing.
| Event | Trigger |
|---|---|
| video.completed | Managed generation or refinement finished (status: complete). Signed when webhook_secret was set. Crash-recovery
emergency saves still fire this event. |
| video.failed | Managed generation or refinement errored (status: errored). |
| video.cancelled | Job cancelled; credits settled/refunded. |
| render.completed | An MCP-initiated MP4 render finished. status is
done; output.download_url contains the final URL. Render webhooks are not signed —
render_video does not accept a webhook_secret. |
| render.failed | An MCP-initiated render failed. status is failed; error contains the reason. |
| tool_run.completed | A workspace tool run finished — static image ads (POST /api/v1/static-ads / generate_static_ad), upscale_video, and any POST /api/v1/workspaces/:id/tools/:name call with a webhook_url. Payload carries run_id, workspace_id, tool, and the tool result. Signed when webhook_secret was supplied. |
| tool_run.failed | A workspace tool run failed. status is error; error contains the reason. Credits auto-refund
on runner crashes. |
| kernel_run.completed / .failed / .cancelled | A workflow-platform run reached a terminal state (set webhook_url on POST /workflows/:id/runs). Payload carries
run_id, workflow_id,
spec_version, settled credits, your
metadata, and (on success) the run
outputs. Signed when webhook_secret was supplied. |
| kernel_run.awaiting_approval | A run parked at an approval gate. Payload carries instance_key —
POST it to /api/v1/kernel-runs/:id/approval
with a decision to resume. |
| training_job.awaiting_checkpoint | A training job finished and is waiting for a checkpoint pick (payload carries the selectable epochs). Set webhook_url on POST /api/v1/training-jobs. |
| training_job.failed | A training job failed; credits settle to actual. Cancellation is your own call and deliberately emits no webhook. |
Generation and refinement both emit video.* when
webhook_url is set (SSE for progress; webhooks for terminal).
Payloads: docs/public-api.md § Webhooks.
| Header | Description |
|---|---|
| X-Webhook-ID | Unique delivery identifier — use for idempotent processing. |
| X-Webhook-Timestamp | Unix timestamp of delivery (seconds). |
| X-Webhook-Signature | HMAC-SHA256 signature, present only when webhook_secret was supplied. Format: v1,<hex-hmac>.
Signed content: <X-Webhook-ID>.<X-Webhook-Timestamp>.<raw body>. |
| User-Agent | Wavemaker/1.0 |
{
"event": "video.completed",
"job_id": "wf_abc123def456789",
"status": "complete",
"composition_id": "comp_xyz",
"created_at": "2026-04-01T12:03:45Z",
"completed_at": "2026-04-01T12:03:45Z"
} {
"event": "video.failed",
"job_id": "wf_abc123def456789",
"status": "errored",
"composition_id": null,
"error": "Generation failed — no composition could be saved",
"created_at": "2026-04-01T12:01:12Z",
"completed_at": "2026-04-01T12:01:12Z"
} {
"event": "render.completed",
"job_id": "rnd_xyz",
"status": "done",
"composition_id": "comp_xyz",
"output": { "download_url": "https://.../final.mp4" },
"created_at": "2026-04-01T12:05:00Z",
"completed_at": "2026-04-01T12:08:14Z"
} {
"event": "tool_run.completed",
"run_id": "wtr_abc123",
"workspace_id": "ws_xyz",
"tool": "generate_static_ad",
"status": "complete",
"result": { ... },
"completed_at": "2026-04-01T12:06:30Z"
} Usage
Retrieve usage statistics and remaining monthly quota for the calling API key.
Quotas are evaluated server-side on every call to POST /api/v1/videos.
| Parameter | Type | Default | Description |
|---|---|---|---|
| days | integer | 30 | Lookback period in days (1–90). Clamped to max(1, min(days, 90)). |
200
{
"period_days": 30,
"total_requests": 142,
"total_cost_cents": 8500,
"by_action": {
"video_generation": { "count": 42, "cost_cents": 8500 }
},
"quota": {
"quota_cents": null,
"used_cents": 8500,
"remaining_cents": null,
"unlimited": true
}
}
All fields use snake_case. Billing is credit-based and org-scoped (enforced with
402 on POST /api/v1/videos), so the per-key dollar quota is reported as unlimited:
quota.unlimited is true and
quota.quota_cents / quota.remaining_cents
are null.
MCP Integration
Wavemaker implements the Model Context Protocol, so you can use it as a tool in Cursor, Claude Desktop, Windsurf, or any
MCP-compatible client. The managed media MCP endpoint is at https://api.wavemaker.io/mcp and uses
OAuth 2.1 with PKCE — no API key required in your config. The first
connection opens a browser tab where you sign in with your Wavemaker account; subsequent
requests use an access token issued by Wavemaker. Product overview: /mcp (marketing page — not the protocol host). Setup walkthrough: connect blog post. Slug-run REST: /workflow-api. Comfy platform MCP → $PLATFORM_API_URL (not api.wavemaker.io)
— see
/platform.
Endpoints
/oauth/authorize — user consent & sign-in
/oauth/token — token issue / refresh
/oauth/register — dynamic client registration
Access tokens live 1 hour; refresh tokens 30 days. Plain PKCE is rejected (S256 only).
Scopes
video:generate ·
video:read ·
video:render
Scopes are advertised in the OAuth discovery metadata so clients can request them, but
the server does not currently use the granted-scope claim as an access
gate — every authenticated MCP client sees the full tool list. Plan for scope
enforcement in a future release. The same 30-rpm rate limit applies to every MCP call.
wait=true blocks with streamed progress notifications.
depth=site)
and return brand profile, images, content summary, and palette. Charges 5 credits
(refunded on failure).
render.completed webhook.
wait=true.
brand_kit_id on
create_workspace / POST /api/v1/workspaces.
brand_kit_id, model_overrides). Pair with describe_tool /
execute_tool in lean/compact mode.
purpose: "image" for a plain image (prompt renders
as-is) and image_model to pin the renderer. 8 credits
each.
multi_ar_pmax /
video_ad_sequence /
hook_variant /
photo_set). Optional
idempotency_key (auto-minted if omitted; REST still
requires the header). Poll with
creative_pack_status. Not registered in
lean/builder.
:06/:15/:30/:60 packs), or Podcast. Poll with generate_audio_status. Kill switch audioOutputModeEnabled (503 when OFF).
Distinct from video soundtrack tools.
audio_set_id or
audio_variant_id.
aus_* styles (optional
purpose filter). Sibling:
list_audio_treatments.
generate_static_ad.
list_image_styles. Catalog of ad-capable visual styles only.
applicable_purposes. style_id values for generate_static_ad / edit_static_ad (change_style).
Beyond this curated list, the MCP server also registers the full workspace tool catalog dynamically — every externally exposed pipeline tool (scraping, subject development,
image/clip generation, review, composition, refinement, upscale_video, and more) as its own MCP tool, executed inside a persistent workspace. Browse it
with
GET /api/v1/tools or the describe_tool MCP tool. Catalog Video adds its own dco_* family (prefer
composites dco_bootstrap_catalog,
dco_run_produce,
dco_get_status_bundle,
dco_review_variants,
dco_manage_delivery) — same service layer as
/api/v1/dco/*.
Registration modes: default full exposes
every static tool (~119, incl. DCO / creative packs / audio / kernel) plus the dynamic workspace
catalog (~100 more; live count in
docs/agent-capabilities.md).
compact keeps all static tools and promotes the lean research/media/planning
subset; remaining catalog stays behind
describe_tool +
execute_tool.
lean (≤56) is meta + managed escape hatches + that promoted
subset only — no DCO, creative packs, or hand-curated audio wrappers (audio generate/edit
still reachable via
execute_tool +
get_tool_run). Set via CONFIG KV
workspace_mcp:mode, header
X-Wavemaker-Mcp-Tools, or
?tools=. Local Cursor against wrangler should use
/mcp-api (API-key) rather than OAuth
/mcp.
Builder mode (?tools=builder): the workflow-platform
surface only, for agents that build and run workflows — kernel workflow/run/catalog
tools (search_hub,
run_hub_workflow,
estimate_workflow, get_run), Training Studio tools (create_dataset,
add_dataset_items,
pick_checkpoint, …),
draft_workflow (one-shot compiler-in-the-loop drafting
from a plain-language description — free), dynamic wf_<slug>
tools (your org's published workflows + your favorites register as first-class tools, cap
40), and billing — zero managed-video or DCO tools (~35 total). See
API & MCP for the map.
Add this to your .cursor/mcp.json (project-local) or
~/.cursor/mcp.json (global). Cursor opens the OAuth browser
flow on first use; no headers or keys go in the config.
{
"mcpServers": {
"wavemaker": {
"url": "https://api.wavemaker.io/mcp"
}
}
}
Add to your claude_desktop_config.json. mcp-remote
handles the OAuth flow and persists tokens locally.
{
"mcpServers": {
"wavemaker": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.wavemaker.io/mcp"]
}
}
}
Other clients (Windsurf, VS Code MCP, Codex) connect the same way — point them
at
https://api.wavemaker.io/mcp as a remote streamable-HTTP
MCP server. Direct curl to the MCP endpoint requires a
valid OAuth access token in the Authorization header, which
you obtain via the standard authorization-code + PKCE flow.
Errors
The API uses standard HTTP status codes. Error responses always include a JSON body
with an
error field describing the issue. Some 4xx responses also
include a machine-readable code and a structured
details object.
| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 202 | Accepted | Job dispatched. Response includes Retry-After. |
| 400 | Bad Request | Invalid JSON, missing/short prompt, unknown aspect_ratio, or invalid webhook_url. |
| 401 | Unauthorized | Missing/invalid Authorization header or API key. |
| 402 | Payment Required | Insufficient credits, CREDITS_OWED from a settlement
upcharge, or NO_BILLING_ORG (key's account has no
organization initialized). Response carries code + details. |
| 404 | Not Found | Job, composition, or resource does not exist (or is not visible to this API key). |
| 429 | Too Many Requests | Per-key rate limit exceeded (retryAfterSeconds +
Retry-After header) or monthly quota
exceeded (response includes a usage snapshot). |
| 500 | Server Error | Unexpected internal failure. Safe to retry with backoff. |
| 503 | Service Unavailable | Dispatch infrastructure (Inngest) temporarily unreachable. Response includes Retry-After: 60. |
{
"error": "Rate limit exceeded",
"retryAfterSeconds": 27
} {
"error": "Monthly quota exceeded",
"usage": {
"quotaCents": 5000,
"usedCents": 5012,
"remainingCents": 0
}
} {
"error": "Organization has 25 credits owed from a prior settlement upcharge. Clear the balance before starting a new generation.",
"code": "CREDITS_OWED",
"details": { "owed": 25, "since": "2026-03-30T10:11:12Z" }
} Ready to integrate? Create your API key to get started.
Manage API Keys →