curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"mask_image_url": "https://example.com/land/mask.png",
"num_constructions": 5
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/original.jpg",
"format": "16:9",
"marked_image_url": "https://example.com/land/marked.jpg",
"scenes": [
{ "image_url": "https://example.com/land/building1.jpg", "order": 1, "camera_movement": "dolly_in" },
{ "image_url": "https://example.com/land/building2.jpg", "order": 2 },
{ "image_url": "https://example.com/land/building3.jpg", "order": 3 }
],
"script": "Discover the potential of this stunning plot. From raw land to a modern family home, the transformation is breathtaking.",
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"language": "en",
"webhook_url": "https://yourserver.com/webhooks/vibepeak"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"voiceover_enabled": false,
"background_music": true,
"music_id": "3f9a1c62-5b4e-4a77-9d21-0c8b6e4f1a35",
"music_kind": "stock"
}'
const response = await fetch('https://api.vibepeak.ai/v1/land', {
method: 'POST',
headers: {
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_image_url: 'https://example.com/land/empty-plot.jpg',
format: '9:16'
})
});
const task = await response.json();
console.log(`Task created: ${task.task_id}`);
console.log(`Status: ${task.status}`);
console.log(`Credits: ${task.charged_credits}`);
console.log(`Breakdown:`, task.resolution.credits_breakdown);
import requests
response = requests.post(
'https://api.vibepeak.ai/v1/land',
headers={
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
json={
'source_image_url': 'https://example.com/land/empty-plot.jpg',
'format': '9:16'
}
)
task = response.json()
print(f"Task created: {task['task_id']}")
print(f"Status: {task['status']}")
print(f"Credits: {task['charged_credits']}")
print(f"Breakdown: {task['resolution']['credits_breakdown']}")
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 40,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 5,
"generated_scene_count": 3,
"generated_scenes": 15,
"generated_script": 5,
"total": 40
},
"used_overrides": []
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 15,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 0,
"generated_scene_count": 0,
"generated_scenes": 0,
"generated_script": 0,
"total": 15
},
"used_overrides": ["marked_image_url", "scenes", "script", "language", "voice_id"]
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1",
"request_id": "req_xyz123",
"details": {
"field": "scenes",
"issues": [
{
"path": "scenes",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1"
}
]
}
}
}
{
"error": {
"code": "INVALID_IMAGE_URL",
"message": "Failed to access image: fetch failed (ENOTFOUND)",
"request_id": "req_xyz123",
"details": {
"invalid_images": ["https://example.com/land/empty-plot.jpg"]
}
}
}
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "ASSET_ACCESS_DENIED",
"message": "Asset is not accessible",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "PLAN_REQUIRED",
"message": "API access requires a Plus, Pro, Max or Enterprise plan. Please upgrade your subscription.",
"request_id": "req_xyz123",
"details": {
"current_plan": "Free",
"required_plans": ["Plus", "Pro", "Max", "Enterprise"]
}
}
}
{
"error": {
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "You have reached your concurrent task limit of 1. Please wait for existing tasks to complete.",
"request_id": "req_xyz123",
"details": {
"limit": 1,
"in_flight": 1,
"plan": "Plus"
}
}
}
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Land transformation service is temporarily unavailable. Please try again.",
"request_id": "req_xyz123"
}
}
Videos
Create Land Transformation Video
Create an AI-powered cinematic video transforming an empty plot of land into proposed constructions
POST
/
v1
/
land
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"mask_image_url": "https://example.com/land/mask.png",
"num_constructions": 5
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/original.jpg",
"format": "16:9",
"marked_image_url": "https://example.com/land/marked.jpg",
"scenes": [
{ "image_url": "https://example.com/land/building1.jpg", "order": 1, "camera_movement": "dolly_in" },
{ "image_url": "https://example.com/land/building2.jpg", "order": 2 },
{ "image_url": "https://example.com/land/building3.jpg", "order": 3 }
],
"script": "Discover the potential of this stunning plot. From raw land to a modern family home, the transformation is breathtaking.",
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"language": "en",
"webhook_url": "https://yourserver.com/webhooks/vibepeak"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"voiceover_enabled": false,
"background_music": true,
"music_id": "3f9a1c62-5b4e-4a77-9d21-0c8b6e4f1a35",
"music_kind": "stock"
}'
const response = await fetch('https://api.vibepeak.ai/v1/land', {
method: 'POST',
headers: {
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_image_url: 'https://example.com/land/empty-plot.jpg',
format: '9:16'
})
});
const task = await response.json();
console.log(`Task created: ${task.task_id}`);
console.log(`Status: ${task.status}`);
console.log(`Credits: ${task.charged_credits}`);
console.log(`Breakdown:`, task.resolution.credits_breakdown);
import requests
response = requests.post(
'https://api.vibepeak.ai/v1/land',
headers={
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
json={
'source_image_url': 'https://example.com/land/empty-plot.jpg',
'format': '9:16'
}
)
task = response.json()
print(f"Task created: {task['task_id']}")
print(f"Status: {task['status']}")
print(f"Credits: {task['charged_credits']}")
print(f"Breakdown: {task['resolution']['credits_breakdown']}")
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 40,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 5,
"generated_scene_count": 3,
"generated_scenes": 15,
"generated_script": 5,
"total": 40
},
"used_overrides": []
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 15,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 0,
"generated_scene_count": 0,
"generated_scenes": 0,
"generated_script": 0,
"total": 15
},
"used_overrides": ["marked_image_url", "scenes", "script", "language", "voice_id"]
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1",
"request_id": "req_xyz123",
"details": {
"field": "scenes",
"issues": [
{
"path": "scenes",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1"
}
]
}
}
}
{
"error": {
"code": "INVALID_IMAGE_URL",
"message": "Failed to access image: fetch failed (ENOTFOUND)",
"request_id": "req_xyz123",
"details": {
"invalid_images": ["https://example.com/land/empty-plot.jpg"]
}
}
}
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "ASSET_ACCESS_DENIED",
"message": "Asset is not accessible",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "PLAN_REQUIRED",
"message": "API access requires a Plus, Pro, Max or Enterprise plan. Please upgrade your subscription.",
"request_id": "req_xyz123",
"details": {
"current_plan": "Free",
"required_plans": ["Plus", "Pro", "Max", "Enterprise"]
}
}
}
{
"error": {
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "You have reached your concurrent task limit of 1. Please wait for existing tasks to complete.",
"request_id": "req_xyz123",
"details": {
"limit": 1,
"in_flight": 1,
"plan": "Plus"
}
}
}
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Land transformation service is temporarily unavailable. Please try again.",
"request_id": "req_xyz123"
}
}
Creates a new land transformation video from a single photo of an empty plot. The system progressively builds the land through chained transitions: raw terrain → marked buildable area → proposed constructions. Each transition is rendered as a cinematic morph, resulting in a fluid visualization of what the land could become.
This endpoint is progressive — only
When
Credits are deducted at creation time. If the job later fails, the credits are refunded automatically — the webhook’s
Requests outside the 3-10 range fail with
Not every task passes through every stage. Supplying
For failed tasks:
Notes on the payload:
Asynchronous failures are reported through the task’s
See Error Handling for more details.
source_image_url and format are required. Everything else is optional and resolved server-side if omitted:
- No mask? The server auto-detects the buildable area via Gemini.
- No scenes? The server generates construction proposals based on terrain analysis.
- No script? The server auto-generates a narration script.
scenes[], script, mask_image_url), the server skips the corresponding generation steps. This avoids double-charging for assets you already created.
Request Body
string
required
Publicly accessible URL of a single photo of an empty plot of land.
- Must be a valid HTTP(S) URL
- Must be an image file (JPEG, PNG, or WebP)
- Only one image is accepted
Every image URL in the request is fetched and validated before the task is created. Unreachable images fail with
400 INVALID_IMAGE_URL, and images below the minimum resolution fail with 400 IMAGE_RESOLUTION_TOO_LOW. VibePeak-hosted assets belonging to another account are rejected with 403 ASSET_ACCESS_DENIED.string
required
Target video format / aspect ratio.
| Value | Description |
|---|---|
9:16 | Vertical video (portrait) — ideal for social media stories and reels |
16:9 | Horizontal video (landscape) — ideal for websites and presentations |
Mask & Area Overrides
string
Optional binary mask image (PNG) marking the buildable area with a neon outline and semi-transparent fill.When provided, the server uses this mask directly and skips auto-detection. Takes precedence over
area_polygon.Alias: mask_url is accepted and normalized to mask_image_url. If both are sent, mask_image_url wins.string
Optional pre-rendered marked terrain image (the
img1 frame — the source photo with the buildable area already highlighted).When provided, the server skips the marking render entirely and uses this frame directly. Like mask_image_url and area_polygon, supplying it waives the 5-credit mask charge, and the task reports marking: "mask".It takes precedence over both mask_image_url and area_polygon.marked_image_url also changes how the frame count is calculated when you supply scenes[] — see Frame Limits.object[]
Optional polygon coordinates marking the buildable area. Minimum 3 points required.Each point is an object with normalized coordinates:
Alias:
Show Point properties
Show Point properties
polygon is accepted and normalized to area_polygon.If none of mask_image_url, area_polygon or marked_image_url is provided, the server auto-detects the buildable area using Gemini vision analysis.Terrain & Construction Overrides
string
Optional terrain type hint, e.g.
"urban", "rural" or "industrial".Any non-empty string is accepted — the value is a free-form hint passed to the proposal generator, not a closed enum. If omitted, the server analyzes the image and auto-detects the terrain type.string[]
Optional array of construction prompts (1-8). One image is generated per prompt in the order provided.When provided, the server skips proposal auto-generation and renders exactly these constructions. Each prompt is still billed as a generated scene.Alias:
prompts is accepted and normalized to construction_prompts.integer
default:"3"
Optional number of constructions to auto-generate (2-8). Default is 3.Ignored if
scenes or construction_prompts are provided.string
Optional style hint that biases auto-generated proposals (e.g.,
"modern", "rustic", "minimalist").string
Optional exclusion hint for auto-generated proposals (e.g.,
"no swimming pools", "no glass facades").Scene Chain (Fully Resolved)
object[]
Optional fully-resolved scene chain (1-8 items). When provided, the server skips scene generation and renders these frames directly.Each scene is an object with:
Show Scene properties
Show Scene properties
string
Publicly accessible URL of the scene image. Optional at the schema level, but a scene without an image cannot be rendered — always send it when supplying a resolved chain.
string
Optional prompt that generated this scene.
integer
Optional display order within the chain. Must be 1 or greater —
0 is rejected with 400 VALIDATION_ERROR.string
Optional per-scene camera movement applied by the WAN renderer.One of
traveling_left, traveling_right, traveling_up, traveling_down, dolly_in, dolly_out, arc_left, arc_right. Omitted means the renderer picks the movement.Providing
scenes[] is the recommended path for first-party callers (like the VibePeak web app) that have already generated images interactively. This ensures you are only charged the base render cost (15 credits) and avoids double-charging for generated assets.Script, Voice & Music Overrides
string
Optional narration script for text-to-speech. Any non-empty string is accepted.
Unlike the narrated slideshow and real-estate endpoints,
/v1/land does not enforce a character-count range or reject digits and symbols. For natural pacing aim for roughly 65-85 characters per scene, but this is guidance, not validation.string
Language code for the auto-generated script and TTS pronunciation (e.g.,
en, es, de, fr, pt). Minimum 2 characters.string
Voice ID for narration. You can find available voices using the List Voices endpoint.Defaults to a platform voice if omitted.
boolean
Set to
false to render the video without narration. When false, the server skips text-to-speech entirely and script / voice_id may be omitted.Omitting this field leaves narration on.boolean
Set to
true to add a background music bed. Omitting this field, or sending false, renders the video without music.string
Optional Music Library track to pin, as a UUID. Without it, a
background_music: true job uses a randomly selected bed.music_id and music_kind must be sent together — sending one without the other fails with 400 VALIDATION_ERROR (music_id and music_kind must be provided together). A non-UUID value fails with music_id must be a valid UUID.Both fields are only honored when background_music is true; with music off the video stays silent regardless.string
Which catalogue
music_id refers to: stock (Music Library track) or ai (AI-generated track). Required whenever music_id is present.Subtitle & Elements Configuration
object
Subtitle configuration for word-level animated overlays. Same structure as other video endpoints — see Create Narrated Slideshow for the full field list.On this endpoint the object is passed through to the renderer without field-level validation, so a malformed key is accepted at request time and simply ignored downstream.
object
Configuration for visual elements and branding overlays (watermark, text, intro/outro). Same structure as other video endpoints — see Create Narrated Slideshow.The
intro_outro block is fully validated; the rest is passed through to the renderer. Browse the designs in the template gallery, or read the catalogue from GET /v1/intro-outro/templates.As on Create Narrated Slideshow, the overlays are applied after the video renders. The task passes through the transitional intro_outro status while that runs, and if the overlays cannot be applied the task still completes: the video is delivered without them and the result carries an intro_outro_warning.Unlike the real-estate and narrated-slideshow endpoints,
/v1/land does not pre-validate the watermark URL at request time, so it never returns INVALID_WATERMARK_URL. An unreachable watermark surfaces later as a render failure. Verify the URL before submitting.Webhook
string
HTTPS URL to receive a webhook notification when the task completes or fails.See Webhooks for payload format and verification details.
Webhook URLs must use HTTPS and cannot point to private/internal networks (SSRF protection). A rejected URL fails with
400 INVALID_WEBHOOK_URL.Response
Returns a202 Accepted response. Unlike other video endpoints, land tasks start in the preparing state while the server resolves inputs (auto-detecting the mask, generating scenes, creating the script), and they report credits under charged_credits with a resolution object.
Land responses are deliberately shaped differently from other video endpoints. Land tasks use
charged_credits and a resolution object; every other task type uses credits_charged and has no resolution. If you share response-parsing code across endpoints, handle both key names.string
required
Unique identifier for the task, in the form
task_<id>. Use this to poll for status via Get Task.The
task_id sent in the webhook payload is a different identifier (vp_land_<id>, the task’s external id). Correlate webhooks by storing both, or by matching on your own reference — do not assume the webhook task_id can be passed to GET /v1/tasks/{id}.string
required
Initial task status. Always
preparing for land tasks.string
required
Human-readable acknowledgement —
"Land transformation request accepted".string
required
ISO 8601 timestamp of task creation.
boolean
required
true for live keys. Requests made with a vpk_test_ key return false and are not charged — see Test Mode.string
required
Request identifier, useful when contacting support.
integer
required
Credits actually deducted for this request. See Credit Model for the breakdown.
object
Preparation metadata describing what the server will compute.
Show Resolution properties
Show Resolution properties
string
Current preparation stage — always
"preparing" at creation.integer
Preparation progress percentage (0-100). Starts at 10.
string[]
The request fields you supplied that overrode server-side generation, in canonical order. An explicit
background_music: false counts as an override; an omitted field does not.object
Detailed credit cost breakdown.
Show Credits breakdown properties
Show Credits breakdown properties
integer
Base render cost (15 credits).
integer
Cost for a server-generated mask (5 credits) — 0 if
mask_image_url, area_polygon or marked_image_url is provided.integer
Number of scenes the server will generate.
integer
Total cost for generated scenes (5 credits each).
integer
Cost for a server-generated script (5 credits) — 0 if
script is provided.integer
Total credits charged.
object
Convenience links:
self (the poll URL) and poll_interval_seconds.Response Headers
| Header | Description |
|---|---|
Location | URL to poll for task status (/v1/tasks/{task_id}) |
Retry-After | Recommended polling interval in seconds (30) |
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"mask_image_url": "https://example.com/land/mask.png",
"num_constructions": 5
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/original.jpg",
"format": "16:9",
"marked_image_url": "https://example.com/land/marked.jpg",
"scenes": [
{ "image_url": "https://example.com/land/building1.jpg", "order": 1, "camera_movement": "dolly_in" },
{ "image_url": "https://example.com/land/building2.jpg", "order": 2 },
{ "image_url": "https://example.com/land/building3.jpg", "order": 3 }
],
"script": "Discover the potential of this stunning plot. From raw land to a modern family home, the transformation is breathtaking.",
"voice_id": "EXAVITQu4vr4xnSDxMaL",
"language": "en",
"webhook_url": "https://yourserver.com/webhooks/vibepeak"
}'
curl -X POST https://api.vibepeak.ai/v1/land \
-H "Authorization: Bearer vpk_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"source_image_url": "https://example.com/land/empty-plot.jpg",
"format": "9:16",
"voiceover_enabled": false,
"background_music": true,
"music_id": "3f9a1c62-5b4e-4a77-9d21-0c8b6e4f1a35",
"music_kind": "stock"
}'
const response = await fetch('https://api.vibepeak.ai/v1/land', {
method: 'POST',
headers: {
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_image_url: 'https://example.com/land/empty-plot.jpg',
format: '9:16'
})
});
const task = await response.json();
console.log(`Task created: ${task.task_id}`);
console.log(`Status: ${task.status}`);
console.log(`Credits: ${task.charged_credits}`);
console.log(`Breakdown:`, task.resolution.credits_breakdown);
import requests
response = requests.post(
'https://api.vibepeak.ai/v1/land',
headers={
'Authorization': 'Bearer vpk_live_xxxxx',
'Content-Type': 'application/json'
},
json={
'source_image_url': 'https://example.com/land/empty-plot.jpg',
'format': '9:16'
}
)
task = response.json()
print(f"Task created: {task['task_id']}")
print(f"Status: {task['status']}")
print(f"Credits: {task['charged_credits']}")
print(f"Breakdown: {task['resolution']['credits_breakdown']}")
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 40,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 5,
"generated_scene_count": 3,
"generated_scenes": 15,
"generated_script": 5,
"total": 40
},
"used_overrides": []
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"task_id": "task_abc123xyz",
"status": "preparing",
"message": "Land transformation request accepted",
"created_at": "2026-08-31T12:15:00.000Z",
"livemode": true,
"request_id": "req_xyz123",
"charged_credits": 15,
"resolution": {
"stage": "preparing",
"progress": 10,
"credits_breakdown": {
"base_render": 15,
"generated_mask": 0,
"generated_scene_count": 0,
"generated_scenes": 0,
"generated_script": 0,
"total": 15
},
"used_overrides": ["marked_image_url", "scenes", "script", "language", "voice_id"]
},
"_links": {
"self": "/v1/tasks/task_abc123xyz",
"poll_interval_seconds": 30
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1",
"request_id": "req_xyz123",
"details": {
"field": "scenes",
"issues": [
{
"path": "scenes",
"message": "Land chains must contain 3 to 10 total frames including img0 and img1"
}
]
}
}
}
{
"error": {
"code": "INVALID_IMAGE_URL",
"message": "Failed to access image: fetch failed (ENOTFOUND)",
"request_id": "req_xyz123",
"details": {
"invalid_images": ["https://example.com/land/empty-plot.jpg"]
}
}
}
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "ASSET_ACCESS_DENIED",
"message": "Asset is not accessible",
"request_id": "req_xyz123"
}
}
{
"error": {
"code": "PLAN_REQUIRED",
"message": "API access requires a Plus, Pro, Max or Enterprise plan. Please upgrade your subscription.",
"request_id": "req_xyz123",
"details": {
"current_plan": "Free",
"required_plans": ["Plus", "Pro", "Max", "Enterprise"]
}
}
}
{
"error": {
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "You have reached your concurrent task limit of 1. Please wait for existing tasks to complete.",
"request_id": "req_xyz123",
"details": {
"limit": 1,
"in_flight": 1,
"plan": "Plus"
}
}
}
{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Land transformation service is temporarily unavailable. Please try again.",
"request_id": "req_xyz123"
}
}
Credit Model
Land transformation videos use a differentiated credit model based on the work the server performs:| Component | Credits | Condition |
|---|---|---|
| Base render | 15 | Always charged |
| Generated mask | 5 | Charged if you do not provide mask_image_url, area_polygon or marked_image_url |
| Generated scenes | 5 per scene | Charged for each scene the server generates (not for pre-provided scenes[]) |
| Generated script | 5 | Charged if you do not provide script |
scenes[] is absent, the number of generated scenes is construction_prompts.length, else num_constructions, else 3.
Example costs:
| Scenario | Calculation | Total |
|---|---|---|
| Minimal (1 image, auto everything) | 15 + 5 + 15 + 5 | 40 |
| With mask, auto scenes + script | 15 + 0 + 15 + 5 | 35 |
| With mask + 5 construction prompts | 15 + 0 + 25 + 5 | 45 |
Fully resolved (scenes[] + script + marked frame) | 15 + 0 + 0 + 0 | 15 |
Assets you pre-provide (
scenes[], script, mask_image_url, marked_image_url) are never regenerated and never re-charged. This prevents double-charging for first-party callers that already generated images interactively.charged_credits reflects the post-refund amount (typically 0).
Plan Access
| Plan | API Access | Concurrent Tasks |
|---|---|---|
| Free | No | — |
| Starter | No | — |
| Plus | Yes | 1 |
| Pro | Yes | 3 |
| Max | Yes | 5 |
| Enterprise | Yes | Custom |
Frame Limits
The video chain must contain 3 to 10 total frames. How the count is derived depends on whether you supplyscenes[]:
- Without
scenes[]— the count is2 + n, wherenisconstruction_prompts.length, elsenum_constructions, else 3. The two implied frames areimg0(original terrain) andimg1(marked terrain). - With
scenes[]— the count is1 + (marked_image_url ? 1 : 0) + scenes.length.img1is only counted when you actually supplymarked_image_url.
A resolved chain of 3 scenes with no
marked_image_url counts as 4 frames, not 5. If you are supplying a chain that already includes the marked frame as its first scene, do not also send marked_image_url — you would count it twice.400 VALIDATION_ERROR on the scenes field.
Polling
Land tasks flow throughpreparing → queued → processing → completed or failed.
If the request asked for post-processing, the task also reports a transitional status between processing and its final state, in this order: watermarking, intro_outro, subtitling. Each appears only when the corresponding feature was requested. Treat all three like processing and keep polling — the task always ends in completed or failed. A step that cannot finish does not fail the task: the video is delivered without that refinement and result carries a matching watermark_warning, intro_outro_warning or subtitle_warning. See Asynchronous Processing.
While in preparing, GET /v1/tasks/{task_id} returns a resolution object that advances through these stages:
| Stage | Progress |
|---|---|
preparing | 10 |
marking_resolved | 35 |
terrain_resolved | 50 |
scenes_resolved | 70 |
script_resolved | 85 |
resolved | 100 |
marked_image_url or mask_image_url resolves marking synchronously, so a fully-resolved request skips the intermediate stages and reports resolved / 100 on the first poll. Treat the ladder as monotonic but sparse — do not wait for a specific stage to appear.
Once marking is decided, the resolution object also carries marking, generated_image_count and used_overrides. It also carries resolved_inputs — a whitelisted view of what the server actually resolved (source_image_url, format, mask_image_url, area_polygon, terrain_type, scenes, construction_prompts, script), which is how you retrieve the generated scene image URLs and the auto-written script. Your webhook_url and other request fields are deliberately not echoed back. generated_image_count counts server-generated scene images plus one for an auto-generated mask, and stays 0 for a fully-resolved request. On land tasks the GET response uses charged_credits; other task types use credits_charged.
Tasks expire 24 hours after creation. After that,
GET /v1/tasks/{task_id} returns 404 TASK_EXPIRED and the video URL is no longer retrievable — download the result before then.Webhook Payload
When the task completes or fails, a POST request is sent to yourwebhook_url. See Webhooks for verification.
Webhook Headers
| Header | Value |
|---|---|
X-VibePeak-Event | task.completed or task.failed |
X-VibePeak-Signature | HMAC-SHA256, hex encoded |
X-VibePeak-Timestamp | Unix timestamp in seconds |
The signed message is
{timestamp}.{raw body}, not the body alone. Concatenate the X-VibePeak-Timestamp value, a literal ., and the exact raw request body before computing the HMAC, or verification will fail.The event name lives in the
X-VibePeak-Event header, not in the body. There is no event field inside the JSON.Payload
{
"task_id": "vp_land_gXATHu1cGcD9Ht9s5-19I",
"status": "completed",
"result": {
"video_url": "https://<project>.supabase.co/storage/v1/object/public/vibepeak/<user-id>/projects/land/<project-id>/final-1788179592692.mp4",
"cover_image_url": "https://storage.googleapis.com/vibepeak-api-images/covers/task_yo9RC7ZrE9YuFx4yXbg4N.jpg"
},
"charged_credits": 15,
"resolution": {
"marking": "mask",
"used_overrides": ["marked_image_url", "scenes", "script", "language"],
"generated_image_count": 0
}
}
{
"task_id": "vp_land_gXATHu1cGcD9Ht9s5-19I",
"status": "failed",
"error": {
"code": "GENERATION_FAILED",
"message": "Land generation failed"
},
"charged_credits": 0,
"resolution": {
"marking": "auto",
"generated_image_count": 4,
"used_overrides": []
}
}
The webhook
task_id is the task’s external id (vp_land_<id>), which is not the task_id returned by POST /v1/land (task_<id>) and cannot be passed to GET /v1/tasks/{id}. Store the value returned at creation and correlate on your side.resolutionis the typed shape only —marking,generated_image_count,used_overrides. It does not includestage,progressorcredits_breakdown; those appear only on the polling response.resolutionis omitted entirely if marking was never resolved (for example, a failure during preparation).resultcarries onlyvideo_urlandcover_image_url. There is noduration_seconds,resolutionorexpires_atinsideresult.cover_image_urlis present only if cover generation succeeded. Note that it is keyed by the internaltask_<id>, while the payload’stask_idis thevp_land_<id>external id.- There is no
event,typeortimestampfield in the body. Distinguish outcomes withstatus, or read theX-VibePeak-Eventheader.
marking field indicates which method was used to identify the buildable area:
| Value | Meaning |
|---|---|
mask | A pre-provided marked_image_url or mask_image_url was used. marked_image_url takes precedence when both are sent. |
polygon | A pre-provided area_polygon was rasterized into a mask |
auto | Gemini auto-detected the buildable area |
fullimage | Reserved. Present in the type contract but not currently emitted by any code path — do not branch on it. |
Error Codes
| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid request parameters, frame count outside 3-10, order below 1, or music_id/music_kind not sent together |
INVALID_IMAGE_URL | 400 | An image URL is unreachable, not an image, or too large |
IMAGE_RESOLUTION_TOO_LOW | 400 | An image is below the minimum resolution |
INVALID_WEBHOOK_URL | 400 | Webhook URL failed SSRF validation |
INVALID_API_KEY | 401 | Invalid or missing API key |
INSUFFICIENT_CREDITS | 402 | Not enough credits to process the request |
ASSET_ACCESS_DENIED | 403 | A VibePeak-hosted image URL belongs to another account |
PLAN_REQUIRED | 403 | Plan doesn’t include API access |
CONCURRENCY_LIMIT_EXCEEDED | 409 | Concurrent task limit reached |
SERVICE_UNAVAILABLE | 503 | The job could not be enqueued; credits are refunded automatically |
error object and the failure webhook:
| Code | Description |
|---|---|
GENERATION_FAILED | The land render failed downstream |
GENERATION_TIMEOUT | Generation exceeded the 15-minute limit |
Next Steps
After creating a task:- Poll for status: Use Get Task with the
task_idfrom the 202 response, every 30 seconds. - Wait for webhook: If configured, receive notification when complete — remembering the id difference noted above.
- Download video: Access
result.video_urlwithin 24 hours of creation.

